diff --git a/README b/README deleted file mode 100644 index 91d9b83..0000000 --- a/README +++ /dev/null @@ -1,23 +0,0 @@ -Live demo -[http://browser.openworm.org] - -This is a C. elegans WebGL body browser coded as part of the OpenWorm project -[http://www.openworm.org/] - -The C. elegans model is provided by the VirtualWorm project -[http://caltech.wormbase.org/virtualworm/] - -We are using the open-3d-viewer as WebGl engine (ex-Google Body Browser) -[http://code.google.com/p/open-3d-viewer/] - -Projects in the repo: - -org.openworm.wormbrowser - google-app-engine web application project -org.openworm.wormbrowser.utils - a set of python scripts for the generation of the open-3d-viewer metadata - -Acknowledgements: - -Jeff Bush for help with WebGL-loader and open3d-viewer -[http://www.coderforlife.com/] diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7d6059 --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# OpenWorm Browser + +Live demo: [http://browser.openworm.org](http://browser.openworm.org) + +This is a C. elegans WebGL body browser coded as part of the OpenWorm project: [http://www.openworm.org/](http://www.openworm.org/) + +The C. elegans model is provided by the VirtualWorm project: [http://caltech.wormbase.org/virtualworm/](http://caltech.wormbase.org/virtualworm/) + +We are using the open-3d-viewer as WebGL engine (ex-Google Body Browser): [http://code.google.com/p/open-3d-viewer/](http://code.google.com/p/open-3d-viewer/) + +## Projects in the Repo + +| Directory | Description | +|-----------|-------------| +| `wormbrowser-appengine` | **Current** - Maven-based Google App Engine Java 17 project | +| `org.openworm.wormbrowser` | Legacy Eclipse-based App Engine project (Java 8, deprecated) | +| `org.openworm.wormbrowser.utils` | Python scripts for generating open-3d-viewer metadata | + +## Prerequisites + +- Java 17 JDK +- Maven 3.8+ +- Google Cloud SDK with `gcloud` CLI +- Authenticated with `gcloud auth login` + +### macOS Installation (Homebrew) + +```bash +brew install openjdk@17 maven +export JAVA_HOME="/opt/homebrew/opt/openjdk@17" +export PATH="/opt/homebrew/opt/openjdk@17/bin:$PATH" +``` + +## Local Development + +```bash +cd wormbrowser-appengine + +# Build the project +mvn clean package + +# Run locally with Jetty (http://localhost:8080) +mvn jetty:run +``` + +### Local Testing Checklist + +1. Open http://localhost:8080/ - WebGL 3D visualization should load +2. Open http://localhost:8080/org_openworm_wormbrowser - Should display "I write ze codez, I am cool!" +3. Verify no console errors in browser DevTools + +## Deployment to Google App Engine + +### Deploy to Staging (No Traffic) + +```bash +cd wormbrowser-appengine + +# Deploy without routing traffic +mvn appengine:deploy -Dapp.deploy.promote=false +``` + +This deploys version `java17-v1` to the `wormbrowser-release` project without affecting production traffic. + +### Test Staging + +```bash +# Test the staging URL directly +curl https://java17-v1-dot-wormbrowser-release.appspot.com/org_openworm_wormbrowser +``` + +### Promote to Production + +Once staging is verified: + +```bash +# Route 100% of traffic to the new version +gcloud app services set-traffic default --splits=java17-v1=1 --project=wormbrowser-release +``` + +### Rollback (If Needed) + +```bash +# List available versions +gcloud app versions list --project=wormbrowser-release + +# Route traffic back to previous version +gcloud app versions migrate --project=wormbrowser-release +``` + +## Project Structure + +``` +wormbrowser-appengine/ +├── pom.xml # Maven build config +├── src/main/java/org/openworm/wormbrowser/ +│ └── WorkbrowserServlet.java # Jakarta Servlet (Java 17) +├── src/main/webapp/ +│ ├── WEB-INF/web.xml # Jakarta EE 10 web descriptor +│ ├── index.html # Main WebGL application +│ ├── main_ui.css +│ ├── scripts/ # JavaScript files +│ ├── models/Virtual_Worm/ # 3D model data (~25MB) +│ └── img/ +└── src/main/appengine/ + └── app.yaml # App Engine Java 17 runtime config +``` + +## Acknowledgements + +- Jeff Bush for help with WebGL-loader and open3d-viewer: [http://www.coderforlife.com/](http://www.coderforlife.com/) diff --git a/wormbrowser-appengine/pom.xml b/wormbrowser-appengine/pom.xml new file mode 100644 index 0000000..d59e70b --- /dev/null +++ b/wormbrowser-appengine/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + org.openworm + wormbrowser + 2.0.0 + war + + OpenWorm Browser + WebGL 3D visualization of C. elegans + https://github.com/openworm/wormbrowser + + + UTF-8 + 17 + 17 + 2.0.29 + + + + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + provided + + + + + com.google.appengine + appengine-api-1.0-sdk + ${appengine.version} + + + + + org.junit.jupiter + junit-jupiter + 5.10.0 + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + false + + + + + + com.google.cloud.tools + appengine-maven-plugin + 2.5.0 + + wormbrowser-release + java17-v1 + + + + + + org.eclipse.jetty.ee10 + jetty-ee10-maven-plugin + 12.0.7 + + + / + + + 8080 + + + + + + diff --git a/wormbrowser-appengine/src/main/appengine/app.yaml b/wormbrowser-appengine/src/main/appengine/app.yaml new file mode 100644 index 0000000..28e9ee6 --- /dev/null +++ b/wormbrowser-appengine/src/main/appengine/app.yaml @@ -0,0 +1,51 @@ +runtime: java17 +instance_class: F2 + +automatic_scaling: + min_idle_instances: 0 + max_idle_instances: 1 + +handlers: + # Static images with long cache + - url: /img/(.*) + static_files: img/\1 + upload: img/.* + expiration: "30d" + + # JavaScript files + - url: /scripts/(.*) + static_files: scripts/\1 + upload: scripts/.* + expiration: "7d" + + # 3D model files with long cache + - url: /models/(.*) + static_files: models/\1 + upload: models/.* + expiration: "30d" + + # CSS file + - url: /main_ui.css + static_files: main_ui.css + upload: main_ui.css + expiration: "7d" + + # Favicon + - url: /favicon.ico + static_files: favicon.ico + upload: favicon.ico + expiration: "30d" + + # No WebGL fallback page + - url: /no_webgl.html + static_files: no_webgl.html + upload: no_webgl.html + + # Root - index.html + - url: / + static_files: index.html + upload: index.html + + # Servlet endpoint + - url: /org_openworm_wormbrowser + script: auto diff --git a/wormbrowser-appengine/src/main/java/org/openworm/wormbrowser/WorkbrowserServlet.java b/wormbrowser-appengine/src/main/java/org/openworm/wormbrowser/WorkbrowserServlet.java new file mode 100644 index 0000000..b4a3e42 --- /dev/null +++ b/wormbrowser-appengine/src/main/java/org/openworm/wormbrowser/WorkbrowserServlet.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * The MIT License (MIT) + * + * Copyright (c) 2011, 2013 OpenWorm. + * http://openworm.org + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License + * which accompanies this distribution, and is available at + * http://opensource.org/licenses/MIT + * + * Contributors: + * OpenWorm - http://openworm.org/people.html + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + *******************************************************************************/ + +package org.openworm.wormbrowser; + +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +@WebServlet(urlPatterns = {"/org_openworm_wormbrowser"}) +public class WorkbrowserServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + resp.getWriter().println("I write ze codez, I am cool!"); + } +} diff --git a/wormbrowser-appengine/src/main/webapp/WEB-INF/web.xml b/wormbrowser-appengine/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..dbc833d --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,14 @@ + + + + OpenWorm Browser + + + index.html + + + diff --git a/wormbrowser-appengine/src/main/webapp/favicon.ico b/wormbrowser-appengine/src/main/webapp/favicon.ico new file mode 100644 index 0000000..06438ad Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/favicon.ico differ diff --git a/wormbrowser-appengine/src/main/webapp/img/ajax-loader.gif b/wormbrowser-appengine/src/main/webapp/img/ajax-loader.gif new file mode 100644 index 0000000..97f525d Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/ajax-loader.gif differ diff --git a/wormbrowser-appengine/src/main/webapp/img/caltechlogo.gif b/wormbrowser-appengine/src/main/webapp/img/caltechlogo.gif new file mode 100644 index 0000000..7f4c34e Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/caltechlogo.gif differ diff --git a/wormbrowser-appengine/src/main/webapp/img/checker.png b/wormbrowser-appengine/src/main/webapp/img/checker.png new file mode 100644 index 0000000..d09ffe7 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/checker.png differ diff --git a/wormbrowser-appengine/src/main/webapp/img/label_close.png b/wormbrowser-appengine/src/main/webapp/img/label_close.png new file mode 100644 index 0000000..3f09ce0 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/label_close.png differ diff --git a/wormbrowser-appengine/src/main/webapp/img/label_expand.png b/wormbrowser-appengine/src/main/webapp/img/label_expand.png new file mode 100644 index 0000000..d8bc58f Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/label_expand.png differ diff --git a/wormbrowser-appengine/src/main/webapp/img/label_pin_and_close.png b/wormbrowser-appengine/src/main/webapp/img/label_pin_and_close.png new file mode 100644 index 0000000..d387533 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/label_pin_and_close.png differ diff --git a/wormbrowser-appengine/src/main/webapp/img/logo.png b/wormbrowser-appengine/src/main/webapp/img/logo.png new file mode 100644 index 0000000..55c91d5 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/logo.png differ diff --git a/wormbrowser-appengine/src/main/webapp/img/logodefault.png b/wormbrowser-appengine/src/main/webapp/img/logodefault.png new file mode 100644 index 0000000..a715f47 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/logodefault.png differ diff --git a/wormbrowser-appengine/src/main/webapp/img/social-facebook.png b/wormbrowser-appengine/src/main/webapp/img/social-facebook.png new file mode 100644 index 0000000..78d2d84 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/social-facebook.png differ diff --git a/wormbrowser-appengine/src/main/webapp/img/social-twitter.png b/wormbrowser-appengine/src/main/webapp/img/social-twitter.png new file mode 100644 index 0000000..df6bc2d Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/social-twitter.png differ diff --git a/wormbrowser-appengine/src/main/webapp/img/toggle_multiple_sliders.png b/wormbrowser-appengine/src/main/webapp/img/toggle_multiple_sliders.png new file mode 100644 index 0000000..dc5b4ed Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/toggle_multiple_sliders.png differ diff --git a/wormbrowser-appengine/src/main/webapp/img/toggle_single_slider.png b/wormbrowser-appengine/src/main/webapp/img/toggle_single_slider.png new file mode 100644 index 0000000..e17d406 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/toggle_single_slider.png differ diff --git a/wormbrowser-appengine/src/main/webapp/img/wormbase.png b/wormbrowser-appengine/src/main/webapp/img/wormbase.png new file mode 100644 index 0000000..7f993e1 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/img/wormbase.png differ diff --git a/wormbrowser-appengine/src/main/webapp/index.html b/wormbrowser-appengine/src/main/webapp/index.html new file mode 100644 index 0000000..fbccc79 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/index.html @@ -0,0 +1,152 @@ + + + +OpenWorm Browser + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+ + + + +
+OpenWorm Browser Quick guide ('?' to toggle) +
+
+Navigation + +
+
+Slider + +
+
+Selection + +
+
+
+Quick guide ('?' to toggle) +
+ + +
+
+
+
+
+
+
+
+ +
+

OpenWorm Browser loading

+
+

Loading time may vary depending on your system

+
+ + + diff --git a/wormbrowser-appengine/src/main/webapp/main_ui.css b/wormbrowser-appengine/src/main/webapp/main_ui.css new file mode 100644 index 0000000..7a286e1 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/main_ui.css @@ -0,0 +1,208 @@ +/** + * CSS styles for main UI components of open-3d-viewer + */ +body { + margin: 0px; + overflow:hidden; + background-color: #92be23; + font-family: Helvetica, Arial, sans-serif; + font-size:12px; +} + + + +/* Tweaks to jQuery theme */ +.ui-widget { + font-family: arial; + font-size: 10pt; +} + +.ui-autocomplete { + max-height: 200px; + overflow-y: hidden; + /* prevent horizontal scrollbar */ + overflow-x: hidden; + /* add padding to account for vertical scrollbar */ + padding-right: 20px; +} + +.label_select, .label_select_expandable, .label_pin, .label_pin_expandable { + position: absolute; + border-radius: 3px; + white-space: nowrap; + font-family: arial, sans-serif; + font-size: 12px; + text-align: center; + background-repeat: no-repeat; +} + +.label_select { + padding: 3px 37px 3px 5px; + background-image: url('img/label_pin_and_close.png'); + background-position: right center; + background-color: #fff; + border: 1px solid #333; + color: #333; + z-index: 5; +} + +.label_select_expandable { + padding: 3px 37px 3px 21px; + background-image: url('img/label_expand.png'), url('img/label_pin_and_close.png'); + background-position: left center, right center; + background-color: #fff; + border: 1px solid #333; + color: white; + z-index: 5; +} + +.label_pin { + padding: 3px 21px 3px 5px; + font-weight: bold; + background-image: url('img/label_close.png'); + background-position: right center; + background-color: #466a15; + border: 1px solid #000; + color: white; + z-index: 4; +} + +.label_pin_expandable { + padding: 3px 21px 3px 21px; + font-weight: bold; + background-image: url('img/label_expand.png'), url('img/label_close.png'); + background-position: left center, right center; + background-color: #466a15; + border: 1px solid #000; + color: #000; + z-index: 4; +} + +.help, .help-hidden { + background: none repeat scroll 0 0 #FFFFFF; + border: 1px solid #466a15; + border-radius: 7px 7px 7px 7px; + font-family: arial; + font-size: 12px; + left: 100%; + padding: 7px; + position: absolute; + bottom: 45px; + z-index: 50; +} + +.help { + margin-left: -395px; + margin-top: -430px; + width: 370px; + display:none; + opacity: 0.85; +} + +.help-hidden { + display: block; + margin-left: -175px; + margin-top: -40px; + width: 150px; + opacity: 0.4; +} + +.loading-feedback { + position: absolute; + left: 47%; + margin-left: -50px; + width: 200px; + top: 47%; + font-family: arial; + font-size: 12px; + border: 1px solid white; + border-radius: 7px 7px 7px 7px; + text-align: center; + padding: 7px; + z-index: 50; +} + +.indicator { + position:absolute; + width:10px; + height:10px; + background:#000; + z-index:1000; + border-radius: 5px 5px 5px 5px; + left: -100px; + top: -100px; +} + + +.attrib_zy { + position: absolute; + background-color:#466a15; + top: 100%; + z-index: 5; + width: 100%; + height: 36px; + margin: -36px 0 0 0; + font-size:11px; + padding-top:7px; + /* color:#91ABC9; */ + border-top: 1px solid #DFDFDF; +} + +.attrib_zy a{ + text-decoration:none; + color:#92be23; +} + +.attrib_zy span{ + /* color: #666; */ +} + +.attrib_zy img{ + vertical-align:middle; +} + +#footer-left{ float:left; margin: 0 0 0 16px; color:white; } +#footer-right{ float:right; margin: 3px 12px 0 0; color:white; } + +.pop{ display:none; } +.validationmsg{ color:red; } + +#sharethis{ + color:white; + font-weight:bold; + float:right; +} + +#copyright{ + position:absolute; + top:100%; + left:100%; + margin: -32px 0 0 -395px; + font-size:11px; + color:white; +} + +#wormbaselogo{ + position:absolute; + cursor:pointer; + top:550px; + left:7px; + width:70px; + height:70px; + background-image:url("img/wormbase.png"); + z-index: 5; +} + +#loader-img { + width:16px; + height:16px; + margin: auto; + background-image:url("img/ajax-loader.gif"); +} + +#appstore{ + margin:0 auto; + width:116px; + height:40px; + margin-top:10px; +} diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/00c50ff0.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/00c50ff0.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..9ea8e0a Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/00c50ff0.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/03c8448a.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/03c8448a.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..cd8ff24 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/03c8448a.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/1266f863.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/1266f863.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..bf9ea2d Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/1266f863.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/267cfce8.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/267cfce8.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..fdcf5d3 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/267cfce8.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/3fc12c12.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/3fc12c12.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..75dfcf3 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/3fc12c12.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/4940a7a2.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/4940a7a2.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..5dfb5cd Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/4940a7a2.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/527bd07e.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/527bd07e.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..4ff89fb Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/527bd07e.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/53f3b3bd.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/53f3b3bd.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..e9dc001 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/53f3b3bd.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/55bbb055.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/55bbb055.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..a4f76fa Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/55bbb055.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/57c495b3.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/57c495b3.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..ef463c1 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/57c495b3.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/5ec61bf2.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/5ec61bf2.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..43785f7 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/5ec61bf2.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/65fda73e.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/65fda73e.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..7163b96 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/65fda73e.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/68eacfe3.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/68eacfe3.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..a23edb6 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/68eacfe3.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/82f5d638.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/82f5d638.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..009204f Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/82f5d638.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/88879a20.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/88879a20.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..2386ce3 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/88879a20.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/89e6fddc.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/89e6fddc.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..73139e6 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/89e6fddc.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/8e763d75.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/8e763d75.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..b8d0f88 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/8e763d75.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/9ef380e0.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/9ef380e0.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..13f55b8 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/9ef380e0.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/Virtual_Worm_February_2012.js b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/Virtual_Worm_February_2012.js new file mode 100644 index 0000000..4c79aeb --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/Virtual_Worm_February_2012.js @@ -0,0 +1,782 @@ +/******************************************************************************* + * The MIT License (MIT) + * + * Copyright (c) 2011, 2013 OpenWorm. + * http://openworm.org + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License + * which accompanies this distribution, and is available at + * http://opensource.org/licenses/MIT + * + * Contributors: + * OpenWorm - http://openworm.org/people.html + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + *******************************************************************************/ + +MODELS['Virtual_Worm_February_2012.obj'] = { + materials: { + 'vulval_muscle': { + Ka: [0, 0, 0], + Kd: [122, 163, 81], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'sphnc_&_anal_dep_musc': { + Ka: [0, 0, 0], + Kd: [81, 122, 0], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'uterine_muscle': { + Ka: [0, 0, 0], + Kd: [122, 163, 0], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'uterus': { + Ka: [0, 0, 0], + Kd: [122, 163, 163], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'seam_cell': { + Ka: [0, 0, 0], + Kd: [163, 81, 40], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'intestine': { + Ka: [0, 0, 0], + Kd: [204, 163, 204], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'interneuron': { + Ka: [0, 0, 0], + Kd: [204, 40, 0], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'socketcell': { + Ka: [0, 0, 0], + Kd: [204, 122, 122], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'temp_drg_color': { + Ka: [0, 0, 0], + Kd: [197, 41, 0], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'dtc_&_somatic_gonad': { + Ka: [0, 0, 0], + Kd: [122, 81, 204], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'hypodermis': { + Ka: [0, 0, 0], + Kd: [175, 156, 137], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'polymodalneuron': { + Ka: [0, 0, 0], + Kd: [196, 1, 1], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'motor_neuron': { + Ka: [0, 0, 0], + Kd: [122, 81, 190], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'neurunkfunc': { + Ka: [0, 0, 0], + Kd: [122, 0, 40], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'stomatoint_muscle': { + Ka: [0, 0, 0], + Kd: [0, 81, 0], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'sensoryneuron': { + Ka: [0, 0, 0], + Kd: [204, 81, 190], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'vpi_&_vir': { + Ka: [0, 0, 0], + Kd: [122, 81, 40], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'coelomocyte': { + Ka: [0, 0, 0], + Kd: [204, 163, 0], + Ks: [127, 127, 127], + Ns: 96.078430, + d: 12 + }, + 'even': { + Ka: [0, 0, 0], + Kd: [163, 204, 122], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'glr_cells': { + Ka: [0, 0, 0], + Kd: [190, 122, 0], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'marginal_cells': { + Ka: [0, 0, 0], + Kd: [190, 40, 190], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'excretory_duct_cell': { + Ka: [0, 0, 0], + Kd: [163, 122, 81], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'body_wall_muscle': { + Ka: [0, 0, 0], + Kd: [40, 81, 0], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'sheathother': { + Ka: [0, 0, 0], + Kd: [40, 122, 122], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'excretory_gland_cells': { + Ka: [0, 0, 0], + Kd: [122, 122, 163], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'vulva_epithelium': { + Ka: [0, 0, 0], + Kd: [122, 122, 204], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'germline': { + Ka: [0, 0, 0], + Kd: [0, 40, 81], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'pharyngeal_epithelium': { + Ka: [0, 0, 0], + Kd: [122, 81, 81], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'head_mesodermal_cell': { + Ka: [0, 0, 0], + Kd: [204, 81, 40], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'rectal_epithelium': { + Ka: [0, 0, 0], + Kd: [122, 81, 81], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'odd': { + Ka: [0, 0, 0], + Kd: [40, 163, 0], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'phary_&_rect_glands': { + Ka: [0, 0, 0], + Kd: [163, 163, 204], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'excretory_cell': { + Ka: [0, 0, 0], + Kd: [163, 40, 81], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'excretory_pore_cell': { + Ka: [0, 0, 0], + Kd: [188, 197, 114], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'arcade_cells': { + Ka: [0, 0, 0], + Kd: [163, 163, 0], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'spermatheca': { + Ka: [0, 0, 0], + Kd: [40, 122, 204], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + }, + 'spermath_uterin_valve': { + Ka: [0, 0, 0], + Kd: [81, 81, 122], + Ks: [0, 0, 0], + Ns: 96.078430, + d: 12 + } + }, + decodeParams: { + decodeOffsets: [-619,-1417,-8191,0,0,-511,-511,-511], + decodeScales: [0.000498,0.000498,0.000498,0.000978,0.000978,0.001957,0.001957,0.001957] + }, + urls: { + 'df4944b1.Virtual_Worm_February_2012.utf8': [ + { material: 'arcade_cells', + attribRange: [0, 14853], + indexRange: [118824, 29504], + bboxes: 207336, + names: ['arcade_cell_ant', 'arcade_cell_post'], + lengths: [47040, 41472] + } + ], + 'fd0da1ba.Virtual_Worm_February_2012.utf8': [ + { material: 'body_wall_muscle', + attribRange: [0, 55294], + indexRange: [442352, 110457], + bboxes: 3208384, + names: ['mu_bod_vl22', 'mu_bod_vl20', 'mu_bod_vl18', 'mu_bod_vl16', 'mu_bod_vl14', 'mu_bod_vl12', 'mu_bod_vl10', 'mu_bod_vl8', 'mu_bod_vl6', 'mu_bod_vl4', 'mu_bod_vl2', 'mu_bod_vl21', 'mu_bod_vl19', 'mu_bod_vl17', 'mu_bod_vl15', 'mu_bod_vl13', 'mu_bod_vl11', 'mu_bod_vl9', 'mu_bod_vl7', 'mu_bod_vl5', 'mu_bod_vl3'], + lengths: [34656, 34944, 28800, 18576, 26736, 27120, 22464, 11616, 7824, 5376, 11232, 10848, 7968, 13728, 14880, 11040, 12576, 12432, 8400, 8400, 1755] + }, + { material: 'body_wall_muscle', + attribRange: [773723, 55294], + indexRange: [1216075, 110297], + bboxes: 3208510, + names: ['mu_bod_vl3', 'mu_bod_vl1', 'mu_bod_vr22', 'mu_bod_vr20', 'mu_bod_vr18', 'mu_bod_vr16', 'mu_bod_vr14', 'mu_bod_vr12', 'mu_bod_vr10', 'mu_bod_vr8', 'mu_bod_vr6', 'mu_bod_vr4', 'mu_bod_vr2', 'mu_bod_vr21', 'mu_bod_vr19', 'mu_bod_vr17', 'mu_bod_vr15', 'mu_bod_vr13', 'mu_bod_vr11', 'mu_bod_vr9'], + lengths: [5445, 9792, 33216, 36480, 28848, 18672, 26736, 27984, 21312, 11616, 6192, 6000, 12384, 12528, 8016, 12480, 14880, 12624, 10944, 14742] + }, + { material: 'body_wall_muscle', + attribRange: [1546966, 55294], + indexRange: [1989318, 110298], + bboxes: 3208630, + names: ['mu_bod_vr9', 'mu_bod_vr7', 'mu_bod_vr5', 'mu_bod_vr3', 'mu_bod_vr1', 'mu_bod_dr23', 'mu_bod_dr22', 'mu_bod_dr20', 'mu_bod_dr18', 'mu_bod_dr16', 'mu_bod_dr14', 'mu_bod_dr12', 'mu_bod_dr10', 'mu_bod_dr8', 'mu_bod_dr6', 'mu_bod_dr4', 'mu_bod_dr2', 'mu_bod_dr3', 'mu_bod_dr5', 'mu_bod_dr7', 'mu_bod_dr9', 'mu_bod_dr11', 'mu_bod_dr21', 'mu_bod_dr19', 'mu_bod_dr17', 'mu_bod_dr15', 'mu_bod_dr13', 'mu_bod_dl22'], + lengths: [11514, 8400, 9696, 5904, 9792, 20784, 17760, 24240, 15984, 14448, 14448, 16224, 12624, 12864, 5856, 6720, 8352, 6096, 9840, 8592, 10992, 10896, 10944, 7248, 11184, 13488, 8544, 17460] + }, + { material: 'body_wall_muscle', + attribRange: [2320212, 55294], + indexRange: [2762564, 110341], + bboxes: 3208798, + names: ['mu_bod_dl22', 'mu_bod_dl20', 'mu_bod_dl18', 'mu_bod_dl16', 'mu_bod_dl14', 'mu_bod_dl12', 'mu_bod_dl10', 'mu_bod_dl8', 'mu_bod_dl6', 'mu_bod_dl4', 'mu_bod_dl2', 'mu_bod_dl23', 'mu_bod_dl21', 'mu_bod_dl19', 'mu_bod_dl17', 'mu_bod_dl15', 'mu_bod_dl3', 'mu_bod_dl5', 'mu_bod_dl7', 'mu_bod_dl9', 'mu_bod_dl11', 'mu_bod_dl13', 'mu_bod_vr23', 'mu_bod_vl23', 'mu_bod_dr1', 'mu_bod_dr24', 'mu_bod_dl1', 'mu_bod_dl24'], + lengths: [300, 19584, 16080, 14448, 14448, 15936, 12576, 11760, 7728, 6720, 8376, 20688, 11016, 7272, 12192, 12120, 6144, 9816, 8496, 10968, 11016, 8544, 23040, 23040, 9744, 18480, 9840, 651] + }, + { material: 'body_wall_muscle', + attribRange: [3093587, 8209], + indexRange: [3159259, 16375], + bboxes: 3208966, + names: ['mu_bod_dl24', 'mu_bod_vr24'], + lengths: [19413, 29712] + } + ], + '5ec61bf2.Virtual_Worm_February_2012.utf8': [ + { material: 'coelomocyte', + attribRange: [0, 5772], + indexRange: [46176, 11520], + bboxes: 80736, + names: ['ccdr', 'ccdl', 'ccpl', 'ccal', 'ccar', 'ccpr'], + lengths: [5760, 5760, 5760, 5760, 5760, 5760] + } + ], + 'ef56b91b.Virtual_Worm_February_2012.utf8': [ + { material: 'dtc_&_somatic_gonad', + attribRange: [0, 32250], + indexRange: [258000, 63680], + bboxes: 449040, + names: ['gonadal_sheath_a5d', 'gonadal_sheath_a4d', 'gonadal_sheath_p5d', 'gonadal_sheath_p4d', 'gonadal_sheath_p3l', 'gonadal_sheath_p2l', 'gonadal_sheath_p1l', 'gonadal_sheath_a3l', 'gonadal_sheath_a2l', 'gonadal_sheath_a1l', 'gonadal_sheath_p4v', 'gonadal_sheath_p3r', 'gonadal_sheath_p2r', 'gonadal_sheath_p1r', 'distal_tip_cell_p', 'gonadal_sheath_a4v', 'gonadal_sheath_a3r', 'gonadal_sheath_a2r', 'gonadal_sheath_a1r', 'distal_tip_cell_a', 'gonadal_sheath_p5v', 'gonadal_sheath_a5v'], + lengths: [9120, 6048, 8400, 6048, 6624, 8736, 19488, 5280, 6624, 18768, 4512, 6624, 8736, 19488, 3264, 4512, 5280, 6624, 18768, 3840, 7440, 6816] + } + ], + '55bbb055.Virtual_Worm_February_2012.utf8': [ + { material: 'even', + attribRange: [0, 18337], + indexRange: [146696, 36656], + bboxes: 256664, + names: ['pm2vr', 'pm2vl', 'pm2d', 'pm8', 'pm6vl', 'pm6d', 'pm6vr', 'pm4vl', 'pm4d', 'pm4vr'], + lengths: [14400, 13824, 13632, 2688, 16608, 3384, 15072, 10128, 10056, 10176] + } + ], + '68eacfe3.Virtual_Worm_February_2012.utf8': [ + { material: 'excretory_cell', + attribRange: [0, 4642], + indexRange: [37136, 9280], + bboxes: 64976, + names: ['excretory_cell_excretory_cell'], + lengths: [27840] + } + ], + 'b7440319.Virtual_Worm_February_2012.utf8': [ + { material: 'excretory_duct_cell', + attribRange: [0, 546], + indexRange: [4368, 1088], + bboxes: 7632, + names: ['excretory_duct_cell_excretory_duct_cell'], + lengths: [3264] + } + ], + '88879a20.Virtual_Worm_February_2012.utf8': [ + { material: 'excretory_gland_cells', + attribRange: [0, 1664], + indexRange: [13312, 3328], + bboxes: 23296, + names: ['excretory_gland_cell'], + lengths: [9984] + } + ], + 'e48d7856.Virtual_Worm_February_2012.utf8': [ + { material: 'excretory_pore_cell', + attribRange: [0, 482], + indexRange: [3856, 960], + bboxes: 6736, + names: ['excretory_pore_cell'], + lengths: [2880] + } + ], + '267cfce8.Virtual_Worm_February_2012.utf8': [ + { material: 'germline', + attribRange: [0, 13460], + indexRange: [107680, 19520], + bboxes: 166240, + names: ['rachis_p', 'rachis_a', 'oocyte_post_8', 'oocyte_post_7', 'oocyte_post_6', 'oocyte_post_5', 'oocyte_post_4', 'oocyte_post_3', 'oocyte_post_2', 'oocyte_post_1', 'oocyte_ant_10', 'oocyte_ant_9', 'oocyte_ant_8', 'oocyte_ant_7', 'oocyte_ant_6', 'oocyte_ant_5', 'oocyte_ant_4', 'oocyte_ant_3', 'oocyte_ant_2', 'oocyte_ant_1'], + lengths: [10560, 9408, 2688, 2112, 2112, 2112, 2112, 2112, 2112, 2112, 2112, 2112, 2112, 2112, 2112, 2112, 2112, 2112, 2112, 2112] + } + ], + 'f8a47c2f.Virtual_Worm_February_2012.utf8': [ + { material: 'glr_cells', + attribRange: [0, 7500], + indexRange: [60000, 14976], + bboxes: 104928, + names: ['glrvr', 'glrdr', 'glrr', 'glrl', 'glrdl', 'glrvl'], + lengths: [6720, 7488, 8256, 8256, 7488, 6720] + } + ], + '03c8448a.Virtual_Worm_February_2012.utf8': [ + { material: 'head_mesodermal_cell', + attribRange: [0, 2432], + indexRange: [19456, 4864], + bboxes: 34048, + names: ['head_mesodermal_cell'], + lengths: [14592] + } + ], + 'cf2096e6.Virtual_Worm_February_2012.utf8': [ + { material: 'hypodermis', + attribRange: [0, 55294], + indexRange: [442352, 109276], + bboxes: 1670280, + names: ['hyp10', 'hyp9', 'hyp8', 'hyp11', 'hyp7'], + lengths: [6720, 6288, 5424, 8304, 301092] + }, + { material: 'hypodermis', + attribRange: [770180, 55294], + indexRange: [1212532, 108432], + bboxes: 1670310, + names: ['hyp7', 'hyp5', 'hyp4', 'hyp2', 'hyp1', 'hyp3', 'cuticle'], + lengths: [43308, 32256, 42240, 68352, 61440, 36864, 40836] + }, + { material: 'hypodermis', + attribRange: [1537828, 9625], + indexRange: [1614828, 18484], + bboxes: 1670352, + names: ['cuticle'], + lengths: [55452] + } + ], + '9ef380e0.Virtual_Worm_February_2012.utf8': [ + { material: 'interneuron', + attribRange: [0, 55294], + indexRange: [442352, 110383], + bboxes: 3215376, + names: ['siavr', 'sdqr', 'sdql', 'bdur', 'pvnr', 'pvwr', 'pvcr', 'luar', 'pvpl', 'ripr', 'urbr', 'aibr', 'adar', 'mi', 'i6', 'i5', 'i4', 'i3', 'i2r', 'i1r', 'pvnl', 'pvcl'], + lengths: [19392, 7872, 13632, 8256, 31680, 6336, 23232, 5472, 19776, 13248, 5184, 12480, 7872, 7488, 9792, 36480, 16320, 5568, 12096, 10560, 37440, 20973] + }, + { material: 'interneuron', + attribRange: [773501, 55294], + indexRange: [1215853, 110422], + bboxes: 3215508, + names: ['pvcl', 'pvql', 'pvt', 'pvpr', 'pvwl', 'lual', 'pvqr', 'bdul', 'i1l', 'i2l', 'rivr', 'avjr', 'ricr', 'sibvr', 'siadr', 'aiar', 'saadl', 'ribr', 'avkr', 'avkl', 'avfl', 'avfr'], + lengths: [1491, 24384, 22848, 19008, 6336, 5472, 24000, 8256, 10560, 12096, 9792, 22080, 9888, 25632, 19872, 5568, 14496, 12480, 24864, 24864, 25152, 2127] + }, + { material: 'interneuron', + attribRange: [1547119, 55295], + indexRange: [1989479, 107288], + bboxes: 3215640, + names: ['avfr', 'avar', 'avbr', 'avdl', 'avbl', 'aval', 'avdr', 'saadr', 'rir', 'sibdr', 'aver', 'riar', 'saavr', 'ainr', 'avhr', 'urxr', 'ris', 'ripl', 'urbl', 'adal', 'rid'], + lengths: [23409, 22176, 25728, 22176, 25728, 22176, 22176, 14496, 10176, 20256, 13248, 10176, 14400, 6816, 22080, 8256, 8736, 13248, 5184, 7872, 3351] + }, + { material: 'interneuron', + attribRange: [2311343, 55294], + indexRange: [2753695, 100535], + bboxes: 3215766, + names: ['rid', 'urxl', 'rivl', 'avhl', 'avjl', 'ainl', 'saavl', 'rial', 'avel', 'sibdl', 'ribl', 'aizl', 'ricl', 'aiyr', 'aimr', 'aibl', 'rigl', 'sabd', 'avg', 'rifl', 'rigr', 'rifr', 'sabvr', 'sabvl', 'aiml', 'aiyl', 'siadl'], + lengths: [24105, 8256, 9792, 22080, 22464, 6816, 14400, 10176, 13248, 20256, 12480, 8640, 9888, 5568, 5952, 12480, 5568, 15936, 19776, 5952, 5568, 5952, 5184, 5184, 5952, 5568, 14364] + }, + { material: 'interneuron', + attribRange: [3055300, 11485], + indexRange: [3147180, 22732], + bboxes: 3215928, + names: ['siadl', 'aial', 'siavl', 'sibvl', 'rih'], + lengths: [5508, 5568, 19392, 25632, 12096] + } + ], + '89e6fddc.Virtual_Worm_February_2012.utf8': [ + { material: 'intestine', + attribRange: [0, 22808], + indexRange: [182464, 45536], + bboxes: 319072, + names: ['int1dl', 'int9l', 'int8l', 'int7l', 'int6l', 'int5l', 'int4v', 'int3v', 'int2d', 'int1vr', 'int1vl', 'int4d', 'int6r', 'int5r', 'int9r', 'int8r', 'int2v', 'int1dr', 'int7r', 'int3d'], + lengths: [6528, 9024, 5568, 5568, 7872, 6720, 7872, 6192, 6720, 6576, 6528, 7872, 7872, 6720, 9024, 5568, 5568, 6528, 5568, 6720] + } + ], + '82f5d638.Virtual_Worm_February_2012.utf8': [ + { material: 'marginal_cells', + attribRange: [0, 30820], + indexRange: [246560, 61604], + bboxes: 431372, + names: ['mc3dr', 'mc2v', 'mc2dr', 'mc1v', 'mc1dr', 'mc3v', 'mc3dl', 'mc2dl', 'mc1dl'], + lengths: [20976, 19104, 19008, 27072, 27936, 18528, 5244, 19008, 27936] + } + ], + '1266f863.Virtual_Worm_February_2012.utf8': [ + { material: 'motor_neuron', + attribRange: [0, 55294], + indexRange: [442352, 110400], + bboxes: 3231456, + names: ['va11', 'hsnr', 'dd6', 'dd4', 'dd3', 'dd5', 'dd2', 'db7', 'db6', 'da9', 'da7', 'da6', 'da5', 'as11', 'as10', 'as9', 'as8', 'as7', 'as6', 'as5', 'as4', 'as3', 'as2', 'vd13', 'vd12', 'vd11', 'vd10', 'vd9'], + lengths: [4416, 17856, 15168, 19008, 15168, 14016, 12096, 12480, 14016, 12096, 17472, 22080, 17664, 9408, 8928, 9408, 10848, 10176, 9024, 9792, 9408, 8256, 8640, 10560, 10944, 10944, 10944, 384] + }, + { material: 'motor_neuron', + attribRange: [773552, 55294], + indexRange: [1215904, 110411], + bboxes: 3231624, + names: ['vd9', 'vd8', 'vd7', 'vd6', 'vd5', 'vd4', 'vd3', 'db5', 'db4', 'da8', 'da4', 'da3', 'da2', 'vc5', 'uravr', 'm5', 'm3r', 'm2r', 'm1', 'dvb', 'va12', 'pda', 'pdb', 'vb11', 'va10', 'vb10', 'va9', 'vc6', 'vb9', 'va8', 'vb8'], + lengths: [10176, 13632, 12480, 10944, 10944, 10944, 9792, 20160, 23232, 12096, 14400, 11712, 11328, 11328, 7872, 18624, 11328, 6336, 9408, 8640, 3264, 10560, 13248, 6336, 5952, 7488, 9408, 5184, 7104, 10176, 7137] + }, + { material: 'motor_neuron', + attribRange: [1547137, 55294], + indexRange: [1989489, 110396], + bboxes: 3231810, + names: ['vb8', 'va7', 'vb7', 'va6', 'vc3', 'vb6', 'va5', 'vc2', 'vb5', 'va4', 'vc1', 'vb4', 'va3', 'db3', 'vb3', 'va2', 'hsnl', 'vc4', 'm4', 'm2l', 'm3l', 'smbvr', 'rmfr', 'rmgr', 'uradr', 'rmer', 'rmhr', 'smbdr', 'smddr'], + lengths: [2655, 10560, 9792, 10176, 23304, 10944, 6720, 10560, 11328, 7104, 11328, 11328, 6720, 19776, 10560, 5568, 17856, 11328, 20160, 6336, 11328, 22080, 10560, 7488, 8640, 7008, 10560, 17856, 11565] + }, + { material: 'motor_neuron', + attribRange: [2320677, 55294], + indexRange: [2763029, 110271], + bboxes: 3231984, + names: ['smddr', 'rmddr', 'rmdr', 'rmdvr', 'smdvr', 'rimr', 'rmed', 'rmel', 'rmev', 'smdvl', 'rmdvl', 'rmdl', 'riml', 'vd2', 'da1', 'vd1', 'as1', 'db1', 'dd1', 'va1', 'db2', 'vb1', 'vb2', 'rmfl', 'smbdl', 'smddl', 'rmhl'], + lengths: [6675, 5952, 6720, 6720, 19776, 14016, 11904, 7008, 25152, 19776, 6720, 6720, 14016, 9408, 10176, 12480, 8256, 24384, 11328, 7488, 20928, 12864, 11712, 10560, 17856, 18240, 3978] + }, + { material: 'motor_neuron', + attribRange: [3093842, 9875], + indexRange: [3172842, 19538], + bboxes: 3232146, + names: ['rmhl', 'rmddl', 'rmgl', 'uravl', 'uradl', 'smbvl'], + lengths: [6582, 5952, 7488, 7872, 8640, 22080] + } + ], + 'd53efa90.Virtual_Worm_February_2012.utf8': [ + { material: 'neurunkfunc', + attribRange: [0, 33050], + indexRange: [264400, 66048], + bboxes: 462544, + names: ['alnr', 'plnr', 'canl', 'uryvr', 'auar', 'plnl', 'alnl', 'canr', 'urydr', 'ala', 'aual', 'urydl', 'uryvl'], + lengths: [17472, 20544, 13632, 7872, 13248, 19776, 17472, 13632, 11712, 29952, 13248, 11712, 7872] + } + ], + '3fc12c12.Virtual_Worm_February_2012.utf8': [ + { material: 'odd', + attribRange: [0, 28764], + indexRange: [230112, 57504], + bboxes: 402624, + names: ['pm1', 'pm7vl', 'pm7d', 'pm5vl', 'pm5d', 'pm5vr', 'pm3vl', 'pm3d', 'pm3vr', 'pm7vr'], + lengths: [30144, 13872, 15552, 15024, 16128, 13104, 19728, 15696, 18960, 14304] + } + ], + 'fd752c87.Virtual_Worm_February_2012.utf8': [ + { material: 'phary_&_rect_glands', + attribRange: [0, 12112], + indexRange: [96896, 24192], + bboxes: 169472, + names: ['phar_gland_g1_r_phar_gland_vd_g1_.000', 'rect_vr', 'rect_d', 'rect_vl', 'phar_gland_g2_vr_phar_gland_vg2r', 'phar_gland_dorsal_g2_phar_gland_vd', 'phar_gland_g1_l_phar_gland_vg1l', 'phar_gland_g2_vl_phar_gland_vg2l'], + lengths: [6720, 2880, 5184, 2880, 5952, 12480, 30528, 5952] + } + ], + '4940a7a2.Virtual_Worm_February_2012.utf8': [ + { material: 'pharyngeal_epithelium', + attribRange: [0, 15434], + indexRange: [123472, 30800], + bboxes: 215872, + names: ['anus', 'e1vr', 'e3vr', 'e2dr', 'e2dl', 'e3d', 'e1d', 'e2v', 'e3vl', 'e1vl'], + lengths: [1920, 7872, 8208, 13104, 12336, 9120, 10176, 13584, 8208, 7872] + } + ], + 'dd73fee7.Virtual_Worm_February_2012.utf8': [ + { material: 'polymodalneuron', + attribRange: [0, 24178], + indexRange: [193424, 48296], + bboxes: 338312, + names: ['olqvr', 'il1vr', 'il1r', 'nsmr', 'mcr', 'mcl', 'olqdr', 'nsml', 'il1dr', 'avl', 'il1dl', 'il1vl', 'il1l', 'olqdl', 'olqvl'], + lengths: [7872, 8256, 10944, 9408, 11712, 11712, 9408, 9408, 2268, 25152, 2268, 8256, 10944, 9408, 7872] + } + ], + 'fd4525b1.Virtual_Worm_February_2012.utf8': [ + { material: 'rectal_epithelium', + attribRange: [0, 4428], + indexRange: [35424, 8832], + bboxes: 61920, + names: ['y', 'b', 'u', 'f', 'k', 'kprime'], + lengths: [4416, 4416, 4416, 4416, 4416, 4416] + } + ], + 'def30c6c.Virtual_Worm_February_2012.utf8': [ + { material: 'seam_cell', + attribRange: [0, 11076], + indexRange: [88608, 22144], + bboxes: 155040, + names: ['seam_cells_left', 'seam_cells_right'], + lengths: [33216, 33216] + } + ], + 'b8859a42.Virtual_Worm_February_2012.utf8': [ + { material: 'sensoryneuron', + attribRange: [0, 55294], + indexRange: [442352, 109643], + bboxes: 4930384, + names: ['pvm', 'pvdr'], + lengths: [14016, 314913] + }, + { material: 'sensoryneuron', + attribRange: [771281, 55294], + indexRange: [1213633, 109391], + bboxes: 4930396, + names: ['pvdr'], + lengths: [328173] + }, + { material: 'sensoryneuron', + attribRange: [1541806, 55294], + indexRange: [1984158, 109433], + bboxes: 4930402, + names: ['pvdr', 'pder', 'avm', 'almr', 'pvr', 'plmr', 'phcr', 'phbr', 'phar', 'pvdl'], + lengths: [22914, 20160, 15936, 12096, 28992, 17088, 9408, 7488, 7488, 186729] + }, + { material: 'sensoryneuron', + attribRange: [2312457, 55294], + indexRange: [2754809, 109194], + bboxes: 4930462, + names: ['pvdl'], + lengths: [327582] + }, + { material: 'sensoryneuron', + attribRange: [3082391, 55294], + indexRange: [3524743, 109764], + bboxes: 4930468, + names: ['pvdl', 'il2vr', 'flpr', 'bagr', 'il2r', 'awcr', 'awbr', 'awar', 'askr', 'asir', 'aser', 'ashr', 'adlr', 'adfr', 'ader'], + lengths: [152073, 7872, 18240, 11712, 8256, 14784, 13632, 13632, 13632, 14784, 16704, 13632, 13248, 13248, 3843] + }, + { material: 'sensoryneuron', + attribRange: [3854035, 55294], + indexRange: [4296387, 110199], + bboxes: 4930558, + names: ['ader', 'pqr', 'phcl', 'phbl', 'phal', 'plml', 'pdel', 'alml', 'il2vl', 'cepdr', 'asgr', 'ollr', 'il2dr', 'afdr', 'cepvr', 'asjr', 'aqr', 'il2dl', 'bagl', 'cepvl', 'adel', 'flpl', 'adll', 'ashl', 'awbl', 'afdl', 'adfl', 'asel'], + lengths: [8637, 9024, 9408, 7488, 7488, 17088, 19776, 12096, 7872, 12096, 11712, 14016, 8256, 13248, 10944, 14400, 14016, 8256, 11712, 10944, 12480, 18240, 13248, 13632, 13632, 13248, 13248, 4392] + }, + { material: 'sensoryneuron', + attribRange: [4626984, 21722], + indexRange: [4800760, 43208], + bboxes: 4930726, + names: ['asel', 'awcl', 'asjl', 'cepdl', 'asil', 'askl', 'asgl', 'awal', 'olll', 'il2l'], + lengths: [12312, 14784, 14400, 12096, 14784, 13632, 11712, 13632, 14016, 8256] + } + ], + 'f9a38374.Virtual_Worm_February_2012.utf8': [ + { material: 'sheathother', + attribRange: [0, 21382], + indexRange: [171056, 42688], + bboxes: 299120, + names: ['olqshvr', 'ilshvr', 'ollshr', 'ilshr', 'amshr', 'phshr', 'phshl', 'olqshdr', 'cepshvr', 'cepshdr', 'ilshdr', 'adeshr', 'adeshl', 'ilshvl', 'ilshdl', 'olqshdl', 'ilshl', 'olqshvl', 'ollshl', 'cepshdl', 'cepshvl'], + lengths: [5568, 3648, 5184, 5568, 8640, 2880, 2880, 5184, 9792, 9792, 4032, 9600, 6528, 3648, 4032, 5184, 5568, 5568, 5184, 9792, 9792] + } + ], + 'b8d90ec1.Virtual_Worm_February_2012.utf8': [ + { material: 'socketcell', + attribRange: [0, 17968], + indexRange: [143744, 35840], + bboxes: 251264, + names: ['olqsovr', 'ilsovr', 'xxxr', 'ollsor', 'ilsor', 'amsor', 'phso1r', 'phso2r', 'phso2l', 'phso1l', 'adesol', 'amsol', 'ilsodr', 'adesor', 'cepsovr', 'cepsodr', 'olqsodr', 'ilsovl', 'olqsovl', 'ilsol', 'ilsodl', 'olqsodl', 'xxxl', 'ollsol', 'cepsodl', 'cepsovl'], + lengths: [3264, 2880, 3648, 5184, 3648, 3648, 5184, 4416, 4416, 5184, 5760, 3648, 3264, 5760, 4416, 5184, 3264, 2880, 3264, 3648, 3264, 3264, 3648, 5184, 5184, 4416] + } + ], + 'da54c9e0.Virtual_Worm_February_2012.utf8': [ + { material: 'spermath_uterin_valve', + attribRange: [0, 1152], + indexRange: [9216, 2304], + bboxes: 16128, + names: ['sp_ut_valve_post', 'sp_ut_valve_ant'], + lengths: [3456, 3456] + } + ], + 'e6aa14af.Virtual_Worm_February_2012.utf8': [ + { material: 'spermatheca', + attribRange: [0, 55294], + indexRange: [442352, 110373], + bboxes: 816424, + names: ['sp_bag_p_4r', 'sp_neck_p_1l', 'sp_neck_p_1r', 'sp_neck_p_2l', 'sp_neck_p_3r', 'sp_neck_p_4l', 'sp_neck_p_3l', 'sp_neck_p_2r', 'sp_neck_p_4r', 'sp_bag_p_1v', 'sp_bag_p_2r', 'sp_bag_p_3v', 'sp_bag_p_1d', 'sp_bag_p_1r', 'sp_bag_p_1l', 'sp_bag_p_2d', 'sp_bag_p_2l', 'sp_bag_p_2v', 'sp_bag_p_3d', 'sp_bag_p_3l', 'sp_bag_p_3r', 'sp_bag_p_4d', 'sp_bag_p_4l', 'sp_bag_p_4v', 'sp_bag_a_4v', 'sp_bag_a_4r', 'sp_bag_a_4d', 'sp_bag_a_3l', 'sp_bag_a_3r', 'sp_bag_a_3d', 'sp_bag_a_2v', 'sp_bag_a_2r', 'sp_bag_a_2d', 'sp_bag_a_1r', 'sp_bag_a_1l', 'sp_bag_a_1d', 'sp_bag_a_3v', 'sp_bag_a_2l', 'sp_bag_a_1v', 'sp_neck_a_4l', 'sp_neck_a_2l', 'sp_neck_a_3r', 'sp_neck_a_4r', 'sp_neck_a_3l', 'sp_neck_a_2r'], + lengths: [3648, 6912, 6912, 7104, 7296, 7296, 7296, 7104, 7296, 18432, 3648, 3648, 18432, 18432, 18432, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 3648, 18432, 18432, 18432, 3648, 3648, 18432, 7296, 7104, 7296, 7296, 7296, 6255] + }, + { material: 'spermatheca', + attribRange: [773471, 3079], + indexRange: [798103, 6107], + bboxes: 816694, + names: ['sp_neck_a_2r', 'sp_neck_a_1l', 'sp_neck_a_1r', 'sp_bag_a_4l'], + lengths: [849, 6912, 6912, 3648] + } + ], + '65fda73e.Virtual_Worm_February_2012.utf8': [ + { material: 'sphnc_&_anal_dep_musc', + attribRange: [0, 3410], + indexRange: [27280, 6816], + bboxes: 47728, + names: ['sph_mu', 'mu_anal'], + lengths: [13344, 7104] + } + ], + '53f3b3bd.Virtual_Worm_February_2012.utf8': [ + { material: 'stomatoint_muscle', + attribRange: [0, 6788], + indexRange: [54304, 13568], + bboxes: 95008, + names: ['mu_int_r', 'mu_int_l'], + lengths: [20496, 20208] + } + ], + '00c50ff0.Virtual_Worm_February_2012.utf8': [ + { material: 'temp_drg_color', + attribRange: [0, 8150], + indexRange: [65200, 16320], + bboxes: 114160, + names: ['dvc', 'dva'], + lengths: [24480, 24480] + } + ], + '527bd07e.Virtual_Worm_February_2012.utf8': [ + { material: 'uterine_muscle', + attribRange: [0, 10064], + indexRange: [80512, 20096], + bboxes: 140800, + names: ['um1l_post', 'um1l_ant', 'um1r_post', 'um2l_post', 'um2l_ant', 'um2r_ant', 'um1r_ant', 'um2r_post'], + lengths: [4032, 3648, 3648, 11328, 11328, 11328, 3648, 11328] + } + ], + 'bf699c9b.Virtual_Worm_February_2012.utf8': [ + { material: 'uterus', + attribRange: [0, 20101], + indexRange: [160808, 40160], + bboxes: 281288, + names: ['ut2_ant', 'uv2_post', 'uv1_post', 'uv3_post', 'uv3_ant', 'uv2_ant', 'uv1_ant', 'du', 'ut3_post', 'ut2_post', 'ut1_post', 'ut1_ant', 'ut3_ant', 'utse', 'ut4_post', 'ut4_ant'], + lengths: [6144, 6336, 6336, 2688, 2688, 6336, 6336, 7488, 4608, 6144, 13248, 12288, 6048, 25728, 4608, 3456] + } + ], + 'cd53909a.Virtual_Worm_February_2012.utf8': [ + { material: 'vpi_&_vir', + attribRange: [0, 11886], + indexRange: [95088, 23744], + bboxes: 166320, + names: ['pharyn-intest-valve', 'virr', 'virl', 'vpi3_v', 'vpi3_d', 'vpi2_dl', 'vpi2_v', 'vpi2_dr', 'vpi1'], + lengths: [12288, 2496, 2496, 5664, 5664, 5616, 6816, 5616, 24576] + } + ], + '57c495b3.Virtual_Worm_February_2012.utf8': [ + { material: 'vulva_epithelium', + attribRange: [0, 5120], + indexRange: [40960, 10240], + bboxes: 71680, + names: ['vula', 'vulb1', 'vulb2', 'vulc', 'vuld', 'vule', 'vulf'], + lengths: [3840, 3840, 3840, 3840, 3840, 7680, 3840] + } + ], + '8e763d75.Virtual_Worm_February_2012.utf8': [ + { material: 'vulval_muscle', + attribRange: [0, 2512], + indexRange: [20096, 4992], + bboxes: 35072, + names: ['vm1l_ant', 'vm2r_ant', 'vm2l_post', 'vm1r_ant', 'vm1r_post', 'vm1l_post', 'vm2r_post', 'vm2l_ant'], + lengths: [1728, 1728, 1728, 2112, 2112, 2112, 1728, 1728] + } + ] + } +}; diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/Virtual_Worm_February_2012.mtl b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/Virtual_Worm_February_2012.mtl new file mode 100644 index 0000000..2a67a7b --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/Virtual_Worm_February_2012.mtl @@ -0,0 +1,372 @@ +# Blender3D MTL File: Virtual_Worm_February_2012.blend +# Material Count: 37 +newmtl Vulval_Muscle +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.640000 0.320000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Sphnc_&_Anal_Dep_Musc +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.320000 0.480000 0.000000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Uterine_Muscle +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.640000 0.000000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Uterus +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.640000 0.640000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Seam_Cell +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.320000 0.160000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Intestine +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.800000 0.640000 0.800000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Interneuron +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.800000 0.160000 0.000000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl SocketCell +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.800000 0.480000 0.480000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Temp_DRG_Color +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.774902 0.163137 0.000000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl DTC_&_Somatic_Gonad +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.320000 0.800000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Hypodermis +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.690196 0.611765 0.539608 +Ks 0.000050 0.000050 0.000050 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl PolymodalNeuron +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.768906 0.007268 0.007268 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Motor_Neuron +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.320000 0.746667 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl NeurUnkFunc +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.000000 0.160000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Stomatoint_Muscle +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.320000 0.000000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl SensoryNeuron +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.800000 0.320000 0.746667 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl VPI_&_VIR +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.320000 0.160000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Coelomocyte +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.800000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Even#PhMusc +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.800000 0.480000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl GLR_Cells +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.746667 0.480000 0.000000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl marginal_cells +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.746667 0.160000 0.746667 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Excretory_Duct_Cell +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.480000 0.320000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Body_Wall_Muscle +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.160000 0.320000 0.000000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl SheathOther +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.160000 0.480000 0.480000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Excretory_Gland_Cells +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.480000 0.640000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Vulva_epithelium +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.480000 0.800000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Germline +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.160000 0.320000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Pharyngeal_Epithelium +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.320000 0.320000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Head_Mesodermal_Cell +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.800000 0.320000 0.160000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Rectal_epithelium +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.480000 0.320000 0.320000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Odd#PhMuscle +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.160000 0.640000 0.000000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Phary_&_Rect_Glands +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.800000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Excretory_Cell +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.160000 0.320000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Excretory_Pore_Cell +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.740392 0.774902 0.448627 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Arcade_Cells +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.000000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Spermatheca +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.160000 0.480000 0.800000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + +newmtl Spermath_Uterin_Valve +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.320000 0.320000 0.480000 +Ks 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.050000 +illum 2 + + diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/b7440319.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/b7440319.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..d5ddc9a Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/b7440319.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/b8859a42.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/b8859a42.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..1dc9528 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/b8859a42.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/b8d90ec1.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/b8d90ec1.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..ebb5928 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/b8d90ec1.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/bf699c9b.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/bf699c9b.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..ec742f6 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/bf699c9b.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/cd53909a.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/cd53909a.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..d059dd5 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/cd53909a.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/cf2096e6.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/cf2096e6.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..c16c776 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/cf2096e6.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/d53efa90.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/d53efa90.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..5fd77eb Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/d53efa90.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/da54c9e0.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/da54c9e0.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..b5744f4 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/da54c9e0.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/dd73fee7.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/dd73fee7.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..3eba8c8 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/dd73fee7.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/def30c6c.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/def30c6c.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..a5057f6 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/def30c6c.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/df4944b1.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/df4944b1.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..7497b5e Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/df4944b1.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/e48d7856.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/e48d7856.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..27805cc Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/e48d7856.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/e6aa14af.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/e6aa14af.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..4577e6b Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/e6aa14af.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/ef56b91b.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/ef56b91b.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..9293c68 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/ef56b91b.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/entity_metadata.json b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/entity_metadata.json new file mode 100644 index 0000000..955ef59 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/entity_metadata.json @@ -0,0 +1 @@ +{"layers":[10,11,12,13],"dag":[[10,[5373,5374,5377,5378,5370,5369,5375,5372,5382,5379,5368,5381,5371,5380,5376]],[13,[5073,5009,5071,5036,5019,5078,5012,5003,5023,5129,5131,5548,5521,5571,5572,5620,5008,5150,5186,5185,5011,5138,5141,5144,5147,5600,5134,5136,5536,5550,5064,5074,5021,5122,5156,5037,5659,5626,5104,5105,5102,5103,5015,5532,5669,5663,5649,5154,5124,5569,5018,5564,5668,5189,5636,5355,5520,5555,5608,5560,5637,5674,5644,5577,5081,5531,5632,5167,5344,5364,5174,5686,5652,5352,5542,5658,5629,5187,5158,5621,5092,5093,5353,5116,5091,5090,5089,5178,5118,5117,5347,5115,5088,5646,5130,5079,5126,5666,5543,5022,5524,5639,5603,5619,5641,5100,5101,5025,5563,5096,5097,5098,5099,5094,5095,5348,5024,5539,5123,5155,5634,5529,5161,5172,5516,5171,5562,5170,5066,5673,5165,5146,5143,5140,5153,5119,5133,5191,5070,5192,5196,5596,5665,5031,5030,5647,5518,5597,5201,5199,5677,5198,5570,5065,5678,5671,5681,5075,5556,5601,5616,5200,5676,5197,5351,5014,5013,5617,5650,5628,5574,5642,5551,5662,5622,5181,5149,5184,5113,5114,5086,5087,5349,5350,5522,5618,5166,5175,5517,5534,5599,5546,5193,5363,5077,5135,5598,5623,5188,5063,5358,5120,5615,5523,5575,5169,5001,5176,5660,5651,5128,5127,5069,5576,5343,5683,5361,5346,5595,5630,5359,5552,5076,5535,5540,5567,5573,5068,5067,5685,5039,5002,5645,5653,5654,5072,5604,5164,5007,5687,5657,5675,5027,5530,5362,5566,5554,5354,5559,5612,5610,5611,5609,5635,5643,5360,5613,5194,5526,5545,5625,5160,5342,5640,5195,5638,5157,5179,5112,5177,5110,5111,5108,5109,5106,5107,5038,5028,5152,5631,5679,5680,5684,5080,5190,5162,5655,5605,5006,5356,5558,5553,5367,5000,5020,5032,5602,5541,5083,5085,5182,5633,5168,5084,5082,5173,5667,5035,5561,5538,5606,5033,5180,5528,5549,5029,5672,5121,5527,5345,5159,5365,5010,5547,5557,5366,5357,5519,5004,5005,5533,5163,5565,5132,5151,5148,5183,5139,5137,5145,5142,5670,5026,5627,5661,5034,5682,5648,5525,5614,5624,5544,5664,5537,5017,5016,5568,5607,5594,5593,5125,5656]],[12,[5398,5447,5462,5446,5445,5492,5424,5390,5480,5469,5481,5470,5482,5471,5488,5472,5487,5473,5440,5430,5443,5433,5444,5441,5431,5442,5432,5386,5463,5448,5465,5450,5464,5449,5461,5452,5466,5451,5385,5420,5410,5419,5409,5422,5421,5411,5418,5408,5393,5397,5435,5425,5467,5478,5468,5479,5438,5428,5439,5429,5436,5426,5437,5427,5400,5399,5402,5412,5401,5490,5384,5417,5407,5416,5406,5415,5405,5414,5404,5413,5403,5383,5388,5485,5475,5484,5476,5483,5477,5493,5494,5486,5474,5423,5489,5387,5434,5495,5395,5392,5389,5391,5394,5460,5453,5396,5491,5457,5456,5458,5455,5459,5454]],[1,[12,11,13,10]],[11,[5250,5271,5254,5503,5272,5041,5580,5273,5253,5515,5278,5226,5581,5324,5498,5268,5214,5208,5341,5235,5044,5333,5047,5207,5217,5511,5290,5277,5304,5234,5269,5328,5258,5312,5323,5340,5237,5334,5297,5248,5310,5331,5330,5040,5232,5303,5497,5213,5298,5274,5505,5327,5309,5236,5209,5307,5506,5325,5296,5338,5510,5302,5056,5509,5270,5501,5295,5294,5293,5292,5255,5289,5052,5051,5050,5049,5275,5326,5046,5314,5285,5276,5586,5281,5238,5222,5279,5215,5319,5300,5322,5042,5512,5045,5306,5251,5220,5225,5504,5313,5229,5308,5316,5591,5231,5206,5059,5058,5057,5257,5062,5061,5060,5216,5592,5055,5054,5053,5233,5280,5203,5228,5243,5329,5242,5221,5283,5332,5584,5263,5502,5227,5291,5244,5264,5239,5241,5582,5507,5588,5590,5311,5282,5318,5284,5043,5202,5266,5246,5265,5583,5262,5252,5261,5578,5260,5579,5240,5267,5259,5286,5288,5212,5339,5245,5287,5500,5210,5335,5336,5508,5219,5247,5218,5205,5249,5513,5223,5315,5320,5230,5496,5256,5048,5204,5299,5301,5585,5224,5499,5317,5305,5321,5211,5337,5589,5514,5587]]],"symmetries":[],"names":[],"leafs":[[5073,"il1dl"],[5009,"ala"],[5071,"il1dr"],[5036,"olqshvl"],[5014,"ccdl"],[5636,"avfl"],[5341,"e1vl"],[5235,"ut1_post"],[5044,"rachis_a"],[5333,"e1vr"],[5019,"olqshvr"],[5043,"rachis_p"],[5234,"ut2_post"],[5078,"excretory_cell_excretory_cell"],[5323,"pm7vl"],[5012,"uryvl"],[5297,"rect_d"],[5003,"uryvr"],[5331,"pm7vr"],[5140,"vc3"],[5386,"um2l_post"],[5396,"vm1l_post"],[5023,"amshr"],[5213,"gonadal_sheath_p2r"],[5129,"vb11"],[5131,"vb10"],[5548,"alml"],[5590,"f"],[5236,"ut1_ant"],[5521,"almr"],[5555,"cepvr"],[5325,"pm5vl"],[5000,"alnr"],[5206,"gonadal_sheath_p2l"],[5243,"sp_neck_p_1r"],[5013,"ccdr"],[5571,"cepdl"],[5572,"asil"],[5620,"pvwl"],[5285,"sp_neck_a_2r"],[5008,"urydr"],[5150,"vb3"],[5186,"vb2"],[5185,"vb1"],[5011,"urydl"],[5138,"vb7"],[5141,"vb6"],[5144,"vb5"],[5147,"vb4"],[5600,"pvwr"],[5134,"vb9"],[5136,"vb8"],[5536,"asir"],[5550,"cepdr"],[5319,"mc3dl"],[5322,"pm1"],[5042,"excretory_pore_cell"],[5064,"il1vr"],[5512,"int2v"],[5306,"pm8"],[5074,"il1vl"],[5504,"int2d"],[5313,"mc3dr"],[5021,"ollshr"],[5316,"mc1v"],[5591,"k"],[5231,"uv1_ant"],[5122,"m3r"],[5059,"oocyte_ant_4"],[5058,"oocyte_ant_5"],[5057,"oocyte_ant_6"],[5156,"m3l"],[5062,"oocyte_ant_1"],[5061,"oocyte_ant_2"],[5462,"mu_bod_dr21"],[5216,"gonadal_sheath_a4v"],[5592,"kprime"],[5037,"ollshl"],[5446,"mu_bod_dr22"],[5400,"mu_int_l"],[5492,"mu_bod_dr24"],[5369,"seam_cells_right"],[5626,"rivr"],[5399,"mu_int_r"],[5104,"vd11"],[5105,"vd10"],[5102,"vd13"],[5103,"vd12"],[5015,"ccpl"],[5532,"awcr"],[5642,"aval"],[5663,"saavl"],[5121,"m5"],[5154,"m4"],[5124,"m1"],[5569,"awcl"],[5018,"ccpr"],[5252,"sp_bag_p_3v"],[5564,"ashl"],[5668,"aizl"],[5189,"smddl"],[5481,"mu_bod_dl17"],[5384,"um1l_ant"],[5654,"ripl"],[5417,"mu_bod_vl11"],[5407,"mu_bod_vl10"],[5416,"mu_bod_vl13"],[5406,"mu_bod_vl12"],[5415,"mu_bod_vl15"],[5405,"mu_bod_vl14"],[5414,"mu_bod_vl17"],[5404,"mu_bod_vl16"],[5413,"mu_bod_vl19"],[5403,"mu_bod_vl18"],[5608,"mi"],[5560,"cepvl"],[5473,"mu_bod_dl10"],[5637,"avfr"],[5674,"sabd"],[5644,"saadr"],[5383,"um1l_post"],[5577,"il2l"],[5261,"sp_bag_p_3r"],[5168,"smdvr"],[5260,"sp_bag_p_3l"],[5531,"il2r"],[5632,"saadl"],[5259,"sp_bag_p_3d"],[5082,"dd4"],[5339,"e2v"],[5500,"int6l"],[5210,"gonadal_sheath_a1l"],[5344,"xxxr"],[5423,"mu_bod_vr22"],[5489,"mu_bod_vr23"],[5174,"rmdvl"],[5434,"mu_bod_vr21"],[5495,"mu_bod_vr24"],[5467,"mu_bod_dl22"],[5119,"vc5"],[5485,"mu_bod_dl7"],[5389,"um1r_ant"],[5513,"int1dr"],[5686,"sibvl"],[5652,"urxr"],[5191,"rmddl"],[5542,"pqr"],[5476,"mu_bod_dl4"],[5658,"urxl"],[5496,"int1dl"],[5629,"sibvr"],[5032,"ilshvl"],[5070,"nsml"],[5095,"as8"],[5585,"vpi1"],[5187,"rmfl"],[5460,"mu_bod_dr9"],[5453,"mu_bod_dr8"],[5237,"ut3_ant"],[5538,"ashr"],[5092,"as11"],[5093,"as10"],[5491,"mu_bod_dr1"],[5457,"mu_bod_dr3"],[5456,"mu_bod_dr2"],[5458,"mu_bod_dr5"],[5455,"mu_bod_dr4"],[5459,"mu_bod_dr7"],[5454,"mu_bod_dr6"],[5353,"amsol"],[5167,"rmdvr"],[5116,"da4"],[5091,"da5"],[5090,"da6"],[5089,"da7"],[5178,"da1"],[5118,"da2"],[5117,"da3"],[5371,"arcade_cell_post"],[5347,"amsor"],[5115,"da8"],[5088,"da9"],[5324,"pm7d"],[5364,"xxxl"],[5184,"db2"],[5214,"gonadal_sheath_p1r"],[5113,"db5"],[5508,"int6r"],[5079,"va11"],[5219,"gonadal_sheath_a1r"],[5086,"db7"],[5207,"gonadal_sheath_p1l"],[5666,"sibdl"],[5277,"sp_bag_a_3v"],[5304,"pm2vl"],[5269,"sp_bag_a_3r"],[5543,"phcl"],[5312,"pm4vr"],[5022,"ilshr"],[5270,"sp_bag_a_3d"],[5310,"pm4vl"],[5524,"phcr"],[5040,"excretory_duct_cell_excretory_duct_cell"],[5268,"sp_bag_a_3l"],[5303,"pm2vr"],[5639,"avbr"],[5248,"sp_neck_p_2r"],[5505,"int1vr"],[5385,"um1r_post"],[5327,"pm5vr"],[5603,"pvpl"],[5392,"vm2r_ant"],[5619,"pvpr"],[5559,"bagl"],[5506,"int1vl"],[5244,"sp_neck_p_2l"],[5641,"avbl"],[5100,"as3"],[5101,"as2"],[5025,"phshl"],[5563,"adll"],[5096,"as7"],[5097,"as6"],[5098,"as5"],[5099,"as4"],[5094,"as9"],[5509,"int5r"],[5348,"phso1r"],[5295,"vulf"],[5294,"vule"],[5293,"vuld"],[5024,"phshr"],[5539,"adlr"],[5289,"vula"],[5052,"oocyte_post_1"],[5051,"oocyte_post_2"],[5050,"oocyte_post_3"],[5049,"oocyte_post_4"],[5048,"oocyte_post_5"],[5047,"oocyte_post_6"],[5046,"oocyte_post_7"],[5045,"oocyte_post_8"],[5123,"m2r"],[5155,"m2l"],[5223,"sp_ut_valve_post"],[5222,"gonadal_sheath_a5v"],[5612,"i3"],[5215,"distal_tip_cell_p"],[5280,"sp_neck_a_4l"],[5300,"phar_gland_dorsal_g2_phar_gland_vd"],[5529,"flpr"],[5109,"vd6"],[5161,"rmer"],[5172,"rmev"],[5516,"head_mesodermal_cell"],[5220,"distal_tip_cell_a"],[5171,"rmel"],[5562,"flpl"],[5203,"gonadal_sheath_p5d"],[5170,"rmed"],[5066,"nsmr"],[5673,"rigl"],[5165,"rmddr"],[5146,"vc1"],[5143,"vc2"],[5425,"mu_bod_vr18"],[5153,"vc4"],[5478,"mu_bod_dl23"],[5468,"mu_bod_dl20"],[5479,"mu_bod_dl21"],[5438,"mu_bod_vr13"],[5428,"mu_bod_vr12"],[5439,"mu_bod_vr11"],[5429,"mu_bod_vr10"],[5436,"mu_bod_vr17"],[5426,"mu_bod_vr16"],[5437,"mu_bod_vr15"],[5427,"mu_bod_vr14"],[5638,"avar"],[5382,"cuticle"],[5053,"oocyte_ant_10"],[5196,"glrvr"],[5596,"sdqr"],[5229,"uv3_ant"],[5665,"avel"],[5431,"mu_bod_vr6"],[5031,"adeshl"],[5370,"arcade_cell_ant"],[5329,"pm3d"],[5242,"sp_neck_p_1l"],[5221,"gonadal_sheath_p5v"],[5030,"adeshr"],[5677,"rigr"],[5494,"mu_bod_dl24"],[5597,"sdql"],[5201,"glrvl"],[5332,"anus"],[5199,"glrl"],[5263,"sp_bag_p_4l"],[5352,"adesol"],[5227,"uv1_post"],[5647,"aver"],[5264,"sp_bag_p_4v"],[5239,"ut4_post"],[5241,"sp_bag_p_4r"],[5198,"glrr"],[5588,"b"],[5570,"asjl"],[5065,"il1r"],[5282,"sp_neck_a_3r"],[5671,"aimr"],[5681,"aiml"],[5075,"il1l"],[5283,"sp_neck_a_4r"],[5360,"olqsovl"],[5556,"asjr"],[5435,"mu_bod_vr19"],[5601,"pvcr"],[5262,"sp_bag_p_4d"],[5411,"mu_bod_vl2"],[5584,"vpi2_dr"],[5616,"pvcl"],[5418,"mu_bod_vl9"],[5192,"rmgl"],[5387,"um2l_ant"],[5158,"rmfr"],[5200,"glrdl"],[5643,"avdr"],[5230,"uv2_ant"],[5315,"mc2dr"],[5320,"mc2dl"],[5640,"avdl"],[5197,"glrdr"],[5311,"pm4d"],[5351,"phso1l"],[5394,"vm1r_ant"],[5224,"sp_ut_valve_ant"],[5604,"ripr"],[5271,"sp_bag_a_2v"],[5272,"sp_bag_a_2r"],[5041,"excretory_gland_cell"],[5273,"sp_bag_a_2d"],[5278,"sp_bag_a_2l"],[5226,"uv2_post"],[5617,"pvql"],[5373,"hyp9"],[5374,"hyp8"],[5650,"ainr"],[5377,"hyp5"],[5378,"hyp4"],[5376,"hyp7"],[5445,"mu_bod_dr23"],[5380,"hyp1"],[5574,"asgl"],[5381,"hyp3"],[5424,"mu_bod_vr20"],[5669,"ricl"],[5551,"asgr"],[5480,"mu_bod_dl19"],[5469,"mu_bod_dl18"],[5662,"ainl"],[5470,"mu_bod_dl16"],[5482,"mu_bod_dl15"],[5471,"mu_bod_dl14"],[5488,"mu_bod_dl13"],[5472,"mu_bod_dl12"],[5487,"mu_bod_dl11"],[5622,"pvqr"],[5328,"pm3vl"],[5340,"e3vl"],[5334,"e3vr"],[5181,"db1"],[5330,"pm3vr"],[5149,"db3"],[5232,"du"],[5130,"va10"],[5114,"db4"],[5126,"va12"],[5087,"db6"],[5497,"int9l"],[5298,"rect_vl"],[5225,"ut2_ant"],[5209,"gonadal_sheath_a2l"],[5349,"phso2r"],[5350,"phso2l"],[5367,"cepsovl"],[5218,"gonadal_sheath_a2r"],[5296,"rect_vr"],[5338,"e1d"],[5510,"int9r"],[5522,"pvr"],[5618,"pvt"],[5166,"rmdr"],[5420,"mu_bod_vl5"],[5410,"mu_bod_vl4"],[5419,"mu_bod_vl7"],[5409,"mu_bod_vl6"],[5422,"mu_bod_vl1"],[5421,"mu_bod_vl3"],[5175,"rmdl"],[5517,"pvm"],[5408,"mu_bod_vl8"],[5534,"awar"],[5599,"pvnr"],[5546,"plml"],[5193,"uravl"],[5363,"olqsodl"],[5077,"olqvl"],[5135,"va8"],[5598,"bdur"],[5623,"bdul"],[5132,"va9"],[5063,"olqvr"],[5358,"olqsodr"],[5120,"uravr"],[5615,"pvnl"],[5523,"plmr"],[5575,"awal"],[5169,"rimr"],[5646,"sibdr"],[5112,"vd3"],[5151,"va2"],[5159,"rmgr"],[5176,"riml"],[5308,"pm6d"],[5246,"sp_neck_p_4l"],[5660,"avhl"],[5651,"avhr"],[5249,"sp_neck_p_4r"],[5142,"va5"],[5128,"pdb"],[5127,"pda"],[5069,"olqdr"],[5576,"olll"],[5343,"ilsovr"],[5683,"siadl"],[5361,"ilsol"],[5346,"ilsor"],[5595,"siavr"],[5630,"siadr"],[5359,"ilsovl"],[5552,"ollr"],[5076,"olqdl"],[5026,"olqshdr"],[5535,"askr"],[5540,"adfr"],[5567,"adfl"],[5573,"askl"],[5068,"mcl"],[5502,"int4v"],[5628,"ricr"],[5393,"vm2l_post"],[5284,"sp_neck_a_3l"],[5067,"mcr"],[5507,"int4d"],[5685,"siavl"],[5318,"mc3v"],[5202,"gonadal_sheath_a4d"],[5039,"cepshvl"],[5624,"i1l"],[5002,"canl"],[5645,"rir"],[5653,"ris"],[5578,"virr"],[5388,"um2r_ant"],[5579,"virl"],[5164,"smddr"],[5007,"canr"],[5687,"rih"],[5657,"rid"],[5675,"avg"],[5027,"cepshvr"],[5530,"bagr"],[5475,"mu_bod_dl6"],[5484,"mu_bod_dl5"],[5362,"ilsodl"],[5483,"mu_bod_dl3"],[5477,"mu_bod_dl2"],[5493,"mu_bod_dl1"],[5397,"vm2r_post"],[5566,"afdl"],[5486,"mu_bod_dl9"],[5474,"mu_bod_dl8"],[5056,"oocyte_ant_7"],[5554,"afdr"],[5354,"ilsodr"],[5205,"gonadal_sheath_p3l"],[5441,"mu_bod_vr7"],[5634,"avkr"],[5060,"oocyte_ant_3"],[5610,"i5"],[5611,"i4"],[5609,"i6"],[5635,"avkl"],[5447,"mu_bod_dr20"],[5432,"mu_bod_vr4"],[5204,"gonadal_sheath_p4d"],[5301,"phar_gland_g1_l_phar_gland_vg1l"],[5678,"rifr"],[5055,"oocyte_ant_8"],[5613,"i2r"],[5194,"uradl"],[5526,"phar"],[5317,"mc1dr"],[5054,"oocyte_ant_9"],[5321,"mc1dl"],[5211,"gonadal_sheath_p4v"],[5133,"vc6"],[5545,"phal"],[5625,"i2l"],[5160,"uradr"],[5342,"olqsovr"],[5587,"y"],[5676,"rifl"],[5250,"sp_bag_p_1v"],[5254,"sp_bag_p_1r"],[5503,"int3v"],[5195,"smbvl"],[5580,"vpi3_v"],[5253,"sp_bag_p_1d"],[5157,"smbvr"],[5515,"int3d"],[5255,"sp_bag_p_1l"],[5614,"i1r"],[5581,"vpi3_d"],[5398,"vm2l_ant"],[5179,"vd1"],[5498,"int8l"],[5177,"vd2"],[5110,"vd5"],[5111,"vd4"],[5108,"vd7"],[5208,"gonadal_sheath_a3l"],[5106,"vd9"],[5107,"vd8"],[5038,"cepshdl"],[5390,"um2r_post"],[5028,"cepshdr"],[5659,"rivl"],[5217,"gonadal_sheath_a3r"],[5511,"int8r"],[5440,"mu_bod_vr9"],[5430,"mu_bod_vr8"],[5152,"hsnl"],[5443,"mu_bod_vr3"],[5433,"mu_bod_vr2"],[5444,"mu_bod_vr1"],[5631,"aiar"],[5375,"hyp11"],[5372,"hyp10"],[5442,"mu_bod_vr5"],[5679,"sabvr"],[5680,"sabvl"],[5684,"aial"],[5080,"hsnr"],[5395,"vm1r_post"],[5190,"rmhl"],[5463,"mu_bod_dr19"],[5448,"mu_bod_dr18"],[5519,"pder"],[5465,"mu_bod_dr15"],[5450,"mu_bod_dr14"],[5464,"mu_bod_dr17"],[5449,"mu_bod_dr16"],[5461,"mu_bod_dr11"],[5452,"mu_bod_dr10"],[5466,"mu_bod_dr13"],[5451,"mu_bod_dr12"],[5309,"pm6vr"],[5307,"pm6vl"],[5162,"rmhr"],[5302,"phar_gland_g2_vl_phar_gland_vg2l"],[5655,"urbl"],[5233,"ut3_post"],[5605,"urbr"],[5006,"alnl"],[5356,"cepsovr"],[5326,"pm5d"],[5558,"il2dl"],[5276,"sp_bag_a_1d"],[5553,"il2dr"],[5238,"utse"],[5274,"sp_bag_a_1r"],[5279,"sp_bag_a_1v"],[5020,"ilshvr"],[5072,"avl"],[5602,"luar"],[5355,"adesor"],[5083,"dd3"],[5085,"dd2"],[5182,"dd1"],[5633,"ribr"],[5081,"dd6"],[5084,"dd5"],[5286,"sp_neck_a_1l"],[5520,"avm"],[5287,"sp_neck_a_1r"],[5173,"smdvl"],[5667,"ribl"],[5035,"ilshl"],[5561,"adel"],[5621,"lual"],[5606,"aibr"],[5033,"ilshdl"],[5180,"as1"],[5528,"il2vr"],[5549,"il2vl"],[5029,"ilshdr"],[5672,"aibl"],[5281,"sp_neck_a_2l"],[5649,"saavr"],[5228,"uv3_post"],[5379,"hyp2"],[5527,"pvdl"],[5345,"ollsor"],[5402,"mu_bod_vl20"],[5412,"mu_bod_vl21"],[5401,"mu_bod_vl22"],[5490,"mu_bod_vl23"],[5541,"ader"],[5212,"gonadal_sheath_p3r"],[5518,"pvdr"],[5365,"ollsol"],[5010,"aual"],[5547,"pdel"],[5557,"aqr"],[5366,"cepsodl"],[5537,"aser"],[5357,"cepsodr"],[5299,"phar_gland_g2_vr_phar_gland_vg2r"],[5582,"vpi2_dl"],[5004,"auar"],[5005,"plnl"],[5533,"awbr"],[5163,"smbdr"],[5565,"awbl"],[5188,"smbdl"],[5001,"plnr"],[5148,"va3"],[5183,"va1"],[5139,"va6"],[5137,"va7"],[5145,"va4"],[5501,"int5l"],[5266,"sp_bag_a_4r"],[5670,"aiyr"],[5265,"sp_bag_a_4v"],[5583,"vpi2_v"],[5627,"avjr"],[5291,"vulb2"],[5368,"seam_cells_left"],[5240,"ut4_ant"],[5267,"sp_bag_a_4d"],[5661,"avjl"],[5034,"olqshdl"],[5682,"aiyl"],[5288,"sp_bag_a_4l"],[5648,"riar"],[5245,"sp_neck_p_3r"],[5525,"phbr"],[5292,"vulc"],[5335,"e2dr"],[5336,"e2dl"],[5544,"phbl"],[5305,"pm2d"],[5656,"adal"],[5247,"sp_neck_p_3l"],[5664,"rial"],[5586,"mu_anal"],[5258,"sp_bag_p_2v"],[5017,"ccar"],[5251,"sp_bag_p_2r"],[5257,"sp_bag_p_2l"],[5016,"ccal"],[5568,"asel"],[5391,"vm1l_ant"],[5256,"sp_bag_p_2d"],[5275,"sp_bag_a_1l"],[5290,"vulb1"],[5607,"adar"],[5499,"int7l"],[5594,"dva"],[5593,"dvc"],[5125,"dvb"],[5314,"mc2v"],[5337,"e3d"],[5589,"u"],[5514,"int7r"]],"hidden":[1,10,11,12,13],"nodes":[[10,"Cuticle"],[13,"Neurons"],[12,"Muscles"],[1,"worm_body"],[11,"Organs"]],"sublayers":[[10,[[0,[5368,5369,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379,5380,5381,5382]]]],[11,[[0,[5040,5041,5042]],[4,[5043,5044,5045,5046,5047,5048,5049,5050,5051,5052,5053,5054,5055,5056,5057,5058,5059,5060,5061,5062]],[1,[5296,5297,5298,5299,5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315,5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331,5332,5333,5334,5335,5336,5337,5338,5339,5340,5341]],[3,[5496,5497,5498,5499,5500,5501,5502,5503,5504,5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515]],[5,[5578,5579,5580,5581,5582,5583,5584,5585,5586,5587,5588,5589,5590,5591,5592]],[2,[5202,5203,5204,5205,5206,5207,5208,5209,5210,5211,5212,5213,5214,5215,5216,5217,5218,5219,5220,5221,5222,5223,5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,5237,5238,5239,5240,5241,5242,5243,5244,5245,5246,5247,5248,5249,5250,5251,5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267,5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283,5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295]]]],[12,[[0,[5383,5384,5385,5386,5387,5388,5389,5390,5391,5392,5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408,5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424,5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,5439,5440,5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456,5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472,5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488,5489,5490,5491,5492,5493,5494,5495]]]],[13,[[6,[5000,5001,5002,5003,5004,5005,5006,5007,5008,5009,5010,5011,5012]],[5,[5013,5014,5015,5016,5017,5018,5019,5020,5021,5022,5023,5024,5025,5026,5027,5028,5029,5030,5031,5032,5033,5034,5035,5036,5037,5038,5039]],[3,[5063,5064,5065,5066,5067,5068,5069,5070,5071,5072,5073,5074,5075,5076,5077]],[1,[5078,5079,5080,5081,5082,5083,5084,5085,5086,5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102,5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,5113,5114,5115,5116,5117,5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133,5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149,5150,5151,5152,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164,5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,5176,5177,5178,5179,5180,5181,5182,5183,5184,5185,5186,5187,5188,5189,5190,5191,5192,5193,5194,5195,5196,5197,5198,5199,5200,5201]],[4,[5342,5343,5344,5345,5346,5347,5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363,5364,5365,5366,5367]],[0,[5516,5517,5518,5519,5520,5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536,5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552,5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568,5569,5570,5571,5572,5573,5574,5575,5576,5577]],[2,[5593,5594,5595,5596,5597,5598,5599,5600,5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616,5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631,5632,5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648,5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664,5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680,5681,5682,5683,5684,5685,5686,5687]]]]]} \ No newline at end of file diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/f8a47c2f.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/f8a47c2f.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..d2eeada Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/f8a47c2f.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/f9a38374.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/f9a38374.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..abdabb7 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/f9a38374.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/fd0da1ba.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/fd0da1ba.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..98448f4 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/fd0da1ba.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/fd4525b1.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/fd4525b1.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..ace4255 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/fd4525b1.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/fd752c87.Virtual_Worm_February_2012.utf8 b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/fd752c87.Virtual_Worm_February_2012.utf8 new file mode 100644 index 0000000..efaef37 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/fd752c87.Virtual_Worm_February_2012.utf8 differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/groupings.txt b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/groupings.txt new file mode 100644 index 0000000..9f00234 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/groupings.txt @@ -0,0 +1,1434 @@ +worm_body + Cuticle + Organs + Muscles + Neurons + +Cuticle + HypCutLayer + seam_cells_left + seam_cells_right + arcade_cell_ant + arcade_cell_post + hyp10 + hyp9 + hyp8 + hyp11 + hyp7 + hyp5 + hyp4 + hyp2 + hyp1 + hyp3 + cuticle + +Organs + ExcSysLayer + excretory_duct_cell_excretory_duct_cell + excretory_gland_cell + excretory_pore_cell + GLRLayer + rachis_p + rachis_a + oocyte_post_8 + oocyte_post_7 + oocyte_post_6 + oocyte_post_5 + oocyte_post_4 + oocyte_post_3 + oocyte_post_2 + oocyte_post_1 + oocyte_ant_10 + oocyte_ant_9 + oocyte_ant_8 + oocyte_ant_7 + oocyte_ant_6 + oocyte_ant_5 + oocyte_ant_4 + oocyte_ant_3 + oocyte_ant_2 + oocyte_ant_1 + PharynxLayer + rect_vr + rect_d + rect_vl + phar_gland_g2_vr_phar_gland_vg2r + phar_gland_dorsal_g2_phar_gland_vd + phar_gland_g1_l_phar_gland_vg1l + phar_gland_g2_vl_phar_gland_vg2l + pm2vr + pm2vl + pm2d + pm8 + pm6vl + pm6d + pm6vr + pm4vl + pm4d + pm4vr + mc3dr + mc2v + mc2dr + mc1v + mc1dr + mc3v + mc3dl + mc2dl + mc1dl + pm1 + pm7vl + pm7d + pm5vl + pm5d + pm5vr + pm3vl + pm3d + pm3vr + pm7vr + anus + e1vr + e3vr + e2dr + e2dl + e3d + e1d + e2v + e3vl + e1vl + IntestineLayer + int1dl + int9l + int8l + int7l + int6l + int5l + int4v + int3v + int2d + int1vr + int1vl + int4d + int6r + int5r + int9r + int8r + int2v + int1dr + int7r + int3d + RectLayer + virr + virl + vpi3_v + vpi3_d + vpi2_dl + vpi2_v + vpi2_dr + vpi1 + mu_anal + y + b + u + f + k + kprime + RepSysLayer + gonadal_sheath_a4d + gonadal_sheath_p5d + gonadal_sheath_p4d + gonadal_sheath_p3l + gonadal_sheath_p2l + gonadal_sheath_p1l + gonadal_sheath_a3l + gonadal_sheath_a2l + gonadal_sheath_a1l + gonadal_sheath_p4v + gonadal_sheath_p3r + gonadal_sheath_p2r + gonadal_sheath_p1r + distal_tip_cell_p + gonadal_sheath_a4v + gonadal_sheath_a3r + gonadal_sheath_a2r + gonadal_sheath_a1r + distal_tip_cell_a + gonadal_sheath_p5v + gonadal_sheath_a5v + sp_ut_valve_post + sp_ut_valve_ant + ut2_ant + uv2_post + uv1_post + uv3_post + uv3_ant + uv2_ant + uv1_ant + du + ut3_post + ut2_post + ut1_post + ut1_ant + ut3_ant + utse + ut4_post + ut4_ant + sp_bag_p_4r + sp_neck_p_1l + sp_neck_p_1r + sp_neck_p_2l + sp_neck_p_3r + sp_neck_p_4l + sp_neck_p_3l + sp_neck_p_2r + sp_neck_p_4r + sp_bag_p_1v + sp_bag_p_2r + sp_bag_p_3v + sp_bag_p_1d + sp_bag_p_1r + sp_bag_p_1l + sp_bag_p_2d + sp_bag_p_2l + sp_bag_p_2v + sp_bag_p_3d + sp_bag_p_3l + sp_bag_p_3r + sp_bag_p_4d + sp_bag_p_4l + sp_bag_p_4v + sp_bag_a_4v + sp_bag_a_4r + sp_bag_a_4d + sp_bag_a_3l + sp_bag_a_3r + sp_bag_a_3d + sp_bag_a_2v + sp_bag_a_2r + sp_bag_a_2d + sp_bag_a_1r + sp_bag_a_1l + sp_bag_a_1d + sp_bag_a_3v + sp_bag_a_2l + sp_bag_a_1v + sp_neck_a_4l + sp_neck_a_2l + sp_neck_a_3r + sp_neck_a_4r + sp_neck_a_3l + sp_neck_a_2r + sp_neck_a_1l + sp_neck_a_1r + sp_bag_a_4l + vula + vulb1 + vulb2 + vulc + vuld + vule + vulf + +Muscles + MuscleLayer + um1l_post + um1l_ant + um1r_post + um2l_post + um2l_ant + um2r_ant + um1r_ant + um2r_post + vm1l_ant + vm2r_ant + vm2l_post + vm1r_ant + vm1r_post + vm1l_post + vm2r_post + vm2l_ant + mu_int_r + mu_int_l + mu_bod_vl22 + mu_bod_vl20 + mu_bod_vl18 + mu_bod_vl16 + mu_bod_vl14 + mu_bod_vl12 + mu_bod_vl10 + mu_bod_vl8 + mu_bod_vl6 + mu_bod_vl4 + mu_bod_vl2 + mu_bod_vl21 + mu_bod_vl19 + mu_bod_vl17 + mu_bod_vl15 + mu_bod_vl13 + mu_bod_vl11 + mu_bod_vl9 + mu_bod_vl7 + mu_bod_vl5 + mu_bod_vl3 + mu_bod_vl1 + mu_bod_vr22 + mu_bod_vr20 + mu_bod_vr18 + mu_bod_vr16 + mu_bod_vr14 + mu_bod_vr12 + mu_bod_vr10 + mu_bod_vr8 + mu_bod_vr6 + mu_bod_vr4 + mu_bod_vr2 + mu_bod_vr21 + mu_bod_vr19 + mu_bod_vr17 + mu_bod_vr15 + mu_bod_vr13 + mu_bod_vr11 + mu_bod_vr9 + mu_bod_vr7 + mu_bod_vr5 + mu_bod_vr3 + mu_bod_vr1 + mu_bod_dr23 + mu_bod_dr22 + mu_bod_dr20 + mu_bod_dr18 + mu_bod_dr16 + mu_bod_dr14 + mu_bod_dr12 + mu_bod_dr10 + mu_bod_dr8 + mu_bod_dr6 + mu_bod_dr4 + mu_bod_dr2 + mu_bod_dr3 + mu_bod_dr5 + mu_bod_dr7 + mu_bod_dr9 + mu_bod_dr11 + mu_bod_dr21 + mu_bod_dr19 + mu_bod_dr17 + mu_bod_dr15 + mu_bod_dr13 + mu_bod_dl22 + mu_bod_dl20 + mu_bod_dl18 + mu_bod_dl16 + mu_bod_dl14 + mu_bod_dl12 + mu_bod_dl10 + mu_bod_dl8 + mu_bod_dl6 + mu_bod_dl4 + mu_bod_dl2 + mu_bod_dl23 + mu_bod_dl21 + mu_bod_dl19 + mu_bod_dl17 + mu_bod_dl15 + mu_bod_dl3 + mu_bod_dl5 + mu_bod_dl7 + mu_bod_dl9 + mu_bod_dl11 + mu_bod_dl13 + mu_bod_vr23 + mu_bod_vl23 + mu_bod_dr1 + mu_bod_dr24 + mu_bod_dl1 + mu_bod_dl24 + mu_bod_vr24 + +Neurons + NUFsLayer + alnr + plnr + canl + uryvr + auar + plnl + alnl + canr + urydr + ala + aual + urydl + uryvl + SheathLayer + ccdr + ccdl + ccpl + ccal + ccar + ccpr + olqshvr + ilshvr + ollshr + ilshr + amshr + phshr + phshl + olqshdr + cepshvr + cepshdr + ilshdr + adeshr + adeshl + ilshvl + ilshdl + olqshdl + ilshl + olqshvl + ollshl + cepshdl + cepshvl + PolyNeurLayer + olqvr + il1vr + il1r + nsmr + mcr + mcl + olqdr + nsml + il1dr + avl + il1dl + il1vl + il1l + olqdl + olqvl + MotNeurLayer + excretory_cell_excretory_cell + va11 + hsnr + dd6 + dd4 + dd3 + dd5 + dd2 + db7 + db6 + da9 + da7 + da6 + da5 + as11 + as10 + as9 + as8 + as7 + as6 + as5 + as4 + as3 + as2 + vd13 + vd12 + vd11 + vd10 + vd9 + vd8 + vd7 + vd6 + vd5 + vd4 + vd3 + db5 + db4 + da8 + da4 + da3 + da2 + vc5 + uravr + m5 + m3r + m2r + m1 + dvb + va12 + pda + pdb + vb11 + va10 + vb10 + va9 + vc6 + vb9 + va8 + vb8 + va7 + vb7 + va6 + vc3 + vb6 + va5 + vc2 + vb5 + va4 + vc1 + vb4 + va3 + db3 + vb3 + va2 + hsnl + vc4 + m4 + m2l + m3l + smbvr + rmfr + rmgr + uradr + rmer + rmhr + smbdr + smddr + rmddr + rmdr + rmdvr + smdvr + rimr + rmed + rmel + rmev + smdvl + rmdvl + rmdl + riml + vd2 + da1 + vd1 + as1 + db1 + dd1 + va1 + db2 + vb1 + vb2 + rmfl + smbdl + smddl + rmhl + rmddl + rmgl + uravl + uradl + smbvl + glrvr + glrdr + glrr + glrl + glrdl + glrvl + SocketLayer + olqsovr + ilsovr + xxxr + ollsor + ilsor + amsor + phso1r + phso2r + phso2l + phso1l + adesol + amsol + ilsodr + adesor + cepsovr + cepsodr + olqsodr + ilsovl + olqsovl + ilsol + ilsodl + olqsodl + xxxl + ollsol + cepsodl + cepsovl + SensNeurLayer + head_mesodermal_cell + pvm + pvdr + pder + avm + almr + pvr + plmr + phcr + phbr + phar + pvdl + il2vr + flpr + bagr + il2r + awcr + awbr + awar + askr + asir + aser + ashr + adlr + adfr + ader + pqr + phcl + phbl + phal + plml + pdel + alml + il2vl + cepdr + asgr + ollr + il2dr + afdr + cepvr + asjr + aqr + il2dl + bagl + cepvl + adel + flpl + adll + ashl + awbl + afdl + adfl + asel + awcl + asjl + cepdl + asil + askl + asgl + awal + olll + il2l + IntNeurLayer + dvc + dva + siavr + sdqr + sdql + bdur + pvnr + pvwr + pvcr + luar + pvpl + ripr + urbr + aibr + adar + mi + i6 + i5 + i4 + i3 + i2r + i1r + pvnl + pvcl + pvql + pvt + pvpr + pvwl + lual + pvqr + bdul + i1l + i2l + rivr + avjr + ricr + sibvr + siadr + aiar + saadl + ribr + avkr + avkl + avfl + avfr + avar + avbr + avdl + avbl + aval + avdr + saadr + rir + sibdr + aver + riar + saavr + ainr + avhr + urxr + ris + ripl + urbl + adal + rid + urxl + rivl + avhl + avjl + ainl + saavl + rial + avel + sibdl + ribl + aizl + ricl + aiyr + aimr + aibl + rigl + sabd + avg + rifl + rigr + rifr + sabvr + sabvl + aiml + aiyl + siadl + aial + siavl + sibvl + rih + +NUFsLayer + alnr + plnr + canl + uryvr + auar + plnl + alnl + canr + urydr + ala + aual + urydl + uryvl + +SheathLayer + ccdr + ccdl + ccpl + ccal + ccar + ccpr + olqshvr + ilshvr + ollshr + ilshr + amshr + phshr + phshl + olqshdr + cepshvr + cepshdr + ilshdr + adeshr + adeshl + ilshvl + ilshdl + olqshdl + ilshl + olqshvl + ollshl + cepshdl + cepshvl + +ExcSysLayer + excretory_duct_cell_excretory_duct_cell + excretory_gland_cell + excretory_pore_cell + +GLRLayer + rachis_p + rachis_a + oocyte_post_8 + oocyte_post_7 + oocyte_post_6 + oocyte_post_5 + oocyte_post_4 + oocyte_post_3 + oocyte_post_2 + oocyte_post_1 + oocyte_ant_10 + oocyte_ant_9 + oocyte_ant_8 + oocyte_ant_7 + oocyte_ant_6 + oocyte_ant_5 + oocyte_ant_4 + oocyte_ant_3 + oocyte_ant_2 + oocyte_ant_1 + +PolyNeurLayer + olqvr + il1vr + il1r + nsmr + mcr + mcl + olqdr + nsml + il1dr + avl + il1dl + il1vl + il1l + olqdl + olqvl + +MotNeurLayer + excretory_cell_excretory_cell + va11 + hsnr + dd6 + dd4 + dd3 + dd5 + dd2 + db7 + db6 + da9 + da7 + da6 + da5 + as11 + as10 + as9 + as8 + as7 + as6 + as5 + as4 + as3 + as2 + vd13 + vd12 + vd11 + vd10 + vd9 + vd8 + vd7 + vd6 + vd5 + vd4 + vd3 + db5 + db4 + da8 + da4 + da3 + da2 + vc5 + uravr + m5 + m3r + m2r + m1 + dvb + va12 + pda + pdb + vb11 + va10 + vb10 + va9 + vc6 + vb9 + va8 + vb8 + va7 + vb7 + va6 + vc3 + vb6 + va5 + vc2 + vb5 + va4 + vc1 + vb4 + va3 + db3 + vb3 + va2 + hsnl + vc4 + m4 + m2l + m3l + smbvr + rmfr + rmgr + uradr + rmer + rmhr + smbdr + smddr + rmddr + rmdr + rmdvr + smdvr + rimr + rmed + rmel + rmev + smdvl + rmdvl + rmdl + riml + vd2 + da1 + vd1 + as1 + db1 + dd1 + va1 + db2 + vb1 + vb2 + rmfl + smbdl + smddl + rmhl + rmddl + rmgl + uravl + uradl + smbvl + glrvr + glrdr + glrr + glrl + glrdl + glrvl + +RepSysLayer + gonadal_sheath_a4d + gonadal_sheath_p5d + gonadal_sheath_p4d + gonadal_sheath_p3l + gonadal_sheath_p2l + gonadal_sheath_p1l + gonadal_sheath_a3l + gonadal_sheath_a2l + gonadal_sheath_a1l + gonadal_sheath_p4v + gonadal_sheath_p3r + gonadal_sheath_p2r + gonadal_sheath_p1r + distal_tip_cell_p + gonadal_sheath_a4v + gonadal_sheath_a3r + gonadal_sheath_a2r + gonadal_sheath_a1r + distal_tip_cell_a + gonadal_sheath_p5v + gonadal_sheath_a5v + sp_ut_valve_post + sp_ut_valve_ant + ut2_ant + uv2_post + uv1_post + uv3_post + uv3_ant + uv2_ant + uv1_ant + du + ut3_post + ut2_post + ut1_post + ut1_ant + ut3_ant + utse + ut4_post + ut4_ant + sp_bag_p_4r + sp_neck_p_1l + sp_neck_p_1r + sp_neck_p_2l + sp_neck_p_3r + sp_neck_p_4l + sp_neck_p_3l + sp_neck_p_2r + sp_neck_p_4r + sp_bag_p_1v + sp_bag_p_2r + sp_bag_p_3v + sp_bag_p_1d + sp_bag_p_1r + sp_bag_p_1l + sp_bag_p_2d + sp_bag_p_2l + sp_bag_p_2v + sp_bag_p_3d + sp_bag_p_3l + sp_bag_p_3r + sp_bag_p_4d + sp_bag_p_4l + sp_bag_p_4v + sp_bag_a_4v + sp_bag_a_4r + sp_bag_a_4d + sp_bag_a_3l + sp_bag_a_3r + sp_bag_a_3d + sp_bag_a_2v + sp_bag_a_2r + sp_bag_a_2d + sp_bag_a_1r + sp_bag_a_1l + sp_bag_a_1d + sp_bag_a_3v + sp_bag_a_2l + sp_bag_a_1v + sp_neck_a_4l + sp_neck_a_2l + sp_neck_a_3r + sp_neck_a_4r + sp_neck_a_3l + sp_neck_a_2r + sp_neck_a_1l + sp_neck_a_1r + sp_bag_a_4l + vula + vulb1 + vulb2 + vulc + vuld + vule + vulf + +PharynxLayer + rect_vr + rect_d + rect_vl + phar_gland_g2_vr_phar_gland_vg2r + phar_gland_dorsal_g2_phar_gland_vd + phar_gland_g1_l_phar_gland_vg1l + phar_gland_g2_vl_phar_gland_vg2l + pm2vr + pm2vl + pm2d + pm8 + pm6vl + pm6d + pm6vr + pm4vl + pm4d + pm4vr + mc3dr + mc2v + mc2dr + mc1v + mc1dr + mc3v + mc3dl + mc2dl + mc1dl + pm1 + pm7vl + pm7d + pm5vl + pm5d + pm5vr + pm3vl + pm3d + pm3vr + pm7vr + anus + e1vr + e3vr + e2dr + e2dl + e3d + e1d + e2v + e3vl + e1vl + +SocketLayer + olqsovr + ilsovr + xxxr + ollsor + ilsor + amsor + phso1r + phso2r + phso2l + phso1l + adesol + amsol + ilsodr + adesor + cepsovr + cepsodr + olqsodr + ilsovl + olqsovl + ilsol + ilsodl + olqsodl + xxxl + ollsol + cepsodl + cepsovl + +HypCutLayer + seam_cells_left + seam_cells_right + arcade_cell_ant + arcade_cell_post + hyp10 + hyp9 + hyp8 + hyp11 + hyp7 + hyp5 + hyp4 + hyp2 + hyp1 + hyp3 + cuticle + +MuscleLayer + um1l_post + um1l_ant + um1r_post + um2l_post + um2l_ant + um2r_ant + um1r_ant + um2r_post + vm1l_ant + vm2r_ant + vm2l_post + vm1r_ant + vm1r_post + vm1l_post + vm2r_post + vm2l_ant + mu_int_r + mu_int_l + mu_bod_vl22 + mu_bod_vl20 + mu_bod_vl18 + mu_bod_vl16 + mu_bod_vl14 + mu_bod_vl12 + mu_bod_vl10 + mu_bod_vl8 + mu_bod_vl6 + mu_bod_vl4 + mu_bod_vl2 + mu_bod_vl21 + mu_bod_vl19 + mu_bod_vl17 + mu_bod_vl15 + mu_bod_vl13 + mu_bod_vl11 + mu_bod_vl9 + mu_bod_vl7 + mu_bod_vl5 + mu_bod_vl3 + mu_bod_vl1 + mu_bod_vr22 + mu_bod_vr20 + mu_bod_vr18 + mu_bod_vr16 + mu_bod_vr14 + mu_bod_vr12 + mu_bod_vr10 + mu_bod_vr8 + mu_bod_vr6 + mu_bod_vr4 + mu_bod_vr2 + mu_bod_vr21 + mu_bod_vr19 + mu_bod_vr17 + mu_bod_vr15 + mu_bod_vr13 + mu_bod_vr11 + mu_bod_vr9 + mu_bod_vr7 + mu_bod_vr5 + mu_bod_vr3 + mu_bod_vr1 + mu_bod_dr23 + mu_bod_dr22 + mu_bod_dr20 + mu_bod_dr18 + mu_bod_dr16 + mu_bod_dr14 + mu_bod_dr12 + mu_bod_dr10 + mu_bod_dr8 + mu_bod_dr6 + mu_bod_dr4 + mu_bod_dr2 + mu_bod_dr3 + mu_bod_dr5 + mu_bod_dr7 + mu_bod_dr9 + mu_bod_dr11 + mu_bod_dr21 + mu_bod_dr19 + mu_bod_dr17 + mu_bod_dr15 + mu_bod_dr13 + mu_bod_dl22 + mu_bod_dl20 + mu_bod_dl18 + mu_bod_dl16 + mu_bod_dl14 + mu_bod_dl12 + mu_bod_dl10 + mu_bod_dl8 + mu_bod_dl6 + mu_bod_dl4 + mu_bod_dl2 + mu_bod_dl23 + mu_bod_dl21 + mu_bod_dl19 + mu_bod_dl17 + mu_bod_dl15 + mu_bod_dl3 + mu_bod_dl5 + mu_bod_dl7 + mu_bod_dl9 + mu_bod_dl11 + mu_bod_dl13 + mu_bod_vr23 + mu_bod_vl23 + mu_bod_dr1 + mu_bod_dr24 + mu_bod_dl1 + mu_bod_dl24 + mu_bod_vr24 + +IntestineLayer + int1dl + int9l + int8l + int7l + int6l + int5l + int4v + int3v + int2d + int1vr + int1vl + int4d + int6r + int5r + int9r + int8r + int2v + int1dr + int7r + int3d + +SensNeurLayer + head_mesodermal_cell + pvm + pvdr + pder + avm + almr + pvr + plmr + phcr + phbr + phar + pvdl + il2vr + flpr + bagr + il2r + awcr + awbr + awar + askr + asir + aser + ashr + adlr + adfr + ader + pqr + phcl + phbl + phal + plml + pdel + alml + il2vl + cepdr + asgr + ollr + il2dr + afdr + cepvr + asjr + aqr + il2dl + bagl + cepvl + adel + flpl + adll + ashl + awbl + afdl + adfl + asel + awcl + asjl + cepdl + asil + askl + asgl + awal + olll + il2l + +RectLayer + virr + virl + vpi3_v + vpi3_d + vpi2_dl + vpi2_v + vpi2_dr + vpi1 + mu_anal + y + b + u + f + k + kprime + +IntNeurLayer + dvc + dva + siavr + sdqr + sdql + bdur + pvnr + pvwr + pvcr + luar + pvpl + ripr + urbr + aibr + adar + mi + i6 + i5 + i4 + i3 + i2r + i1r + pvnl + pvcl + pvql + pvt + pvpr + pvwl + lual + pvqr + bdul + i1l + i2l + rivr + avjr + ricr + sibvr + siadr + aiar + saadl + ribr + avkr + avkl + avfl + avfr + avar + avbr + avdl + avbl + aval + avdr + saadr + rir + sibdr + aver + riar + saavr + ainr + avhr + urxr + ris + ripl + urbl + adal + rid + urxl + rivl + avhl + avjl + ainl + saavl + rial + avel + sibdl + ribl + aizl + ricl + aiyr + aimr + aibl + rigl + sabd + avg + rifl + rigr + rifr + sabvr + sabvl + aiml + aiyl + siadl + aial + siavl + sibvl + rih diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/layer_icons.png b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/layer_icons.png new file mode 100644 index 0000000..177f026 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/layer_icons.png differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/model_icon.png b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/model_icon.png new file mode 100644 index 0000000..141c917 Binary files /dev/null and b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/model_icon.png differ diff --git a/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/parts_info.txt b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/parts_info.txt new file mode 100644 index 0000000..069b0cd --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/models/Virtual_Worm/parts_info.txt @@ -0,0 +1,3585 @@ +# Parts + +alnr + id: 5000 + type: part + display_name: alnr + +plnr + id: 5001 + type: part + display_name: plnr + +canl + id: 5002 + type: part + display_name: canl + +uryvr + id: 5003 + type: part + display_name: uryvr + +auar + id: 5004 + type: part + display_name: auar + +plnl + id: 5005 + type: part + display_name: plnl + +alnl + id: 5006 + type: part + display_name: alnl + +canr + id: 5007 + type: part + display_name: canr + +urydr + id: 5008 + type: part + display_name: urydr + +ala + id: 5009 + type: part + display_name: ala + +aual + id: 5010 + type: part + display_name: aual + +urydl + id: 5011 + type: part + display_name: urydl + +uryvl + id: 5012 + type: part + display_name: uryvl + +ccdr + id: 5013 + type: part + display_name: ccdr + +ccdl + id: 5014 + type: part + display_name: ccdl + +ccpl + id: 5015 + type: part + display_name: ccpl + +ccal + id: 5016 + type: part + display_name: ccal + +ccar + id: 5017 + type: part + display_name: ccar + +ccpr + id: 5018 + type: part + display_name: ccpr + +olqshvr + id: 5019 + type: part + display_name: olqshvr + +ilshvr + id: 5020 + type: part + display_name: ilshvr + +ollshr + id: 5021 + type: part + display_name: ollshr + +ilshr + id: 5022 + type: part + display_name: ilshr + +amshr + id: 5023 + type: part + display_name: amshr + +phshr + id: 5024 + type: part + display_name: phshr + +phshl + id: 5025 + type: part + display_name: phshl + +olqshdr + id: 5026 + type: part + display_name: olqshdr + +cepshvr + id: 5027 + type: part + display_name: cepshvr + +cepshdr + id: 5028 + type: part + display_name: cepshdr + +ilshdr + id: 5029 + type: part + display_name: ilshdr + +adeshr + id: 5030 + type: part + display_name: adeshr + +adeshl + id: 5031 + type: part + display_name: adeshl + +ilshvl + id: 5032 + type: part + display_name: ilshvl + +ilshdl + id: 5033 + type: part + display_name: ilshdl + +olqshdl + id: 5034 + type: part + display_name: olqshdl + +ilshl + id: 5035 + type: part + display_name: ilshl + +olqshvl + id: 5036 + type: part + display_name: olqshvl + +ollshl + id: 5037 + type: part + display_name: ollshl + +cepshdl + id: 5038 + type: part + display_name: cepshdl + +cepshvl + id: 5039 + type: part + display_name: cepshvl + +excretory_duct_cell_excretory_duct_cell + id: 5040 + type: part + display_name: excretory_duct_cell_excretory_duct_cell + +excretory_gland_cell + id: 5041 + type: part + display_name: excretory_gland_cell + +excretory_pore_cell + id: 5042 + type: part + display_name: excretory_pore_cell + +rachis_p + id: 5043 + type: part + display_name: rachis_p + +rachis_a + id: 5044 + type: part + display_name: rachis_a + +oocyte_post_8 + id: 5045 + type: part + display_name: oocyte_post_8 + +oocyte_post_7 + id: 5046 + type: part + display_name: oocyte_post_7 + +oocyte_post_6 + id: 5047 + type: part + display_name: oocyte_post_6 + +oocyte_post_5 + id: 5048 + type: part + display_name: oocyte_post_5 + +oocyte_post_4 + id: 5049 + type: part + display_name: oocyte_post_4 + +oocyte_post_3 + id: 5050 + type: part + display_name: oocyte_post_3 + +oocyte_post_2 + id: 5051 + type: part + display_name: oocyte_post_2 + +oocyte_post_1 + id: 5052 + type: part + display_name: oocyte_post_1 + +oocyte_ant_10 + id: 5053 + type: part + display_name: oocyte_ant_10 + +oocyte_ant_9 + id: 5054 + type: part + display_name: oocyte_ant_9 + +oocyte_ant_8 + id: 5055 + type: part + display_name: oocyte_ant_8 + +oocyte_ant_7 + id: 5056 + type: part + display_name: oocyte_ant_7 + +oocyte_ant_6 + id: 5057 + type: part + display_name: oocyte_ant_6 + +oocyte_ant_5 + id: 5058 + type: part + display_name: oocyte_ant_5 + +oocyte_ant_4 + id: 5059 + type: part + display_name: oocyte_ant_4 + +oocyte_ant_3 + id: 5060 + type: part + display_name: oocyte_ant_3 + +oocyte_ant_2 + id: 5061 + type: part + display_name: oocyte_ant_2 + +oocyte_ant_1 + id: 5062 + type: part + display_name: oocyte_ant_1 + +olqvr + id: 5063 + type: part + display_name: olqvr + +il1vr + id: 5064 + type: part + display_name: il1vr + +il1r + id: 5065 + type: part + display_name: il1r + +nsmr + id: 5066 + type: part + display_name: nsmr + +mcr + id: 5067 + type: part + display_name: mcr + +mcl + id: 5068 + type: part + display_name: mcl + +olqdr + id: 5069 + type: part + display_name: olqdr + +nsml + id: 5070 + type: part + display_name: nsml + +il1dr + id: 5071 + type: part + display_name: il1dr + +avl + id: 5072 + type: part + display_name: avl + +il1dl + id: 5073 + type: part + display_name: il1dl + +il1vl + id: 5074 + type: part + display_name: il1vl + +il1l + id: 5075 + type: part + display_name: il1l + +olqdl + id: 5076 + type: part + display_name: olqdl + +olqvl + id: 5077 + type: part + display_name: olqvl + +excretory_cell_excretory_cell + id: 5078 + type: part + display_name: excretory_cell_excretory_cell + +va11 + id: 5079 + type: part + display_name: va11 + +hsnr + id: 5080 + type: part + display_name: hsnr + +dd6 + id: 5081 + type: part + display_name: dd6 + +dd4 + id: 5082 + type: part + display_name: dd4 + +dd3 + id: 5083 + type: part + display_name: dd3 + +dd5 + id: 5084 + type: part + display_name: dd5 + +dd2 + id: 5085 + type: part + display_name: dd2 + +db7 + id: 5086 + type: part + display_name: db7 + +db6 + id: 5087 + type: part + display_name: db6 + +da9 + id: 5088 + type: part + display_name: da9 + +da7 + id: 5089 + type: part + display_name: da7 + +da6 + id: 5090 + type: part + display_name: da6 + +da5 + id: 5091 + type: part + display_name: da5 + +as11 + id: 5092 + type: part + display_name: as11 + +as10 + id: 5093 + type: part + display_name: as10 + +as9 + id: 5094 + type: part + display_name: as9 + +as8 + id: 5095 + type: part + display_name: as8 + +as7 + id: 5096 + type: part + display_name: as7 + +as6 + id: 5097 + type: part + display_name: as6 + +as5 + id: 5098 + type: part + display_name: as5 + +as4 + id: 5099 + type: part + display_name: as4 + +as3 + id: 5100 + type: part + display_name: as3 + +as2 + id: 5101 + type: part + display_name: as2 + +vd13 + id: 5102 + type: part + display_name: vd13 + +vd12 + id: 5103 + type: part + display_name: vd12 + +vd11 + id: 5104 + type: part + display_name: vd11 + +vd10 + id: 5105 + type: part + display_name: vd10 + +vd9 + id: 5106 + type: part + display_name: vd9 + +vd8 + id: 5107 + type: part + display_name: vd8 + +vd7 + id: 5108 + type: part + display_name: vd7 + +vd6 + id: 5109 + type: part + display_name: vd6 + +vd5 + id: 5110 + type: part + display_name: vd5 + +vd4 + id: 5111 + type: part + display_name: vd4 + +vd3 + id: 5112 + type: part + display_name: vd3 + +db5 + id: 5113 + type: part + display_name: db5 + +db4 + id: 5114 + type: part + display_name: db4 + +da8 + id: 5115 + type: part + display_name: da8 + +da4 + id: 5116 + type: part + display_name: da4 + +da3 + id: 5117 + type: part + display_name: da3 + +da2 + id: 5118 + type: part + display_name: da2 + +vc5 + id: 5119 + type: part + display_name: vc5 + +uravr + id: 5120 + type: part + display_name: uravr + +m5 + id: 5121 + type: part + display_name: m5 + +m3r + id: 5122 + type: part + display_name: m3r + +m2r + id: 5123 + type: part + display_name: m2r + +m1 + id: 5124 + type: part + display_name: m1 + +dvb + id: 5125 + type: part + display_name: dvb + +va12 + id: 5126 + type: part + display_name: va12 + +pda + id: 5127 + type: part + display_name: pda + +pdb + id: 5128 + type: part + display_name: pdb + +vb11 + id: 5129 + type: part + display_name: vb11 + +va10 + id: 5130 + type: part + display_name: va10 + +vb10 + id: 5131 + type: part + display_name: vb10 + +va9 + id: 5132 + type: part + display_name: va9 + +vc6 + id: 5133 + type: part + display_name: vc6 + +vb9 + id: 5134 + type: part + display_name: vb9 + +va8 + id: 5135 + type: part + display_name: va8 + +vb8 + id: 5136 + type: part + display_name: vb8 + +va7 + id: 5137 + type: part + display_name: va7 + +vb7 + id: 5138 + type: part + display_name: vb7 + +va6 + id: 5139 + type: part + display_name: va6 + +vc3 + id: 5140 + type: part + display_name: vc3 + +vb6 + id: 5141 + type: part + display_name: vb6 + +va5 + id: 5142 + type: part + display_name: va5 + +vc2 + id: 5143 + type: part + display_name: vc2 + +vb5 + id: 5144 + type: part + display_name: vb5 + +va4 + id: 5145 + type: part + display_name: va4 + +vc1 + id: 5146 + type: part + display_name: vc1 + +vb4 + id: 5147 + type: part + display_name: vb4 + +va3 + id: 5148 + type: part + display_name: va3 + +db3 + id: 5149 + type: part + display_name: db3 + +vb3 + id: 5150 + type: part + display_name: vb3 + +va2 + id: 5151 + type: part + display_name: va2 + +hsnl + id: 5152 + type: part + display_name: hsnl + +vc4 + id: 5153 + type: part + display_name: vc4 + +m4 + id: 5154 + type: part + display_name: m4 + +m2l + id: 5155 + type: part + display_name: m2l + +m3l + id: 5156 + type: part + display_name: m3l + +smbvr + id: 5157 + type: part + display_name: smbvr + +rmfr + id: 5158 + type: part + display_name: rmfr + +rmgr + id: 5159 + type: part + display_name: rmgr + +uradr + id: 5160 + type: part + display_name: uradr + +rmer + id: 5161 + type: part + display_name: rmer + +rmhr + id: 5162 + type: part + display_name: rmhr + +smbdr + id: 5163 + type: part + display_name: smbdr + +smddr + id: 5164 + type: part + display_name: smddr + +rmddr + id: 5165 + type: part + display_name: rmddr + +rmdr + id: 5166 + type: part + display_name: rmdr + +rmdvr + id: 5167 + type: part + display_name: rmdvr + +smdvr + id: 5168 + type: part + display_name: smdvr + +rimr + id: 5169 + type: part + display_name: rimr + +rmed + id: 5170 + type: part + display_name: rmed + +rmel + id: 5171 + type: part + display_name: rmel + +rmev + id: 5172 + type: part + display_name: rmev + +smdvl + id: 5173 + type: part + display_name: smdvl + +rmdvl + id: 5174 + type: part + display_name: rmdvl + +rmdl + id: 5175 + type: part + display_name: rmdl + +riml + id: 5176 + type: part + display_name: riml + +vd2 + id: 5177 + type: part + display_name: vd2 + +da1 + id: 5178 + type: part + display_name: da1 + +vd1 + id: 5179 + type: part + display_name: vd1 + +as1 + id: 5180 + type: part + display_name: as1 + +db1 + id: 5181 + type: part + display_name: db1 + +dd1 + id: 5182 + type: part + display_name: dd1 + +va1 + id: 5183 + type: part + display_name: va1 + +db2 + id: 5184 + type: part + display_name: db2 + +vb1 + id: 5185 + type: part + display_name: vb1 + +vb2 + id: 5186 + type: part + display_name: vb2 + +rmfl + id: 5187 + type: part + display_name: rmfl + +smbdl + id: 5188 + type: part + display_name: smbdl + +smddl + id: 5189 + type: part + display_name: smddl + +rmhl + id: 5190 + type: part + display_name: rmhl + +rmddl + id: 5191 + type: part + display_name: rmddl + +rmgl + id: 5192 + type: part + display_name: rmgl + +uravl + id: 5193 + type: part + display_name: uravl + +uradl + id: 5194 + type: part + display_name: uradl + +smbvl + id: 5195 + type: part + display_name: smbvl + +glrvr + id: 5196 + type: part + display_name: glrvr + +glrdr + id: 5197 + type: part + display_name: glrdr + +glrr + id: 5198 + type: part + display_name: glrr + +glrl + id: 5199 + type: part + display_name: glrl + +glrdl + id: 5200 + type: part + display_name: glrdl + +glrvl + id: 5201 + type: part + display_name: glrvl + +gonadal_sheath_a4d + id: 5202 + type: part + display_name: gonadal_sheath_a4d + +gonadal_sheath_p5d + id: 5203 + type: part + display_name: gonadal_sheath_p5d + +gonadal_sheath_p4d + id: 5204 + type: part + display_name: gonadal_sheath_p4d + +gonadal_sheath_p3l + id: 5205 + type: part + display_name: gonadal_sheath_p3l + +gonadal_sheath_p2l + id: 5206 + type: part + display_name: gonadal_sheath_p2l + +gonadal_sheath_p1l + id: 5207 + type: part + display_name: gonadal_sheath_p1l + +gonadal_sheath_a3l + id: 5208 + type: part + display_name: gonadal_sheath_a3l + +gonadal_sheath_a2l + id: 5209 + type: part + display_name: gonadal_sheath_a2l + +gonadal_sheath_a1l + id: 5210 + type: part + display_name: gonadal_sheath_a1l + +gonadal_sheath_p4v + id: 5211 + type: part + display_name: gonadal_sheath_p4v + +gonadal_sheath_p3r + id: 5212 + type: part + display_name: gonadal_sheath_p3r + +gonadal_sheath_p2r + id: 5213 + type: part + display_name: gonadal_sheath_p2r + +gonadal_sheath_p1r + id: 5214 + type: part + display_name: gonadal_sheath_p1r + +distal_tip_cell_p + id: 5215 + type: part + display_name: distal_tip_cell_p + +gonadal_sheath_a4v + id: 5216 + type: part + display_name: gonadal_sheath_a4v + +gonadal_sheath_a3r + id: 5217 + type: part + display_name: gonadal_sheath_a3r + +gonadal_sheath_a2r + id: 5218 + type: part + display_name: gonadal_sheath_a2r + +gonadal_sheath_a1r + id: 5219 + type: part + display_name: gonadal_sheath_a1r + +distal_tip_cell_a + id: 5220 + type: part + display_name: distal_tip_cell_a + +gonadal_sheath_p5v + id: 5221 + type: part + display_name: gonadal_sheath_p5v + +gonadal_sheath_a5v + id: 5222 + type: part + display_name: gonadal_sheath_a5v + +sp_ut_valve_post + id: 5223 + type: part + display_name: sp_ut_valve_post + +sp_ut_valve_ant + id: 5224 + type: part + display_name: sp_ut_valve_ant + +ut2_ant + id: 5225 + type: part + display_name: ut2_ant + +uv2_post + id: 5226 + type: part + display_name: uv2_post + +uv1_post + id: 5227 + type: part + display_name: uv1_post + +uv3_post + id: 5228 + type: part + display_name: uv3_post + +uv3_ant + id: 5229 + type: part + display_name: uv3_ant + +uv2_ant + id: 5230 + type: part + display_name: uv2_ant + +uv1_ant + id: 5231 + type: part + display_name: uv1_ant + +du + id: 5232 + type: part + display_name: du + +ut3_post + id: 5233 + type: part + display_name: ut3_post + +ut2_post + id: 5234 + type: part + display_name: ut2_post + +ut1_post + id: 5235 + type: part + display_name: ut1_post + +ut1_ant + id: 5236 + type: part + display_name: ut1_ant + +ut3_ant + id: 5237 + type: part + display_name: ut3_ant + +utse + id: 5238 + type: part + display_name: utse + +ut4_post + id: 5239 + type: part + display_name: ut4_post + +ut4_ant + id: 5240 + type: part + display_name: ut4_ant + +sp_bag_p_4r + id: 5241 + type: part + display_name: sp_bag_p_4r + +sp_neck_p_1l + id: 5242 + type: part + display_name: sp_neck_p_1l + +sp_neck_p_1r + id: 5243 + type: part + display_name: sp_neck_p_1r + +sp_neck_p_2l + id: 5244 + type: part + display_name: sp_neck_p_2l + +sp_neck_p_3r + id: 5245 + type: part + display_name: sp_neck_p_3r + +sp_neck_p_4l + id: 5246 + type: part + display_name: sp_neck_p_4l + +sp_neck_p_3l + id: 5247 + type: part + display_name: sp_neck_p_3l + +sp_neck_p_2r + id: 5248 + type: part + display_name: sp_neck_p_2r + +sp_neck_p_4r + id: 5249 + type: part + display_name: sp_neck_p_4r + +sp_bag_p_1v + id: 5250 + type: part + display_name: sp_bag_p_1v + +sp_bag_p_2r + id: 5251 + type: part + display_name: sp_bag_p_2r + +sp_bag_p_3v + id: 5252 + type: part + display_name: sp_bag_p_3v + +sp_bag_p_1d + id: 5253 + type: part + display_name: sp_bag_p_1d + +sp_bag_p_1r + id: 5254 + type: part + display_name: sp_bag_p_1r + +sp_bag_p_1l + id: 5255 + type: part + display_name: sp_bag_p_1l + +sp_bag_p_2d + id: 5256 + type: part + display_name: sp_bag_p_2d + +sp_bag_p_2l + id: 5257 + type: part + display_name: sp_bag_p_2l + +sp_bag_p_2v + id: 5258 + type: part + display_name: sp_bag_p_2v + +sp_bag_p_3d + id: 5259 + type: part + display_name: sp_bag_p_3d + +sp_bag_p_3l + id: 5260 + type: part + display_name: sp_bag_p_3l + +sp_bag_p_3r + id: 5261 + type: part + display_name: sp_bag_p_3r + +sp_bag_p_4d + id: 5262 + type: part + display_name: sp_bag_p_4d + +sp_bag_p_4l + id: 5263 + type: part + display_name: sp_bag_p_4l + +sp_bag_p_4v + id: 5264 + type: part + display_name: sp_bag_p_4v + +sp_bag_a_4v + id: 5265 + type: part + display_name: sp_bag_a_4v + +sp_bag_a_4r + id: 5266 + type: part + display_name: sp_bag_a_4r + +sp_bag_a_4d + id: 5267 + type: part + display_name: sp_bag_a_4d + +sp_bag_a_3l + id: 5268 + type: part + display_name: sp_bag_a_3l + +sp_bag_a_3r + id: 5269 + type: part + display_name: sp_bag_a_3r + +sp_bag_a_3d + id: 5270 + type: part + display_name: sp_bag_a_3d + +sp_bag_a_2v + id: 5271 + type: part + display_name: sp_bag_a_2v + +sp_bag_a_2r + id: 5272 + type: part + display_name: sp_bag_a_2r + +sp_bag_a_2d + id: 5273 + type: part + display_name: sp_bag_a_2d + +sp_bag_a_1r + id: 5274 + type: part + display_name: sp_bag_a_1r + +sp_bag_a_1l + id: 5275 + type: part + display_name: sp_bag_a_1l + +sp_bag_a_1d + id: 5276 + type: part + display_name: sp_bag_a_1d + +sp_bag_a_3v + id: 5277 + type: part + display_name: sp_bag_a_3v + +sp_bag_a_2l + id: 5278 + type: part + display_name: sp_bag_a_2l + +sp_bag_a_1v + id: 5279 + type: part + display_name: sp_bag_a_1v + +sp_neck_a_4l + id: 5280 + type: part + display_name: sp_neck_a_4l + +sp_neck_a_2l + id: 5281 + type: part + display_name: sp_neck_a_2l + +sp_neck_a_3r + id: 5282 + type: part + display_name: sp_neck_a_3r + +sp_neck_a_4r + id: 5283 + type: part + display_name: sp_neck_a_4r + +sp_neck_a_3l + id: 5284 + type: part + display_name: sp_neck_a_3l + +sp_neck_a_2r + id: 5285 + type: part + display_name: sp_neck_a_2r + +sp_neck_a_1l + id: 5286 + type: part + display_name: sp_neck_a_1l + +sp_neck_a_1r + id: 5287 + type: part + display_name: sp_neck_a_1r + +sp_bag_a_4l + id: 5288 + type: part + display_name: sp_bag_a_4l + +vula + id: 5289 + type: part + display_name: vula + +vulb1 + id: 5290 + type: part + display_name: vulb1 + +vulb2 + id: 5291 + type: part + display_name: vulb2 + +vulc + id: 5292 + type: part + display_name: vulc + +vuld + id: 5293 + type: part + display_name: vuld + +vule + id: 5294 + type: part + display_name: vule + +vulf + id: 5295 + type: part + display_name: vulf + +rect_vr + id: 5296 + type: part + display_name: rect_vr + +rect_d + id: 5297 + type: part + display_name: rect_d + +rect_vl + id: 5298 + type: part + display_name: rect_vl + +phar_gland_g2_vr_phar_gland_vg2r + id: 5299 + type: part + display_name: phar_gland_g2_vr_phar_gland_vg2r + +phar_gland_dorsal_g2_phar_gland_vd + id: 5300 + type: part + display_name: phar_gland_dorsal_g2_phar_gland_vd + +phar_gland_g1_l_phar_gland_vg1l + id: 5301 + type: part + display_name: phar_gland_g1_l_phar_gland_vg1l + +phar_gland_g2_vl_phar_gland_vg2l + id: 5302 + type: part + display_name: phar_gland_g2_vl_phar_gland_vg2l + +pm2vr + id: 5303 + type: part + display_name: pm2vr + +pm2vl + id: 5304 + type: part + display_name: pm2vl + +pm2d + id: 5305 + type: part + display_name: pm2d + +pm8 + id: 5306 + type: part + display_name: pm8 + +pm6vl + id: 5307 + type: part + display_name: pm6vl + +pm6d + id: 5308 + type: part + display_name: pm6d + +pm6vr + id: 5309 + type: part + display_name: pm6vr + +pm4vl + id: 5310 + type: part + display_name: pm4vl + +pm4d + id: 5311 + type: part + display_name: pm4d + +pm4vr + id: 5312 + type: part + display_name: pm4vr + +mc3dr + id: 5313 + type: part + display_name: mc3dr + +mc2v + id: 5314 + type: part + display_name: mc2v + +mc2dr + id: 5315 + type: part + display_name: mc2dr + +mc1v + id: 5316 + type: part + display_name: mc1v + +mc1dr + id: 5317 + type: part + display_name: mc1dr + +mc3v + id: 5318 + type: part + display_name: mc3v + +mc3dl + id: 5319 + type: part + display_name: mc3dl + +mc2dl + id: 5320 + type: part + display_name: mc2dl + +mc1dl + id: 5321 + type: part + display_name: mc1dl + +pm1 + id: 5322 + type: part + display_name: pm1 + +pm7vl + id: 5323 + type: part + display_name: pm7vl + +pm7d + id: 5324 + type: part + display_name: pm7d + +pm5vl + id: 5325 + type: part + display_name: pm5vl + +pm5d + id: 5326 + type: part + display_name: pm5d + +pm5vr + id: 5327 + type: part + display_name: pm5vr + +pm3vl + id: 5328 + type: part + display_name: pm3vl + +pm3d + id: 5329 + type: part + display_name: pm3d + +pm3vr + id: 5330 + type: part + display_name: pm3vr + +pm7vr + id: 5331 + type: part + display_name: pm7vr + +anus + id: 5332 + type: part + display_name: anus + +e1vr + id: 5333 + type: part + display_name: e1vr + +e3vr + id: 5334 + type: part + display_name: e3vr + +e2dr + id: 5335 + type: part + display_name: e2dr + +e2dl + id: 5336 + type: part + display_name: e2dl + +e3d + id: 5337 + type: part + display_name: e3d + +e1d + id: 5338 + type: part + display_name: e1d + +e2v + id: 5339 + type: part + display_name: e2v + +e3vl + id: 5340 + type: part + display_name: e3vl + +e1vl + id: 5341 + type: part + display_name: e1vl + +olqsovr + id: 5342 + type: part + display_name: olqsovr + +ilsovr + id: 5343 + type: part + display_name: ilsovr + +xxxr + id: 5344 + type: part + display_name: xxxr + +ollsor + id: 5345 + type: part + display_name: ollsor + +ilsor + id: 5346 + type: part + display_name: ilsor + +amsor + id: 5347 + type: part + display_name: amsor + +phso1r + id: 5348 + type: part + display_name: phso1r + +phso2r + id: 5349 + type: part + display_name: phso2r + +phso2l + id: 5350 + type: part + display_name: phso2l + +phso1l + id: 5351 + type: part + display_name: phso1l + +adesol + id: 5352 + type: part + display_name: adesol + +amsol + id: 5353 + type: part + display_name: amsol + +ilsodr + id: 5354 + type: part + display_name: ilsodr + +adesor + id: 5355 + type: part + display_name: adesor + +cepsovr + id: 5356 + type: part + display_name: cepsovr + +cepsodr + id: 5357 + type: part + display_name: cepsodr + +olqsodr + id: 5358 + type: part + display_name: olqsodr + +ilsovl + id: 5359 + type: part + display_name: ilsovl + +olqsovl + id: 5360 + type: part + display_name: olqsovl + +ilsol + id: 5361 + type: part + display_name: ilsol + +ilsodl + id: 5362 + type: part + display_name: ilsodl + +olqsodl + id: 5363 + type: part + display_name: olqsodl + +xxxl + id: 5364 + type: part + display_name: xxxl + +ollsol + id: 5365 + type: part + display_name: ollsol + +cepsodl + id: 5366 + type: part + display_name: cepsodl + +cepsovl + id: 5367 + type: part + display_name: cepsovl + +seam_cells_left + id: 5368 + type: part + display_name: seam_cells_left + +seam_cells_right + id: 5369 + type: part + display_name: seam_cells_right + +arcade_cell_ant + id: 5370 + type: part + display_name: arcade_cell_ant + +arcade_cell_post + id: 5371 + type: part + display_name: arcade_cell_post + +hyp10 + id: 5372 + type: part + display_name: hyp10 + +hyp9 + id: 5373 + type: part + display_name: hyp9 + +hyp8 + id: 5374 + type: part + display_name: hyp8 + +hyp11 + id: 5375 + type: part + display_name: hyp11 + +hyp7 + id: 5376 + type: part + display_name: hyp7 + +hyp5 + id: 5377 + type: part + display_name: hyp5 + +hyp4 + id: 5378 + type: part + display_name: hyp4 + +hyp2 + id: 5379 + type: part + display_name: hyp2 + +hyp1 + id: 5380 + type: part + display_name: hyp1 + +hyp3 + id: 5381 + type: part + display_name: hyp3 + +cuticle + id: 5382 + type: part + display_name: cuticle + +um1l_post + id: 5383 + type: part + display_name: um1l_post + +um1l_ant + id: 5384 + type: part + display_name: um1l_ant + +um1r_post + id: 5385 + type: part + display_name: um1r_post + +um2l_post + id: 5386 + type: part + display_name: um2l_post + +um2l_ant + id: 5387 + type: part + display_name: um2l_ant + +um2r_ant + id: 5388 + type: part + display_name: um2r_ant + +um1r_ant + id: 5389 + type: part + display_name: um1r_ant + +um2r_post + id: 5390 + type: part + display_name: um2r_post + +vm1l_ant + id: 5391 + type: part + display_name: vm1l_ant + +vm2r_ant + id: 5392 + type: part + display_name: vm2r_ant + +vm2l_post + id: 5393 + type: part + display_name: vm2l_post + +vm1r_ant + id: 5394 + type: part + display_name: vm1r_ant + +vm1r_post + id: 5395 + type: part + display_name: vm1r_post + +vm1l_post + id: 5396 + type: part + display_name: vm1l_post + +vm2r_post + id: 5397 + type: part + display_name: vm2r_post + +vm2l_ant + id: 5398 + type: part + display_name: vm2l_ant + +mu_int_r + id: 5399 + type: part + display_name: mu_int_r + +mu_int_l + id: 5400 + type: part + display_name: mu_int_l + +mu_bod_vl22 + id: 5401 + type: part + display_name: mu_bod_vl22 + +mu_bod_vl20 + id: 5402 + type: part + display_name: mu_bod_vl20 + +mu_bod_vl18 + id: 5403 + type: part + display_name: mu_bod_vl18 + +mu_bod_vl16 + id: 5404 + type: part + display_name: mu_bod_vl16 + +mu_bod_vl14 + id: 5405 + type: part + display_name: mu_bod_vl14 + +mu_bod_vl12 + id: 5406 + type: part + display_name: mu_bod_vl12 + +mu_bod_vl10 + id: 5407 + type: part + display_name: mu_bod_vl10 + +mu_bod_vl8 + id: 5408 + type: part + display_name: mu_bod_vl8 + +mu_bod_vl6 + id: 5409 + type: part + display_name: mu_bod_vl6 + +mu_bod_vl4 + id: 5410 + type: part + display_name: mu_bod_vl4 + +mu_bod_vl2 + id: 5411 + type: part + display_name: mu_bod_vl2 + +mu_bod_vl21 + id: 5412 + type: part + display_name: mu_bod_vl21 + +mu_bod_vl19 + id: 5413 + type: part + display_name: mu_bod_vl19 + +mu_bod_vl17 + id: 5414 + type: part + display_name: mu_bod_vl17 + +mu_bod_vl15 + id: 5415 + type: part + display_name: mu_bod_vl15 + +mu_bod_vl13 + id: 5416 + type: part + display_name: mu_bod_vl13 + +mu_bod_vl11 + id: 5417 + type: part + display_name: mu_bod_vl11 + +mu_bod_vl9 + id: 5418 + type: part + display_name: mu_bod_vl9 + +mu_bod_vl7 + id: 5419 + type: part + display_name: mu_bod_vl7 + +mu_bod_vl5 + id: 5420 + type: part + display_name: mu_bod_vl5 + +mu_bod_vl3 + id: 5421 + type: part + display_name: mu_bod_vl3 + +mu_bod_vl1 + id: 5422 + type: part + display_name: mu_bod_vl1 + +mu_bod_vr22 + id: 5423 + type: part + display_name: mu_bod_vr22 + +mu_bod_vr20 + id: 5424 + type: part + display_name: mu_bod_vr20 + +mu_bod_vr18 + id: 5425 + type: part + display_name: mu_bod_vr18 + +mu_bod_vr16 + id: 5426 + type: part + display_name: mu_bod_vr16 + +mu_bod_vr14 + id: 5427 + type: part + display_name: mu_bod_vr14 + +mu_bod_vr12 + id: 5428 + type: part + display_name: mu_bod_vr12 + +mu_bod_vr10 + id: 5429 + type: part + display_name: mu_bod_vr10 + +mu_bod_vr8 + id: 5430 + type: part + display_name: mu_bod_vr8 + +mu_bod_vr6 + id: 5431 + type: part + display_name: mu_bod_vr6 + +mu_bod_vr4 + id: 5432 + type: part + display_name: mu_bod_vr4 + +mu_bod_vr2 + id: 5433 + type: part + display_name: mu_bod_vr2 + +mu_bod_vr21 + id: 5434 + type: part + display_name: mu_bod_vr21 + +mu_bod_vr19 + id: 5435 + type: part + display_name: mu_bod_vr19 + +mu_bod_vr17 + id: 5436 + type: part + display_name: mu_bod_vr17 + +mu_bod_vr15 + id: 5437 + type: part + display_name: mu_bod_vr15 + +mu_bod_vr13 + id: 5438 + type: part + display_name: mu_bod_vr13 + +mu_bod_vr11 + id: 5439 + type: part + display_name: mu_bod_vr11 + +mu_bod_vr9 + id: 5440 + type: part + display_name: mu_bod_vr9 + +mu_bod_vr7 + id: 5441 + type: part + display_name: mu_bod_vr7 + +mu_bod_vr5 + id: 5442 + type: part + display_name: mu_bod_vr5 + +mu_bod_vr3 + id: 5443 + type: part + display_name: mu_bod_vr3 + +mu_bod_vr1 + id: 5444 + type: part + display_name: mu_bod_vr1 + +mu_bod_dr23 + id: 5445 + type: part + display_name: mu_bod_dr23 + +mu_bod_dr22 + id: 5446 + type: part + display_name: mu_bod_dr22 + +mu_bod_dr20 + id: 5447 + type: part + display_name: mu_bod_dr20 + +mu_bod_dr18 + id: 5448 + type: part + display_name: mu_bod_dr18 + +mu_bod_dr16 + id: 5449 + type: part + display_name: mu_bod_dr16 + +mu_bod_dr14 + id: 5450 + type: part + display_name: mu_bod_dr14 + +mu_bod_dr12 + id: 5451 + type: part + display_name: mu_bod_dr12 + +mu_bod_dr10 + id: 5452 + type: part + display_name: mu_bod_dr10 + +mu_bod_dr8 + id: 5453 + type: part + display_name: mu_bod_dr8 + +mu_bod_dr6 + id: 5454 + type: part + display_name: mu_bod_dr6 + +mu_bod_dr4 + id: 5455 + type: part + display_name: mu_bod_dr4 + +mu_bod_dr2 + id: 5456 + type: part + display_name: mu_bod_dr2 + +mu_bod_dr3 + id: 5457 + type: part + display_name: mu_bod_dr3 + +mu_bod_dr5 + id: 5458 + type: part + display_name: mu_bod_dr5 + +mu_bod_dr7 + id: 5459 + type: part + display_name: mu_bod_dr7 + +mu_bod_dr9 + id: 5460 + type: part + display_name: mu_bod_dr9 + +mu_bod_dr11 + id: 5461 + type: part + display_name: mu_bod_dr11 + +mu_bod_dr21 + id: 5462 + type: part + display_name: mu_bod_dr21 + +mu_bod_dr19 + id: 5463 + type: part + display_name: mu_bod_dr19 + +mu_bod_dr17 + id: 5464 + type: part + display_name: mu_bod_dr17 + +mu_bod_dr15 + id: 5465 + type: part + display_name: mu_bod_dr15 + +mu_bod_dr13 + id: 5466 + type: part + display_name: mu_bod_dr13 + +mu_bod_dl22 + id: 5467 + type: part + display_name: mu_bod_dl22 + +mu_bod_dl20 + id: 5468 + type: part + display_name: mu_bod_dl20 + +mu_bod_dl18 + id: 5469 + type: part + display_name: mu_bod_dl18 + +mu_bod_dl16 + id: 5470 + type: part + display_name: mu_bod_dl16 + +mu_bod_dl14 + id: 5471 + type: part + display_name: mu_bod_dl14 + +mu_bod_dl12 + id: 5472 + type: part + display_name: mu_bod_dl12 + +mu_bod_dl10 + id: 5473 + type: part + display_name: mu_bod_dl10 + +mu_bod_dl8 + id: 5474 + type: part + display_name: mu_bod_dl8 + +mu_bod_dl6 + id: 5475 + type: part + display_name: mu_bod_dl6 + +mu_bod_dl4 + id: 5476 + type: part + display_name: mu_bod_dl4 + +mu_bod_dl2 + id: 5477 + type: part + display_name: mu_bod_dl2 + +mu_bod_dl23 + id: 5478 + type: part + display_name: mu_bod_dl23 + +mu_bod_dl21 + id: 5479 + type: part + display_name: mu_bod_dl21 + +mu_bod_dl19 + id: 5480 + type: part + display_name: mu_bod_dl19 + +mu_bod_dl17 + id: 5481 + type: part + display_name: mu_bod_dl17 + +mu_bod_dl15 + id: 5482 + type: part + display_name: mu_bod_dl15 + +mu_bod_dl3 + id: 5483 + type: part + display_name: mu_bod_dl3 + +mu_bod_dl5 + id: 5484 + type: part + display_name: mu_bod_dl5 + +mu_bod_dl7 + id: 5485 + type: part + display_name: mu_bod_dl7 + +mu_bod_dl9 + id: 5486 + type: part + display_name: mu_bod_dl9 + +mu_bod_dl11 + id: 5487 + type: part + display_name: mu_bod_dl11 + +mu_bod_dl13 + id: 5488 + type: part + display_name: mu_bod_dl13 + +mu_bod_vr23 + id: 5489 + type: part + display_name: mu_bod_vr23 + +mu_bod_vl23 + id: 5490 + type: part + display_name: mu_bod_vl23 + +mu_bod_dr1 + id: 5491 + type: part + display_name: mu_bod_dr1 + +mu_bod_dr24 + id: 5492 + type: part + display_name: mu_bod_dr24 + +mu_bod_dl1 + id: 5493 + type: part + display_name: mu_bod_dl1 + +mu_bod_dl24 + id: 5494 + type: part + display_name: mu_bod_dl24 + +mu_bod_vr24 + id: 5495 + type: part + display_name: mu_bod_vr24 + +int1dl + id: 5496 + type: part + display_name: int1dl + +int9l + id: 5497 + type: part + display_name: int9l + +int8l + id: 5498 + type: part + display_name: int8l + +int7l + id: 5499 + type: part + display_name: int7l + +int6l + id: 5500 + type: part + display_name: int6l + +int5l + id: 5501 + type: part + display_name: int5l + +int4v + id: 5502 + type: part + display_name: int4v + +int3v + id: 5503 + type: part + display_name: int3v + +int2d + id: 5504 + type: part + display_name: int2d + +int1vr + id: 5505 + type: part + display_name: int1vr + +int1vl + id: 5506 + type: part + display_name: int1vl + +int4d + id: 5507 + type: part + display_name: int4d + +int6r + id: 5508 + type: part + display_name: int6r + +int5r + id: 5509 + type: part + display_name: int5r + +int9r + id: 5510 + type: part + display_name: int9r + +int8r + id: 5511 + type: part + display_name: int8r + +int2v + id: 5512 + type: part + display_name: int2v + +int1dr + id: 5513 + type: part + display_name: int1dr + +int7r + id: 5514 + type: part + display_name: int7r + +int3d + id: 5515 + type: part + display_name: int3d + +head_mesodermal_cell + id: 5516 + type: part + display_name: head_mesodermal_cell + +pvm + id: 5517 + type: part + display_name: pvm + +pvdr + id: 5518 + type: part + display_name: pvdr + +pder + id: 5519 + type: part + display_name: pder + +avm + id: 5520 + type: part + display_name: avm + +almr + id: 5521 + type: part + display_name: almr + +pvr + id: 5522 + type: part + display_name: pvr + +plmr + id: 5523 + type: part + display_name: plmr + +phcr + id: 5524 + type: part + display_name: phcr + +phbr + id: 5525 + type: part + display_name: phbr + +phar + id: 5526 + type: part + display_name: phar + +pvdl + id: 5527 + type: part + display_name: pvdl + +il2vr + id: 5528 + type: part + display_name: il2vr + +flpr + id: 5529 + type: part + display_name: flpr + +bagr + id: 5530 + type: part + display_name: bagr + +il2r + id: 5531 + type: part + display_name: il2r + +awcr + id: 5532 + type: part + display_name: awcr + +awbr + id: 5533 + type: part + display_name: awbr + +awar + id: 5534 + type: part + display_name: awar + +askr + id: 5535 + type: part + display_name: askr + +asir + id: 5536 + type: part + display_name: asir + +aser + id: 5537 + type: part + display_name: aser + +ashr + id: 5538 + type: part + display_name: ashr + +adlr + id: 5539 + type: part + display_name: adlr + +adfr + id: 5540 + type: part + display_name: adfr + +ader + id: 5541 + type: part + display_name: ader + +pqr + id: 5542 + type: part + display_name: pqr + +phcl + id: 5543 + type: part + display_name: phcl + +phbl + id: 5544 + type: part + display_name: phbl + +phal + id: 5545 + type: part + display_name: phal + +plml + id: 5546 + type: part + display_name: plml + +pdel + id: 5547 + type: part + display_name: pdel + +alml + id: 5548 + type: part + display_name: alml + +il2vl + id: 5549 + type: part + display_name: il2vl + +cepdr + id: 5550 + type: part + display_name: cepdr + +asgr + id: 5551 + type: part + display_name: asgr + +ollr + id: 5552 + type: part + display_name: ollr + +il2dr + id: 5553 + type: part + display_name: il2dr + +afdr + id: 5554 + type: part + display_name: afdr + +cepvr + id: 5555 + type: part + display_name: cepvr + +asjr + id: 5556 + type: part + display_name: asjr + +aqr + id: 5557 + type: part + display_name: aqr + +il2dl + id: 5558 + type: part + display_name: il2dl + +bagl + id: 5559 + type: part + display_name: bagl + +cepvl + id: 5560 + type: part + display_name: cepvl + +adel + id: 5561 + type: part + display_name: adel + +flpl + id: 5562 + type: part + display_name: flpl + +adll + id: 5563 + type: part + display_name: adll + +ashl + id: 5564 + type: part + display_name: ashl + +awbl + id: 5565 + type: part + display_name: awbl + +afdl + id: 5566 + type: part + display_name: afdl + +adfl + id: 5567 + type: part + display_name: adfl + +asel + id: 5568 + type: part + display_name: asel + +awcl + id: 5569 + type: part + display_name: awcl + +asjl + id: 5570 + type: part + display_name: asjl + +cepdl + id: 5571 + type: part + display_name: cepdl + +asil + id: 5572 + type: part + display_name: asil + +askl + id: 5573 + type: part + display_name: askl + +asgl + id: 5574 + type: part + display_name: asgl + +awal + id: 5575 + type: part + display_name: awal + +olll + id: 5576 + type: part + display_name: olll + +il2l + id: 5577 + type: part + display_name: il2l + +virr + id: 5578 + type: part + display_name: virr + +virl + id: 5579 + type: part + display_name: virl + +vpi3_v + id: 5580 + type: part + display_name: vpi3_v + +vpi3_d + id: 5581 + type: part + display_name: vpi3_d + +vpi2_dl + id: 5582 + type: part + display_name: vpi2_dl + +vpi2_v + id: 5583 + type: part + display_name: vpi2_v + +vpi2_dr + id: 5584 + type: part + display_name: vpi2_dr + +vpi1 + id: 5585 + type: part + display_name: vpi1 + +mu_anal + id: 5586 + type: part + display_name: mu_anal + +y + id: 5587 + type: part + display_name: y + +b + id: 5588 + type: part + display_name: b + +u + id: 5589 + type: part + display_name: u + +f + id: 5590 + type: part + display_name: f + +k + id: 5591 + type: part + display_name: k + +kprime + id: 5592 + type: part + display_name: kprime + +dvc + id: 5593 + type: part + display_name: dvc + +dva + id: 5594 + type: part + display_name: dva + +siavr + id: 5595 + type: part + display_name: siavr + +sdqr + id: 5596 + type: part + display_name: sdqr + +sdql + id: 5597 + type: part + display_name: sdql + +bdur + id: 5598 + type: part + display_name: bdur + +pvnr + id: 5599 + type: part + display_name: pvnr + +pvwr + id: 5600 + type: part + display_name: pvwr + +pvcr + id: 5601 + type: part + display_name: pvcr + +luar + id: 5602 + type: part + display_name: luar + +pvpl + id: 5603 + type: part + display_name: pvpl + +ripr + id: 5604 + type: part + display_name: ripr + +urbr + id: 5605 + type: part + display_name: urbr + +aibr + id: 5606 + type: part + display_name: aibr + +adar + id: 5607 + type: part + display_name: adar + +mi + id: 5608 + type: part + display_name: mi + +i6 + id: 5609 + type: part + display_name: i6 + +i5 + id: 5610 + type: part + display_name: i5 + +i4 + id: 5611 + type: part + display_name: i4 + +i3 + id: 5612 + type: part + display_name: i3 + +i2r + id: 5613 + type: part + display_name: i2r + +i1r + id: 5614 + type: part + display_name: i1r + +pvnl + id: 5615 + type: part + display_name: pvnl + +pvcl + id: 5616 + type: part + display_name: pvcl + +pvql + id: 5617 + type: part + display_name: pvql + +pvt + id: 5618 + type: part + display_name: pvt + +pvpr + id: 5619 + type: part + display_name: pvpr + +pvwl + id: 5620 + type: part + display_name: pvwl + +lual + id: 5621 + type: part + display_name: lual + +pvqr + id: 5622 + type: part + display_name: pvqr + +bdul + id: 5623 + type: part + display_name: bdul + +i1l + id: 5624 + type: part + display_name: i1l + +i2l + id: 5625 + type: part + display_name: i2l + +rivr + id: 5626 + type: part + display_name: rivr + +avjr + id: 5627 + type: part + display_name: avjr + +ricr + id: 5628 + type: part + display_name: ricr + +sibvr + id: 5629 + type: part + display_name: sibvr + +siadr + id: 5630 + type: part + display_name: siadr + +aiar + id: 5631 + type: part + display_name: aiar + +saadl + id: 5632 + type: part + display_name: saadl + +ribr + id: 5633 + type: part + display_name: ribr + +avkr + id: 5634 + type: part + display_name: avkr + +avkl + id: 5635 + type: part + display_name: avkl + +avfl + id: 5636 + type: part + display_name: avfl + +avfr + id: 5637 + type: part + display_name: avfr + +avar + id: 5638 + type: part + display_name: avar + +avbr + id: 5639 + type: part + display_name: avbr + +avdl + id: 5640 + type: part + display_name: avdl + +avbl + id: 5641 + type: part + display_name: avbl + +aval + id: 5642 + type: part + display_name: aval + +avdr + id: 5643 + type: part + display_name: avdr + +saadr + id: 5644 + type: part + display_name: saadr + +rir + id: 5645 + type: part + display_name: rir + +sibdr + id: 5646 + type: part + display_name: sibdr + +aver + id: 5647 + type: part + display_name: aver + +riar + id: 5648 + type: part + display_name: riar + +saavr + id: 5649 + type: part + display_name: saavr + +ainr + id: 5650 + type: part + display_name: ainr + +avhr + id: 5651 + type: part + display_name: avhr + +urxr + id: 5652 + type: part + display_name: urxr + +ris + id: 5653 + type: part + display_name: ris + +ripl + id: 5654 + type: part + display_name: ripl + +urbl + id: 5655 + type: part + display_name: urbl + +adal + id: 5656 + type: part + display_name: adal + +rid + id: 5657 + type: part + display_name: rid + +urxl + id: 5658 + type: part + display_name: urxl + +rivl + id: 5659 + type: part + display_name: rivl + +avhl + id: 5660 + type: part + display_name: avhl + +avjl + id: 5661 + type: part + display_name: avjl + +ainl + id: 5662 + type: part + display_name: ainl + +saavl + id: 5663 + type: part + display_name: saavl + +rial + id: 5664 + type: part + display_name: rial + +avel + id: 5665 + type: part + display_name: avel + +sibdl + id: 5666 + type: part + display_name: sibdl + +ribl + id: 5667 + type: part + display_name: ribl + +aizl + id: 5668 + type: part + display_name: aizl + +ricl + id: 5669 + type: part + display_name: ricl + +aiyr + id: 5670 + type: part + display_name: aiyr + +aimr + id: 5671 + type: part + display_name: aimr + +aibl + id: 5672 + type: part + display_name: aibl + +rigl + id: 5673 + type: part + display_name: rigl + +sabd + id: 5674 + type: part + display_name: sabd + +avg + id: 5675 + type: part + display_name: avg + +rifl + id: 5676 + type: part + display_name: rifl + +rigr + id: 5677 + type: part + display_name: rigr + +rifr + id: 5678 + type: part + display_name: rifr + +sabvr + id: 5679 + type: part + display_name: sabvr + +sabvl + id: 5680 + type: part + display_name: sabvl + +aiml + id: 5681 + type: part + display_name: aiml + +aiyl + id: 5682 + type: part + display_name: aiyl + +siadl + id: 5683 + type: part + display_name: siadl + +aial + id: 5684 + type: part + display_name: aial + +siavl + id: 5685 + type: part + display_name: siavl + +sibvl + id: 5686 + type: part + display_name: sibvl + +rih + id: 5687 + type: part + display_name: rih + +# Layers + +Cuticle + id: 10 + type: group + display_name: Cuticle + layer: yes + hidden: yes + +Organs + id: 11 + type: group + display_name: Organs + layer: yes + hidden: yes + +Muscles + id: 12 + type: group + display_name: Muscles + layer: yes + hidden: yes + +Neurons + id: 13 + type: group + display_name: Neurons + layer: yes + hidden: yes + +# Sub-Layers + +ExcSysLayer + id: 14 + type: sublayer + display_name: ExcSysLayer + sublayer_index: 0 + hidden: yes + +PharynxLayer + id: 15 + type: sublayer + display_name: PharynxLayer + sublayer_index: 1 + hidden: yes + +RepSysLayer + id: 16 + type: sublayer + display_name: RepSysLayer + sublayer_index: 2 + hidden: yes + +IntestineLayer + id: 17 + type: sublayer + display_name: IntestineLayer + sublayer_index: 3 + hidden: yes + +GLRLayer + id: 18 + type: sublayer + display_name: GLRLayer + sublayer_index: 4 + hidden: yes + +RectLayer + id: 19 + type: sublayer + display_name: RectLayer + sublayer_index: 5 + hidden: yes + +MuscleLayer + id: 20 + type: sublayer + display_name: MuscleLayer + sublayer_index: 0 + hidden: yes + +SensNeurLayer + id: 21 + type: sublayer + display_name: SensNeurLayer + sublayer_index: 0 + hidden: yes + +MotNeurLayer + id: 22 + type: sublayer + display_name: MotNeurLayer + sublayer_index: 1 + hidden: yes + +IntNeurLayer + id: 23 + type: sublayer + display_name: IntNeurLayer + sublayer_index: 2 + hidden: yes + +PolyNeurLayer + id: 24 + type: sublayer + display_name: PolyNeurLayer + sublayer_index: 3 + hidden: yes + +SocketLayer + id: 25 + type: sublayer + display_name: SocketLayer + sublayer_index: 4 + hidden: yes + +SheathLayer + id: 26 + type: sublayer + display_name: SheathLayer + sublayer_index: 5 + hidden: yes + +NUFsLayer + id: 27 + type: sublayer + display_name: NUFsLayer + sublayer_index: 6 + hidden: yes + +HypCutLayer + id: 28 + type: sublayer + display_name: HypCutLayer + sublayer_index: 0 + hidden: yes + +# Group + +worm_body + id: 1 + type: group + hidden: yes diff --git a/wormbrowser-appengine/src/main/webapp/no_webgl.html b/wormbrowser-appengine/src/main/webapp/no_webgl.html new file mode 100644 index 0000000..c1539b1 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/no_webgl.html @@ -0,0 +1,21 @@ + + + +WebGL not supported + + + + +WebGL not supported +
+This product uses WebGL, which your browser does not appear to support. Possible reasons: + + + + \ No newline at end of file diff --git a/wormbrowser-appengine/src/main/webapp/scripts/common.js b/wormbrowser-appengine/src/main/webapp/scripts/common.js new file mode 100644 index 0000000..474952d --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/common.js @@ -0,0 +1,606 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Utility functions and miscellaneous items. + */ + +/** + * Debug mode + */ +o3v.LOG_NONE = 0; +o3v.LOG_ERROR = 1; +o3v.LOG_WARNING = 2; +o3v.LOG_INFO = 3; + +o3v.LOG_LEVEL = o3v.LOG_INFO; + +/** + * Basic logging + */ +o3v.log = { + info: function () { + if (o3v.LOG_LEVEL >= o3v.LOG_INFO && window['console'] !== undefined) { + var newArgs = ['INFO: ']; + for (var i = 0; i < arguments.length; i++) { + newArgs[i+1] = arguments[i]; + } + window['console'].log.apply(window['console'], newArgs); + } + }, + warning: function () { + if (o3v.LOG_LEVEL >= o3v.LOG_WARNING && window['console'] !== undefined) { + var newArgs = ['WARNING: ']; + for (var i = 0; i < arguments.length; i++) { + newArgs[i+1] = arguments[i]; + } + window['console'].log.apply(window['console'], newArgs); + } + }, + error: function () { + if (o3v.LOG_LEVEL >= o3v.LOG_ERROR && window['console'] !== undefined) { + var newArgs = ['ERROR: ']; + for (var i = 0; i < arguments.length; i++) { + newArgs[i+1] = arguments[i]; + } + window['console'].log.apply(window['console'], newArgs); + } + } +}; + +/** + * UI settings + */ +o3v.uiSettings = { + ZINDEX_VIEWER: 0, + ZINDEX_MAINUI_STATUS_LOWER: 1, + ZINDEX_MAINUI_STATUS_UPPER: 2, + ZINDEX_MAINUI: 3 +}; + +/** + * Enum for handedness + * @enum {number} + * @private + */ +var HANDEDNESS_ = { + LEFT: 0, + RIGHT: 1 +}; + +/** + * Grows a bounding box to encompass another bounding box. If + * original is undefined, it is created as a copy of addition. + * If original is defined, it's modified in place. + * @param {Array|Float32Array} original Original bounding box. + * @param {Array|Float32Array} addition Bounding box to add to original. + * @return {Array|Float32Array} original (modified in place as well). + */ +o3v.growBBox = function (original, addition) { + if (original === undefined) { + return addition.slice(0); + } else { + if (original[0] > addition[0]) { + original[0] = addition[0]; + } + if (original[1] > addition[1]) { + original[1] = addition[1]; + } + if (original[2] > addition[2]) { + original[2] = addition[2]; + } + if (original[3] < addition[3]) { + original[3] = addition[3]; + } + if (original[4] < addition[4]) { + original[4] = addition[4]; + } + if (original[5] < addition[5]) { + original[5] = addition[5]; + } + return original; + } +}; + +// General utilities. +o3v.util = {}; + +o3v.util.isEmpty = function(obj) { + return (Object.keys(obj).length === 0); +}; + +o3v.util.isArray = function(val) { + return (Object.prototype.toString.call(val) === '[object Array]'); +}; + +o3v.util.cloneObject = function(obj) { + // Shallow copy. For deep, change to $.extend(true, {}, obj). + return $.extend({}, obj); +}; + +o3v.util.extendObject = function(target, var_args) { + return $.extend(target, var_args); +}; + +o3v.util.objectContains = function(obj, val) { + for (var key in obj) { + if (obj[key] == val) { + return true; + } + } + return false; +}; + +o3v.util.getObjectCount = function(obj) { + return Object.keys(obj).length; +}; + +o3v.util.forEach = function(obj, f, opt_obj) { + for (var key in obj) { + f.call(opt_obj, obj[key], key, obj); + } +}; + +o3v.util.createSet = function(var_args) { + var argLength = arguments.length; + if (argLength == 1 && o3v.util.isArray(arguments[0])) { + return o3v.util.createSet.apply(null, arguments[0]); + } + + var rv = {}; + for (var i = 0; i < argLength; i++) { + rv[arguments[i]] = true; + } + return rv; +}; + +o3v.util.setIfUndefined = function(obj, key, value) { + return key in obj ? obj[key] : (obj[key] = value); +}; + +o3v.util.isDef = function(val) { + return typeof val != 'undefined'; +}; + +//////////////////////////////////////////////////////////////////////////////// +// goog.math.Bezier, imported from Google Closure. + +goog = {}; +goog.math = {}; + +/** + * Object representing a cubic bezier curve. + * @param {number} x0 X coordinate of the start point. + * @param {number} y0 Y coordinate of the start point. + * @param {number} x1 X coordinate of the first control point. + * @param {number} y1 Y coordinate of the first control point. + * @param {number} x2 X coordinate of the second control point. + * @param {number} y2 Y coordinate of the second control point. + * @param {number} x3 X coordinate of the end point. + * @param {number} y3 Y coordinate of the end point. + * @constructor + */ +goog.math.Bezier = function(x0, y0, x1, y1, x2, y2, x3, y3) { + /** + * X coordinate of the first point. + * @type {number} + */ + this.x0 = x0; + + /** + * Y coordinate of the first point. + * @type {number} + */ + this.y0 = y0; + + /** + * X coordinate of the first control point. + * @type {number} + */ + this.x1 = x1; + + /** + * Y coordinate of the first control point. + * @type {number} + */ + this.y1 = y1; + + /** + * X coordinate of the second control point. + * @type {number} + */ + this.x2 = x2; + + /** + * Y coordinate of the second control point. + * @type {number} + */ + this.y2 = y2; + + /** + * X coordinate of the end point. + * @type {number} + */ + this.x3 = x3; + + /** + * Y coordinate of the end point. + * @type {number} + */ + this.y3 = y3; +}; + + +/** + * Constant used to approximate ellipses. + * See: http://canvaspaint.org/blog/2006/12/ellipse/ + * @type {number} + */ +goog.math.Bezier.KAPPA = 4 * (Math.sqrt(2) - 1) / 3; + + +/** + * @return {!goog.math.Bezier} A copy of this curve. + */ +goog.math.Bezier.prototype.clone = function() { + return new goog.math.Bezier(this.x0, this.y0, this.x1, this.y1, this.x2, + this.y2, this.x3, this.y3); +}; + + +/** + * Test if the given curve is exactly the same as this one. + * @param {goog.math.Bezier} other The other curve. + * @return {boolean} Whether the given curve is the same as this one. + */ +goog.math.Bezier.prototype.equals = function(other) { + return this.x0 == other.x0 && this.y0 == other.y0 && this.x1 == other.x1 && + this.y1 == other.y1 && this.x2 == other.x2 && this.y2 == other.y2 && + this.x3 == other.x3 && this.y3 == other.y3; +}; + + +/** + * Modifies the curve in place to progress in the opposite direction. + */ +goog.math.Bezier.prototype.flip = function() { + var temp = this.x0; + this.x0 = this.x3; + this.x3 = temp; + temp = this.y0; + this.y0 = this.y3; + this.y3 = temp; + + temp = this.x1; + this.x1 = this.x2; + this.x2 = temp; + temp = this.y1; + this.y1 = this.y2; + this.y2 = temp; +}; + + +/** + * Computes the curve at a point between 0 and 1. + * @param {number} t The point on the curve to find. + * @return {!goog.math.Coordinate} The computed coordinate. + */ +goog.math.Bezier.prototype.getPoint = function(t) { + // Special case start and end + if (t == 0) { + return new goog.math.Coordinate(this.x0, this.y0); + } else if (t == 1) { + return new goog.math.Coordinate(this.x3, this.y3); + } + + // Step one - from 4 points to 3 + var ix0 = goog.math.lerp(this.x0, this.x1, t); + var iy0 = goog.math.lerp(this.y0, this.y1, t); + + var ix1 = goog.math.lerp(this.x1, this.x2, t); + var iy1 = goog.math.lerp(this.y1, this.y2, t); + + var ix2 = goog.math.lerp(this.x2, this.x3, t); + var iy2 = goog.math.lerp(this.y2, this.y3, t); + + // Step two - from 3 points to 2 + ix0 = goog.math.lerp(ix0, ix1, t); + iy0 = goog.math.lerp(iy0, iy1, t); + + ix1 = goog.math.lerp(ix1, ix2, t); + iy1 = goog.math.lerp(iy1, iy2, t); + + // Final step - last point + return new goog.math.Coordinate(goog.math.lerp(ix0, ix1, t), + goog.math.lerp(iy0, iy1, t)); +}; + + +/** + * Changes this curve in place to be the portion of itself from [t, 1]. + * @param {number} t The start of the desired portion of the curve. + */ +goog.math.Bezier.prototype.subdivideLeft = function(t) { + if (t == 1) { + return; + } + + // Step one - from 4 points to 3 + var ix0 = goog.math.lerp(this.x0, this.x1, t); + var iy0 = goog.math.lerp(this.y0, this.y1, t); + + var ix1 = goog.math.lerp(this.x1, this.x2, t); + var iy1 = goog.math.lerp(this.y1, this.y2, t); + + var ix2 = goog.math.lerp(this.x2, this.x3, t); + var iy2 = goog.math.lerp(this.y2, this.y3, t); + + // Collect our new x1 and y1 + this.x1 = ix0; + this.y1 = iy0; + + // Step two - from 3 points to 2 + ix0 = goog.math.lerp(ix0, ix1, t); + iy0 = goog.math.lerp(iy0, iy1, t); + + ix1 = goog.math.lerp(ix1, ix2, t); + iy1 = goog.math.lerp(iy1, iy2, t); + + // Collect our new x2 and y2 + this.x2 = ix0; + this.y2 = iy0; + + // Final step - last point + this.x3 = goog.math.lerp(ix0, ix1, t); + this.y3 = goog.math.lerp(iy0, iy1, t); +}; + + +/** + * Changes this curve in place to be the portion of itself from [0, t]. + * @param {number} t The end of the desired portion of the curve. + */ +goog.math.Bezier.prototype.subdivideRight = function(t) { + this.flip(); + this.subdivideLeft(1 - t); + this.flip(); +}; + + +/** + * Changes this curve in place to be the portion of itself from [s, t]. + * @param {number} s The start of the desired portion of the curve. + * @param {number} t The end of the desired portion of the curve. + */ +goog.math.Bezier.prototype.subdivide = function(s, t) { + this.subdivideRight(s); + this.subdivideLeft((t - s) / (1 - s)); +}; + + +/** + * Computes the position t of a point on the curve given its x coordinate. + * That is, for an input xVal, finds t s.t. getPoint(t).x = xVal. + * As such, the following should always be true up to some small epsilon: + * t ~ solvePositionFromXValue(getPoint(t).x) for t in [0, 1]. + * @param {number} xVal The x coordinate of the point to find on the curve. + * @return {number} The position t. + */ +goog.math.Bezier.prototype.solvePositionFromXValue = function(xVal) { + // Desired precision on the computation. + var epsilon = 1e-6; + + // Initial estimate of t using linear interpolation. + var t = (xVal - this.x0) / (this.x3 - this.x0); + if (t <= 0) { + return 0; + } else if (t >= 1) { + return 1; + } + + // Try gradient descent to solve for t. If it works, it is very fast. + var tMin = 0; + var tMax = 1; + for (var i = 0; i < 8; i++) { + var value = this.getPoint(t).x; + var derivative = (this.getPoint(t + epsilon).x - value) / epsilon; + if (Math.abs(value - xVal) < epsilon) { + return t; + } else if (Math.abs(derivative) < epsilon) { + break; + } else { + if (value < xVal) { + tMin = t; + } else { + tMax = t; + } + t -= (value - xVal) / derivative; + } + } + + // If the gradient descent got stuck in a local minimum, e.g. because + // the derivative was close to 0, use a Dichotomy refinement instead. + // We limit the number of interations to 8. + for (var i = 0; Math.abs(value - xVal) > epsilon && i < 8; i++) { + if (value < xVal) { + tMin = t; + t = (t + tMax) / 2; + } else { + tMax = t; + t = (t + tMin) / 2; + } + value = this.getPoint(t).x; + } + return t; +}; + +/** + * Computes the y coordinate of a point on the curve given its x coordinate. + * @param {number} xVal The x coordinate of the point on the curve. + * @return {number} The y coordinate of the point on the curve. + */ +goog.math.Bezier.prototype.solveYValueFromXValue = function(xVal) { + return this.getPoint(this.solvePositionFromXValue(xVal)).y; +}; + +/** + * Class for representing coordinates and positions. + * @param {number=} opt_x Left, defaults to 0. + * @param {number=} opt_y Top, defaults to 0. + * @constructor + */ +goog.math.Coordinate = function(opt_x, opt_y) { + /** + * X-value + * @type {number} + */ + this.x = o3v.util.isDef(opt_x) ? opt_x : 0; + + /** + * Y-value + * @type {number} + */ + this.y = o3v.util.isDef(opt_y) ? opt_y : 0; +}; + + +/** + * Returns a new copy of the coordinate. + * @return {!goog.math.Coordinate} A clone of this coordinate. + */ +goog.math.Coordinate.prototype.clone = function() { + return new goog.math.Coordinate(this.x, this.y); +}; + + +if (goog.DEBUG) { + /** + * Returns a nice string representing the coordinate. + * @return {string} In the form (50, 73). + */ + goog.math.Coordinate.prototype.toString = function() { + return '(' + this.x + ', ' + this.y + ')'; + }; +} + + +/** + * Compares coordinates for equality. + * @param {goog.math.Coordinate} a A Coordinate. + * @param {goog.math.Coordinate} b A Coordinate. + * @return {boolean} True iff the coordinates are equal, or if both are null. + */ +goog.math.Coordinate.equals = function(a, b) { + if (a == b) { + return true; + } + if (!a || !b) { + return false; + } + return a.x == b.x && a.y == b.y; +}; + + +/** + * Returns the distance between two coordinates. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {number} The distance between {@code a} and {@code b}. + */ +goog.math.Coordinate.distance = function(a, b) { + var dx = a.x - b.x; + var dy = a.y - b.y; + return Math.sqrt(dx * dx + dy * dy); +}; + + +/** + * Returns the squared distance between two coordinates. Squared distances can + * be used for comparisons when the actual value is not required. + * + * Performance note: eliminating the square root is an optimization often used + * in lower-level languages, but the speed difference is not nearly as + * pronounced in JavaScript (only a few percent.) + * + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {number} The squared distance between {@code a} and {@code b}. + */ +goog.math.Coordinate.squaredDistance = function(a, b) { + var dx = a.x - b.x; + var dy = a.y - b.y; + return dx * dx + dy * dy; +}; + + +/** + * Returns the difference between two coordinates as a new + * goog.math.Coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {!goog.math.Coordinate} A Coordinate representing the difference + * between {@code a} and {@code b}. + */ +goog.math.Coordinate.difference = function(a, b) { + return new goog.math.Coordinate(a.x - b.x, a.y - b.y); +}; + + +/** + * Returns the sum of two coordinates as a new goog.math.Coordinate. + * @param {!goog.math.Coordinate} a A Coordinate. + * @param {!goog.math.Coordinate} b A Coordinate. + * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two + * coordinates. + */ +goog.math.Coordinate.sum = function(a, b) { + return new goog.math.Coordinate(a.x + b.x, a.y + b.y); +}; + +/** + * Performs linear interpolation between values a and b. Returns the value + * between a and b proportional to x (when x is between 0 and 1. When x is + * outside this range, the return value is a linear extrapolation). + * @param {number} a + * @param {number} b + * @param {number} x The proportion between a and b + * @return {number} The interpolated value between a and b + */ +goog.math.lerp = function(a, b, x) { + return a + x * (b - a); +}; + +// Shim layer with setTimeout fallback, adapted from: +// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +window.requestAnimFrame = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback, unused_dom) { + window.setTimeout(callback, 16); // 16ms ~ 60Hz + }; + +// XMLHttpRequest stuff for loader.js. +function getHttpRequest(url, onload, opt_onprogress) { + var req = new XMLHttpRequest(); + req.onload = function(e) { onload(req, e); }; + if (opt_onprogress) { + req.onprogress = function(e) { + opt_onprogress(req, e); + }; + } + req.open('GET', url, true); + req.send(null); +} diff --git a/wormbrowser-appengine/src/main/webapp/scripts/content.js b/wormbrowser-appengine/src/main/webapp/scripts/content.js new file mode 100644 index 0000000..41deaf8 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/content.js @@ -0,0 +1,106 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview This object keeps track of the model currently loaded. + * On initialization, loads model 0 in o3v.MODELS. + */ +o3v.ContentManager = function() { + this.models_ = o3v.MODELS; + this.metadata_ = null; + this.currentModel_ = -1; // Force it to cycle to the first model. + + // metadata caches. + this.scriptsLoaded_ = {}; // e.g. adult_female.js + this.metadataLoaded_ = {}; // e.g. entity_metadata.json +}; + +o3v.ContentManager.prototype.nextModel = function(loadModelInfoCallback, + loadMeshCallback, + loadModelCallback, + loadMetadataCallback) { + this.currentModel_ = (this.currentModel_ + 1) % this.models_.length; + + loadModelInfoCallback(this.models_[this.currentModel_]); + + this.loadModel_(this.models_[this.currentModel_], + loadMeshCallback, loadModelCallback, loadMetadataCallback); +}; + +o3v.ContentManager.prototype.getCurrentModelInfo = function() { + return this.models_[this.currentModel_]; +}; + +o3v.ContentManager.prototype.loadModel_ = + function(modelInfo, + loadMeshCallback, // After each mesh + loadModelCallback, // After all meshes + loadMetadataCallback // After metadata + ) { + // First, load javascript. + var scriptPath = modelInfo.modelPath + modelInfo.scriptName; + if (this.scriptsLoaded_[scriptPath]) { + this.loadModelAfterScript_(modelInfo, loadMeshCallback, loadModelCallback, + loadMetadataCallback); + } else { + $.getScript(scriptPath, function() { + this.scriptsLoaded_[scriptPath] = true; + this.loadModelAfterScript_(modelInfo, loadMeshCallback, + loadModelCallback, + loadMetadataCallback); + }.bind(this)); + } +}; + +o3v.ContentManager.prototype.loadModelAfterScript_ = + function(modelInfo, + loadMeshCallback, // After each mesh + loadModelCallback, // After all meshes + loadMetadataCallback // After metadata + ) { + // Call out to webgl loader. + downloadModel(modelInfo.modelPath, modelInfo.name, loadMeshCallback, + loadModelCallback); + + // Load metadata. + this.loadMetadata_(modelInfo.modelPath + modelInfo.metadataFile, + MODELS[modelInfo.name], + loadMetadataCallback); +}; + +o3v.ContentManager.prototype.loadMetadata_ = function(metadataPath, + modelMetadata, + callback) { + var cached_metadata = this.metadataLoaded_[metadataPath]; + if (cached_metadata) { + this.metadata_ = cached_metadata; + callback(); + } else { + var self = this; + + function onload(req) { + // TODO: error handling. + var metadata = new o3v.EntityMetadata(JSON.parse(req.responseText)); + self.metadata_ = new o3v.EntityModel(modelMetadata, metadata); + self.metadataLoaded_[metadataPath] = this.metadata_; + callback(); + }; + + getHttpRequest(metadataPath, onload); + } +}; + +o3v.ContentManager.prototype.getMetadata = function() { + return this.metadata_; +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/entities.js b/wormbrowser-appengine/src/main/webapp/scripts/entities.js new file mode 100644 index 0000000..018805f --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/entities.js @@ -0,0 +1,1130 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Entity database. + */ +/** + * Temporary storage for information about entities and their relationships. + * @param {Object} json Json data from server. + * @constructor + */ +o3v.EntityMetadata = function (json) { + /** + * Logger. + * @type {Object.} + * @private + */ + this.log_ = o3v.log; + + /** + * Map of entity id to entity metadata. + * @type {Object.} + * @private + */ + this.entities_ = {}; + + /** + * Map of external id to id. + * @type {Object.} + * @private + */ + this.externalIdToId_ = {}; + + /** + * Set of layer ids. + * @type {Object.} + * @private + */ + this.layers_ = {}; + + /** + * Map of layer name to entity id. + * @type {Object.} + * @private + */ + this.layerNameToId_ = {}; + + /** + * Sublayers: indexed by layer entity id, array of arrays of entity ids. + * @type {Object.>>} + * @private + */ + this.sublayers_ = {}; + + /** + * Symmetry information - pair id to symmetry info. + * @type {Object.} + * @private + */ + this.symmetries_ = {}; + + /** + * Set of entity ids that are hidden from search and selection. + * @type {Object.} + * @private + */ + this.hidden_ = {}; + + this.loadEntities_(json); + this.loadDag_(json); + this.loadLayers_(json); + + this.log_.info('loaded entity metadata: ', json); +}; + + +/** + * Generates a readable name from an external id. + * This badly needs to be moved to the pipeline. + * @param {string} stringId Id (e.g. 'r_lower_subclavian_artery'). + * @return {string} Name (e.g. 'lower subclavian artery'). + * @private + */ +o3v.makeName = function (stringId) { + return stringId.replace(/_/g, ' ').replace(/^r /, '').replace(/^l /, ''); +}; + +/** + * Loads entity data from json. + * @param {Object} json Json data from server. + * @private + */ +o3v.EntityMetadata.prototype.loadEntities_ = function (json) { + // Load leafs. + json['leafs'].forEach( + function (entityInfo) { + this.loadEntity_(entityInfo, true); + }, this); + + // Load non-leafs. + json['nodes'].forEach( + function (entityInfo) { + this.loadEntity_(entityInfo, false); + }, this); + + // Load hidden. + json['hidden'].forEach( + function (entityId) { + this.hidden_[entityId] = true; + }, this); + + // Load symmetries. + json['symmetries'].forEach(this.computeSymmetryObject_, this); + + // Load names. + /** + * Set of entities with overridden names. + * @type {Object.} + * @private + */ + this.entitiesWithOverriddenNames_ = {}; + json['names'].forEach(this.computeName_, this); +}; + +/** + * Loads one entity. + * @param {Array.} entityInfo Data about entity from json. + * @param {boolean} isLeaf True if this is a leaf entity. + * @private + */ +o3v.EntityMetadata.prototype.loadEntity_ = function (entityInfo, isLeaf) { + var entityId = +entityInfo[0]; + var externalId = '' + entityInfo[1]; + // TODO(dkogan): This logic needs to move into the data pipeline. + var entityNames = [o3v.makeName(externalId)]; + var entity = {}; + entity.externalId = externalId; + entity.names = entityNames; + entity.parentIds = {}; + + this.entities_[entityId] = entity; + if (isLeaf) { // We don't want the externalid -> id map for nonleafs + this.externalIdToId_[externalId] = entityId; + } +}; + +/** + * Loads the dag of parent/child relationships between entities. + * @param {Object} json Json data from server. + * @private + */ +o3v.EntityMetadata.prototype.loadDag_ = function (json) { + json['dag'].forEach( + function (groupInfo) { + var parentId = groupInfo[0]; + var childIds = groupInfo[1]; + // All children are under one parent id. + this.entities_[parentId].childIds = o3v.util.createSet(childIds); + childIds.forEach( + function (childId) { + this.entities_[childId].parentIds[parentId] = true; + }, this); + }, this); +}; + +/** + * Loads layer and sublayer data. + * @param {Object} json Json data from server. + * @private + */ +o3v.EntityMetadata.prototype.loadLayers_ = function (json) { + // Load layers. + json['layers'].forEach( + function (layerId) { + this.layers_[layerId] = true; + this.layerNameToId_[this.getEntity(layerId).name] = layerId; + }, this); + + var entitiesAccountedFor = {}; + + // Load sublayers. + // this.subLayers = Object.>> + // layerId -> [ [entityId, entityId], [entityId, entityId] ...] + // The sublayers are sorted from innermost to outermost. + json['sublayers'].forEach( + function (layer) { + var layerId = layer[0]; + var sublayers = layer[1]; + var sublayerArray = []; + sublayers.forEach( + function (sublayer) { + var depth = sublayer[0]; + var entityIds = sublayer[1]; + sublayerArray[depth] = []; + entityIds.forEach( + function (entityId) { + sublayerArray[depth].push(entityId); + entitiesAccountedFor[entityId] = true; + }, this); + }, this); + this.sublayers_[layerId] = sublayerArray; + }, this); + + // If, for some reason, there are sublayers with gaps, fill in those + // gaps with empty sets. + o3v.util.forEach(this.sublayers_, function (sublayerArray) { + for (var i = 0; i < sublayerArray.length; i++) { + if (sublayerArray[i] === undefined) { + sublayerArray[i] = []; + } + } + }, this); + + // Complete sublayers by calculating any leftover entities + // and putting them in the default (top) layer. + o3v.util.forEach(this.layers_, function (unused_true, layerId) { + if (this.sublayers_[layerId] === undefined) { + this.sublayers_[layerId] = []; + } + var sublayerArray = this.sublayers_[layerId]; + sublayerArray[sublayerArray.length] = []; + }, this); + o3v.util.forEach( + this.entities_, + function(entity, entityId) { + // If this a new leaf entity, it needs to be assigned to a layer. + if (entitiesAccountedFor[entityId] === undefined && + entity.childIds === undefined) { + var layerId = this.getLayerId(entityId); + if (!layerId) { + this.log_.warning('Failed to find layer for leaf entity ', + entityId, ' ', entity.names[0]); + } else { + var sublayerArray = this.sublayers_[layerId]; + sublayerArray[sublayerArray.length - 1].push( + parseInt(entityId)); + } + } + }, this); +}; + +/** + * Gets the layer id of an entity. + * @param {number} The entity id. + * @return {number} The layer id or 0 if none. + */ +o3v.EntityMetadata.prototype.getLayerId = function(entityId) { + var entity = this.entities_[entityId]; + var layerId = 0; + // Inefficient (because no short-circuiting) but easy. + o3v.util.forEach( + entity.parentIds, + function(true_unused, parentId) { + if (this.layers_[parentId] !== undefined) { + layerId = parentId; + } else { + var parentLayerId = this.getLayerId(parentId); + if (parentLayerId != 0) { + layerId = parentLayerId; + } + } + }, this); + return layerId; +}; + +/** + * Maps an external id to an internal id. + * @param {string} externalId External id. + * @return {number} The internal id. + */ +o3v.EntityMetadata.prototype.externalIdToId = function (externalId) { + // TODO(dkogan): This lower case should not be necessary once the pipeline + // does the right thing. + return this.externalIdToId_[externalId.toLowerCase()]; +}; + +/** + * Gets an entity object by id. + * @param {number} entityId The id of the entity. + * @return {Object} The entity. + */ +o3v.EntityMetadata.prototype.getEntity = function (entityId) { + return this.entities_[entityId]; +}; + +/** + * Gets the layers. + * @return {Object.} Set of layer entity ids. + */ +o3v.EntityMetadata.prototype.getLayers = function () { + return this.layers_; +}; + +/** + * Gets the sublayers. See definition of EntityMetadata.sublayers_ for + * structure explanation. + * @return {Object.>>} Sublayer object. + */ +o3v.EntityMetadata.prototype.getSublayers = function () { + return this.sublayers_; +}; + +/** + * Gets symmetry information. + * @return {Object.} Map of pair id to symmetry info. + */ +o3v.EntityMetadata.prototype.getSymmetries = function () { + return this.symmetries_; +}; + +/** + * Gets the hidden entities. + * @return {Object.} Set of hidden entity ids. + */ +o3v.EntityMetadata.prototype.getHidden = function () { + return this.hidden_; +}; + +/** + * Computes and stores a single symmetry object. + * This sets this.symmetries_. + * @param {Array.} symmetryJson Json data for the symmetry. + * Structure: [, , , ]. + * @private + */ +o3v.EntityMetadata.prototype.computeSymmetryObject_ = function (symmetryJson) { + var pairId = symmetryJson[0]; + var symmetryObj = {}; + symmetryObj.childIds = []; + symmetryObj.childIds[HANDEDNESS_.LEFT] = symmetryJson[1]; + symmetryObj.childIds[HANDEDNESS_.RIGHT] = symmetryJson[2]; + symmetryObj.singularName = o3v.makeName('' + symmetryJson[3]); + + this.symmetries_[pairId] = symmetryObj; +}; + +/** + * Stores a name associated with an entity. + * @param {Array.} nameTuple Tuple of (entityId, name). + * @private + */ +o3v.EntityMetadata.prototype.computeName_ = function (nameTuple) { + var entityId = +nameTuple[0]; + var name = nameTuple[1]; + if (!this.entitiesWithOverriddenNames_[entityId]) { + // First override clobbers existing name. + this.entities_[entityId].names = [name]; + this.entitiesWithOverriddenNames_[entityId] = true; + } else { + this.entities_[entityId].names.push(name); + } +}; + + +/** + * Storage for entity information associated with a particular model. + * @param {Object} json Json data from server for this model. + * @param {o3v.EntityMetadata} metadata Global metadata. + * @constructor + */ +o3v.EntityModel = function (json, metadata) { + // TODO(wonchun): This should be constructed out of models, not out of json. + // TODO(dkogan): Much of this code needs to be pushed back earlier into the + // data pipeline. The symmetry code is especially bad. + this.log_ = o3v.log; + + /** + * Map of entity id to entity metadata. + * @type {Object.} + * @private + */ + this.entities_ = {}; + + /** + * Map of external id to id. + * @type {Object.} + * @private + */ + this.externalIdToId_ = {}; + + /** + * Map of search term to array of entity ids. + * @type {Object.>} + * @private + */ + this.searchToEntityIds_ = {}; + + /** + * Matcher for search. + * @type {Object} + * @private + */ + this.searchMatcher_ = null; + + /** + * Root of the entity DAG (there must only be one). + * @type {number} + * @private + */ + this.rootId_; + + /** + * Array of layer names. + * @type Array. + * @private + */ + this.layerNames_ = []; + + /** + * Map of layer name to entity id. + * @type {Object.} + * @private + */ + this.layerNameToId_ = {}; + + /** + * Set of entity ids that are unselectable. + * @type {Object.} + * @private + */ + this.unselectable_ = o3v.util.cloneObject(metadata.getHidden()); + + this.loadLeafEntities_(json, metadata); + this.nonSearchableEntityIds_ = o3v.util.cloneObject(metadata.getHidden()); + this.computeDagAndSymmetries_(metadata); + this.computeRoot_(); + this.computeSplits_(); + this.computeLayers_(metadata); + this.computeSearches_(metadata); + + /** + * Sublayers: indexed by layer entity id, array of arrays of entity ids. + * @type {Object.>>}view + * @private + */ + this.sublayers_ = this.loadSublayers_(metadata.getSublayers()); +}; + +/** + * Maximum number of entities into which a group entity is allowed to split. If + * it's not possible to split under this number, entity is considered + * unsplittable. + * @type {number} + * @const + * @private + */ +o3v.EntityModel.MAX_SPLIT_COUNT_ = 25; + +/** + * Loads sublayers from metadata. + * This is just postprocessing to remove any entities not in this model. + * @param {Object.>>} sublayers + * @return {Object.>>} sublayers + */ +o3v.EntityModel.prototype.loadSublayers_ = function(sublayers) { + + var newSublayers = {}; + + o3v.util.forEach( + sublayers, + function(sublayer, layerId) { + newSublayers[layerId] = []; + sublayer.forEach( + function(sublayerArray) { + newSublayers[layerId][newSublayers[layerId].length] = []; + sublayerArray.forEach( + function(entityId) { + if (this.entities_[entityId] !== undefined) { + newSublayers[layerId][newSublayers[layerId].length - 1] + .push(entityId); + } + }, this); + }, this); + }, this); + + return newSublayers; +}; + +/** + * Loads leaf entities from json and metadata. + * This sets this.entities_ and this.externalIdToId_ for leaf entities. + * @param {Object} json Json data for this model. + * @param {o3v.EntityMetadata} metadata Overall metadata. + * @private + */ +o3v.EntityModel.prototype.loadLeafEntities_ = function (json, metadata) { + // Generate list of initial entities. + for (var url in json.urls) { + var urlItems = json.urls[url].length; + for (var i = 0; i < urlItems; ++i) { + json.urls[url][i].names.forEach( + function(externalId) { + var entityId = metadata.externalIdToId(externalId); + var entityMetadata = metadata.getEntity(entityId); + if (!entityId) { + this.log_.error('Missing leaf geometry ', externalId, + ' in metadata.'); + } else { + var entity = {}; + entity.name = entityMetadata.names[0]; + // TODO(dkogan): This field only used for symmetry calculation - + // really should move that to the data pipeline somewhere as a + // boolean. + entity.externalId = externalId; + + // TODO(dkogan): Make parents & children just pointers. + entity.parentIds = entityMetadata.parentIds; + + this.entities_[entityId] = entity; + this.externalIdToId_[externalId] = entityId; + } + }, this); + } + } +}; + +/** + * Computes the entity hierarchy DAG and fills in any symmetries. The DAG + * represents how entities and groups of entities relate to one another: e.g. + * 'cervical vertebrae' belong to the groups 'spine' and 'skeleton'. + * This sets this.entities_ for groups, and sets parentIds and childIds on the + * entities. + * @param {o3v.EntityMetadata} metadata Overall metadata. + * @private + */ +o3v.EntityModel.prototype.computeDagAndSymmetries_ = function (metadata) { + var symmetries = metadata.getSymmetries(); + + // Generate a lookup table for group entities which known to be symmetric. + // The left and right children of group entities are generated in the + // pipeline. + var entityIdToHandedness = {}; + o3v.util.forEach(symmetries, function (pair, pairId) { + o3v.util.forEach(HANDEDNESS_, function (handedness) { + var childId = pair.childIds[handedness]; + this.nonSearchableEntityIds_[childId] = true; + entityIdToHandedness[childId] = handedness; + }, this); + }, this); + + // Function to get handedness given an entity. Group entity handedness is + // known from metadata (entityIdToHandedness); leaf entity handedness in + // determined by the prefix of the external id. + var getHandedness = function (entityId, entity) { + if (o3v.util.objectContains(entityIdToHandedness, entityId)) { + return entityIdToHandedness[entityId]; + } else if (entity.externalId && entity.externalId.match(/^l_/i)) { + return HANDEDNESS_.LEFT; + } else if (entity.externalId && entity.externalId.match(/^r_/i)) { + return HANDEDNESS_.RIGHT; + } else { + this.log_.error('paired entity of unknown handedness ', entityId, + ' ', entity.name); + } + }; + + // Queue of entities to process. Every entity is processed to set the parent + // child connections. + // Any entity created in the process of dag generation or symmetry + // generation gets added to the queue and processed in turn. + var queue = Object.keys(this.entities_).map( + function (entityId) { + return +entityId; + }); + while (queue.length) { + var childId = queue.shift(); + var child = this.entities_[childId]; + var modifiedParentIds = {}; // Updated parent ids for this child. + for (var parentId in child.parentIds) { + parentId = +parentId; + // If no entity for parentId exists, create an entity. This is how the + // DAG is grown (from the bottom up). + if (!this.entities_[parentId]) { + var parentMetadata = metadata.getEntity(parentId); + var parent = {}; + parent.name = parentMetadata.names[0]; + parent.parentIds = parentMetadata.parentIds; + parent.childIds = o3v.util.createSet(); + this.entities_[parentId] = parent; + if (symmetries[parentId]) { + // If parent is symmetric, create its left and right children along + // with it. One of these children becomes the parent of the child + // entity. For example 'left thumb' becomes the child of 'hand' and + // 'left hand', and 'left hand' is the child of 'hand'. + var symmetry = symmetries[parentId]; + o3v.util.forEach(HANDEDNESS_, function (handedness) { + var subParentId = symmetry.childIds[handedness]; + var subParent = {}; + subParent.name = symmetry.singularName; + subParent.parentIds = o3v.util.createSet(); + subParent.parentIds[parentId] = true; + subParent.childIds = o3v.util.createSet(); + + parent.childIds[subParentId] = true; + + this.entities_[subParentId] = subParent; + + // New subParent needs to be processed. + queue.push(subParentId); + }, this); + } + // New parent needs to be processed. + queue.push(parentId); + } + // Now that the parent is guaranteed to exist, hook up the child to it. + var parent = this.entities_[parentId]; + if (symmetries[parentId] && !parent.childIds[childId]) { + // This is the child of a symmetric entity. + if (symmetries[childId]) { + // This entity itself is symmetric, so the following connections + // need to be made: + // a -> b + // a -> left_a * done prior to this code executing + // a -> right_a * done prior to this code executing + // b -> left_b * done prior to this code executing + // b -> right_b * done prior to this code executing + // left_a -> left_b + // right_a -> right_b + var parentSymmetry = symmetries[parentId]; + var childSymmetry = symmetries[childId]; + + // a -> b + modifiedParentIds[parentId] = true; + parent.childIds[childId] = true; + + // left_a -> left_b && right_a -> right_b + o3v.util.forEach(HANDEDNESS_, function (handedness) { + var subParentId = parentSymmetry.childIds[handedness]; + var subChildId = childSymmetry.childIds[handedness]; + var subParent = this.entities_[subParentId]; + var subChild = this.entities_[subChildId]; + subParent.childIds[subChildId] = true; + subChild.parentIds[subParentId] = true; + }, this); + } else { + // This entity is not symmetric, which means it + // belongs under either the left or right child of its parent. + var handedness = getHandedness(childId, child); + var symmetry = symmetries[parentId]; + var subParentId = symmetry.childIds[handedness]; + var subParent = this.entities_[subParentId]; + + if (subParent) { + subParent.childIds[childId] = true; + modifiedParentIds[subParentId] = true; + } else { + this.log_.error('no subparent for ', parent.name, + ' -> ', child.name); + } + } + } else { + // Regular parent->child; not symmetric. + parent.childIds[childId] = true; + modifiedParentIds[parentId] = true; + } + } + // Incorporate changes due to symmetries. + child.parentIds = modifiedParentIds; + } +}; + +/** + * Computes the root entity and verifies there is only one. + * This sets this.rootId_. + * @private + */ +o3v.EntityModel.prototype.computeRoot_ = function () { + // Compute root node. + o3v.util.forEach( + this.entities_, function (entity, entityId) { + if (o3v.util.isEmpty(entity.parentIds)) { + if (!this.rootId_) { + this.rootId_ = entityId; + } else { + this.log_.error('MULTIPLE ROOTS', this.rootId_, ' ', entityId, + ' ', this.entities_[this.rootId_].name, + ' ', this.entities_[entityId].name); + } + } + }, this); +}; + +/** + * Computes bounding boxes and their centers for all entities. + * This sets bbox and ctr on all the entities. + * This must get called before bboxes are read. + * @param {Object.> leafBboxesByExternalId + * @private + */ +o3v.EntityModel.prototype.computeBboxes = function (leafBboxesByExternalId) { + var leafIds = this.getLeafIds(this.rootId_); + o3v.util.forEach( + leafIds, + function(unused_true, entityId) { + var entity = this.entities_[entityId]; + entity.bbox = leafBboxesByExternalId[entity.externalId]; + }, this); + + var dirty = leafIds; // dirty = need to propagate change up + var queue = Object.keys(leafIds); + while (queue.length) { + var nodeId = queue.shift(); + if (dirty[nodeId]) { + delete dirty[nodeId]; + var node = this.entities_[nodeId]; + o3v.util.forEach( + node.parentIds, + function (unused_true, parentId) { + var parent = this.entities_[parentId]; + if (node.bbox !== undefined) { + parent.bbox = o3v.growBBox(parent.bbox, node.bbox); + dirty[parentId] = true; + queue.push(parentId); + } else { + o3v.log.error('error adding ', node.name, ' to ', parent.name); + } + }, this); + } + } + + // Compute bbox centers. + o3v.util.forEach( + this.entities_, + function (entity) { + if (entity.bbox !== undefined) { + entity.ctr = []; + entity.ctr[0] = 0.5 * (entity.bbox[0] + entity.bbox[3]); + entity.ctr[1] = 0.5 * (entity.bbox[1] + entity.bbox[4]); + entity.ctr[2] = 0.5 * (entity.bbox[2] + entity.bbox[5]); + } else { + o3v.log.error('no bbox or center for entity', entity); + } + }); +}; + +/** + * Computes the set of entityIds that best splits this group entity. It returns + * null if the entity is unsplittable, or if it's impossible to split it into + * fewer than MAX_SPLIT_COUNT_ subentities. + * Note: Both the entity and entity id are passed in to simplify the recursion. + * @param {Object} entity Entity. + * @param {number} entityId Entity id. + * @return {Object.?} Set of entity ids. + * @private + */ +o3v.EntityModel.prototype.computeOneSplit_ = function (entity, entityId) { + if (!entity.childIds) { + return null; + } + + var split = {}; + + // If this is a synonym, delegate. + if (o3v.util.getObjectCount(entity.childIds) == 1) { + var childId = +(Object.keys(entity.childIds)[0]); + return this.computeOneSplit_(this.getEntity(childId), childId); + } + + var leafIds = this.getLeafIds(entityId); + + // Generate child groups. + var childGroupIdToGroupLeafIds = {}; + for (var childId in entity.childIds) { + if (!this.unselectable_[childId]) { + var childLeafIds = this.getLeafIds(+childId); + if (childLeafIds && o3v.util.getObjectCount(childLeafIds) > 1) { + childGroupIdToGroupLeafIds[childId] = childLeafIds; + } + } + } + + // Sort child groups by number of subelements. + var childGroupIds = Object.keys(childGroupIdToGroupLeafIds); + childGroupIds.sort(function (a, b) { + return (o3v.util.getObjectCount(childGroupIdToGroupLeafIds[b]) - o3v.util.getObjectCount(childGroupIdToGroupLeafIds[a])); + }); + + // Add useful child groups to split. + childGroupIds.forEach( + function (childGroupId) { + var useful = false; + var childLeafIds = childGroupIdToGroupLeafIds[childGroupId]; + for (var childLeafId in childLeafIds) { + if (leafIds[childLeafId]) { + useful = true; + break; + } + } + if (useful) { + split[childGroupId] = true; + for (var childLeafId in childLeafIds) { + delete leafIds[childLeafId]; + } + } + }); + + // Add any individual leafs unaccounted for. + for (var leafId in leafIds) { + if (!this.unselectable_[leafId]) { + split[leafId] = true; + } + } + + if (o3v.util.getObjectCount(split) <= 1) { + // Leaf entity or group - unsplittable. + return null; + } else if (o3v.util.getObjectCount(split) <= o3v.EntityModel.MAX_SPLIT_COUNT_) { + return split; + } else { + this.log_.warning('entity ', entity.name,' splits into too many: ', + o3v.util.getObjectCount(split), ' ', split); + if (o3v.debug) { + return split; + } else { + return null; + } + } +}; + +/** + * Computes the best split (where possible) for group entities. + * This sets split_ on this.entities_. + * @private + */ +o3v.EntityModel.prototype.computeSplits_ = function () { + // TODO(dkogan): This needs to go into the pipeline, but requires that + // the pipeline be model-specific. + o3v.util.forEach(this.entities_, function (entity, entityId) { + if (!this.unselectable_[entityId]) { + var split = this.computeOneSplit_(entity, entityId); + if (split) { + entity.split_ = split; + } + } + }, this); +}; + +/** + * Propagates layer information down to leaf entities. If entityId is leaf, the + * layer is set on that leaf. Otherwise, the function is called recursively on + * all the entity's children. + * @param {number} layerId Entity id of the layer to propagate. + * @param {number} entityId Entity id to propagate through. + * @private + */ +o3v.EntityModel.prototype.propagateLayerDown_ = function (layerId, entityId) { + var entity = this.entities_[entityId]; + if (!entity.layers) { + entity.layers = {}; + } + if (!entity.childIds) { + entity.layers[layerId] = true; + } else { + // TODO(dkogan): Implement without recursion. Should be okay for now. + for (var childId in entity.childIds) { + this.propagateLayerDown_(layerId, +childId); + } + } +}; + +/** + * Propagates layer information up through the tree. The layer is set on both + * the current entity, and all its ancestors (recursively). + * @param {number} layerId Entity id of the layer to propagate. + * @param {number} entityId Entity id to propagate through. + * @private + */ +o3v.EntityModel.prototype.propagateLayerUp_ = function (layerId, entityId) { + var entity = this.entities_[entityId]; + if (!entity.layers) { + entity.layers = {}; + } + entity.layers[layerId] = true; + // TODO(dkogan): Implement without recursion. Should be okay for now. + for (var parentId in entity.parentIds) { + this.propagateLayerUp_(layerId, +parentId); + } +}; + +/** + * Computes layer information on all entities. Leaf entities are analogous + * to render groups, and must be in exactly one layer. All other entities are + * considered to be in every layer in which one of their children is. Thus, + * the root entity is in every layer, and 'elbow' may be in 'muscle' and + * 'skeleton' layers. + * This sets layers on this.entities_. + * @param {o3v.EntityMetadata} metadata Metadata. + * @private + */ +o3v.EntityModel.prototype.computeLayers_ = function (metadata) { + // Compute initial layers. + Object.keys(metadata.getLayers()).forEach( + function (layerId) { + // Use external ids for layers, not names. + // TODO(dkogan): We should split up layers and groups + // to avoid this kind of hack. + if (this.entities_[layerId]) { + var layerName = metadata.getEntity(layerId).externalId; + this.layerNames_.push(layerName); + this.entities_[layerId].externalId = layerName; + this.layerNameToId_[layerName] = layerId; + } + }, this); + + // Pass layer info to the child nodes. + Object.keys(metadata.getLayers()).forEach( + function (layerId) { + if (this.entities_[layerId]) { + this.propagateLayerDown_(layerId, layerId); + } + }, this); + + // Sanity check - any leaf entity must be in exactly one layer. + o3v.util.forEach(this.entities_, function (entity) { + if (!entity.childIds && (!entity.layers || o3v.util.getObjectCount(entity.layers) != 1)) { + this.log_.error('leaf entity not in one layer: ', entity.name); + } + }, this); + + // Propagate layer info up through the tree. + o3v.util.forEach(this.entities_, function (entity, entityId) { + if (!entity.childIds) { + this.propagateLayerUp_( + Object.keys(entity.layers)[0], entityId); + } + }, this); + + // Turn layers into arrays for easier processing. + o3v.util.forEach( + this.entities_, function (entity) { + if (!o3v.util.isEmpty(entity.layers)) { + entity.layers = Object.keys(entity.layers); + } + }); +}; + +/** + * Computes layer information on all entities. Leaf entities are analogous + * to render groups, and must be in exactly one layer. All other entities are + * considered to be in every layer in which one of their children is. Thus, + * the root entity is in every layer, and 'elbow' may be in 'muscle' and + * 'skeleton' layers. + * This sets this.searchMatcher_ and this.searchToEntityIds_. + * @param {o3v.EntityMetadata} metadata Metadata. + * @private + */ +o3v.EntityModel.prototype.computeSearches_ = function (metadata) { + var symmetries = metadata.getSymmetries(); + + // Compute search table. + for (var entityId in this.entities_) { + entityId = +entityId; + if (!this.nonSearchableEntityIds_[entityId]) { + var names = metadata.getEntity(entityId).names.slice(0); + // Use singular form as the primarywhen searching, for aesthetics. + if (symmetries[entityId]) { + names[0] = symmetries[entityId].singularName; + } + // TODO(dkogan): Expand this to be able to handle 'left lung'. + names.forEach( + function (name) { + o3v.util.setIfUndefined(this.searchToEntityIds_, name, []); + this.searchToEntityIds_[name].push(entityId); + }, this); + } + } + + var searches = Object.keys(this.searchToEntityIds_); + searches.sort(function (a, b) { + return a.length - b.length; + }); + + this.autocompleteList_ = searches; +}; + +/** + * Get a selectable entity by traversing the DAG up. This function tries to find + * the smallest group of entities that includes the current entity, and is + * selectable. In many cases, this is just the current entity. Note that no + * guarantee is made about whether this is actually the smallst group - this + * function is heuristical. + * @param {number} entityId Entity id to start from. + * @return {number} Entity id of the group. + * @private + */ +// TODO(dkogan): Extend this function to generically unexplode entities. +// TODO(dkogan): Move this calculation into the pipeline. +o3v.EntityModel.prototype.getSelectable_ = function (entityId) { + if (!this.unselectable_[entityId]) { + return entityId; + } else { + // Recurse on parent id with fewest children. + var parentIds = Object.keys(this.entities_[entityId].parentIds); + + var minCount = o3v.util.getObjectCount(this.getRootEntity().childIds) + 1; + var minParentId = -1; + parentIds.forEach( + function (parentId) { + var parent = this.entities_[parentId]; + var count = o3v.util.getObjectCount(parent.childIds); + if (count < minCount) { + minCount = count; + minParentId = parentId; + } + }, this); + if (minParentId == -1) { + this.log_.error('Unable to find entity id under click.'); + return this.rootId_; + } else { + return this.getSelectable_(minParentId); + } + } +}; + +/** + * Maps an external id to an internal id. + * @param {string} externalId External id. + * @return {number} The internal id. + */ +o3v.EntityModel.prototype.externalIdToId = function (externalId) { + return this.getSelectable_(this.externalIdToId_[externalId]); +}; + +/** + * Gets an entity object by id. + * @param {number} entityId The id of the entity. + * @return {Object} The entity. + */ +o3v.EntityModel.prototype.getEntity = function (entityId) { + return this.entities_[entityId]; +}; + +/** + * Gets the root entity. (Entity, not just entity id.) + * @return {Object} The root entity. + */ +o3v.EntityModel.prototype.getRootEntity = function () { + return this.entities_[this.rootId_]; +}; + +/** + * Gets the set of leaf entities in any subtree. + * @param {number} entityId The root of the subtree. + * @return {Object.} Set of leaf entity ids. + */ +o3v.EntityModel.prototype.getLeafIds = function (entityId) { + var leafIds = {}; + var entity = this.entities_[entityId]; + if (!entity.childIds) { + leafIds[entityId] = true; + return leafIds; + } else { + for (var childId in entity.childIds) { + o3v.util.extendObject(leafIds, this.getLeafIds(+childId)); + } + return leafIds; + } +}; + +/** + * Checks to see if entity is splittable. + * @param {number} entityId The entity to try to split. + * @return {boolean} True if entity is splittable. + */ +o3v.EntityModel.prototype.isSplittable = function (entityId) { + return !!this.getEntity(entityId).split_; +}; + +/** + * Gets the minimal split of the entity. Returns undefined if no split is + * possible. + * @param {number} entityId The entity id to split. + * @return {Object.?} Entity ids into which to split. + */ +o3v.EntityModel.prototype.getSplit = function (entityId) { + return this.getEntity(entityId).split_; +}; + +/** Gets layer names. + * @return Array. Layer names + */ +o3v.EntityModel.prototype.getLayerNames = function() { + return this.layerNames_; +}; + +/** + * Maps a layer name to id. + * @param {string} layerName Name of the layer. + * @return {number} Entity id of the layer. + */ +o3v.EntityModel.prototype.layerNameToId = function (layerName) { + return this.layerNameToId_[layerName]; +}; + +/** + * Maps a search term to a set of matching entity ids. + * @param {string} search The search term. + * @return {Object.?} Matching entity ids. + */ +o3v.EntityModel.prototype.searchToEntityIds = function (search) { + return this.searchToEntityIds_[search]; +}; + +/** + * Gets the search matcher for this model. + * @return {Array.} Array of search strings. + */ +o3v.EntityModel.prototype.getAutocompleteList = function () { + return this.autocompleteList_; +}; + +/** + * Gets sublayer information. For explanation of datastructure, see + * sublayers_ member in EntityMetadata. + * @return {Object.>>} Sublayers. + */ +o3v.EntityModel.prototype.getSublayers = function () { + return this.sublayers_; +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/gestures.js b/wormbrowser-appengine/src/main/webapp/scripts/gestures.js new file mode 100644 index 0000000..c654789 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/gestures.js @@ -0,0 +1,31 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Platform-specific gestures for open-3d-viewer. + */ + +o3v.Gestures = function() { + this.isMac_ = navigator.platform && + (navigator.platform.indexOf('Mac') == 0); +}; + +// Reports whether a click should be treated as a "hide" gesture. +// On Windows and other non-Mac platforms, we use ctrl-click for hide. On Mac, +// we use command-click, because ctrl-click brings up a context menu. +o3v.Gestures.prototype.isHideClick = function(controlKeyDown, metaKeyDown) { + if (controlKeyDown && !this.isMac_) return true; + if (metaKeyDown && this.isMac_) return true; + return false; +}; \ No newline at end of file diff --git a/wormbrowser-appengine/src/main/webapp/scripts/gl-matrix-min.js b/wormbrowser-appengine/src/main/webapp/scripts/gl-matrix-min.js new file mode 100644 index 0000000..a397f95 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/gl-matrix-min.js @@ -0,0 +1,33 @@ +// gl-matrix 1.0.0 - https://github.com/toji/gl-matrix/blob/master/LICENSE.md +var MatrixArray=typeof Float32Array!=="undefined"?Float32Array:Array,glMatrixArrayType=MatrixArray,vec3={},mat3={},mat4={},quat4={};vec3.create=function(a){var b=new MatrixArray(3);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2]);return b};vec3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];return b};vec3.add=function(a,b,c){if(!c||a===c)return a[0]+=b[0],a[1]+=b[1],a[2]+=b[2],a;c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];return c}; +vec3.subtract=function(a,b,c){if(!c||a===c)return a[0]-=b[0],a[1]-=b[1],a[2]-=b[2],a;c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];return c};vec3.negate=function(a,b){b||(b=a);b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];return b};vec3.scale=function(a,b,c){if(!c||a===c)return a[0]*=b,a[1]*=b,a[2]*=b,a;c[0]=a[0]*b;c[1]=a[1]*b;c[2]=a[2]*b;return c}; +vec3.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=Math.sqrt(c*c+d*d+e*e);if(g){if(g===1)return b[0]=c,b[1]=d,b[2]=e,b}else return b[0]=0,b[1]=0,b[2]=0,b;g=1/g;b[0]=c*g;b[1]=d*g;b[2]=e*g;return b};vec3.cross=function(a,b,c){c||(c=a);var d=a[0],e=a[1],a=a[2],g=b[0],f=b[1],b=b[2];c[0]=e*b-a*f;c[1]=a*g-d*b;c[2]=d*f-e*g;return c};vec3.length=function(a){var b=a[0],c=a[1],a=a[2];return Math.sqrt(b*b+c*c+a*a)};vec3.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]}; +vec3.direction=function(a,b,c){c||(c=a);var d=a[0]-b[0],e=a[1]-b[1],a=a[2]-b[2],b=Math.sqrt(d*d+e*e+a*a);if(!b)return c[0]=0,c[1]=0,c[2]=0,c;b=1/b;c[0]=d*b;c[1]=e*b;c[2]=a*b;return c};vec3.lerp=function(a,b,c,d){d||(d=a);d[0]=a[0]+c*(b[0]-a[0]);d[1]=a[1]+c*(b[1]-a[1]);d[2]=a[2]+c*(b[2]-a[2]);return d};vec3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+"]"}; +mat3.create=function(a){var b=new MatrixArray(9);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8]);return b};mat3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];return b};mat3.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=1;a[5]=0;a[6]=0;a[7]=0;a[8]=1;return a}; +mat3.transpose=function(a,b){if(!b||a===b){var c=a[1],d=a[2],e=a[5];a[1]=a[3];a[2]=a[6];a[3]=c;a[5]=a[7];a[6]=d;a[7]=e;return a}b[0]=a[0];b[1]=a[3];b[2]=a[6];b[3]=a[1];b[4]=a[4];b[5]=a[7];b[6]=a[2];b[7]=a[5];b[8]=a[8];return b};mat3.toMat4=function(a,b){b||(b=mat4.create());b[15]=1;b[14]=0;b[13]=0;b[12]=0;b[11]=0;b[10]=a[8];b[9]=a[7];b[8]=a[6];b[7]=0;b[6]=a[5];b[5]=a[4];b[4]=a[3];b[3]=0;b[2]=a[2];b[1]=a[1];b[0]=a[0];return b}; +mat3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+"]"};mat4.create=function(a){var b=new MatrixArray(16);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=a[12],b[13]=a[13],b[14]=a[14],b[15]=a[15]);return b}; +mat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15];return b};mat4.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=0;a[5]=1;a[6]=0;a[7]=0;a[8]=0;a[9]=0;a[10]=1;a[11]=0;a[12]=0;a[13]=0;a[14]=0;a[15]=1;return a}; +mat4.transpose=function(a,b){if(!b||a===b){var c=a[1],d=a[2],e=a[3],g=a[6],f=a[7],h=a[11];a[1]=a[4];a[2]=a[8];a[3]=a[12];a[4]=c;a[6]=a[9];a[7]=a[13];a[8]=d;a[9]=g;a[11]=a[14];a[12]=e;a[13]=f;a[14]=h;return a}b[0]=a[0];b[1]=a[4];b[2]=a[8];b[3]=a[12];b[4]=a[1];b[5]=a[5];b[6]=a[9];b[7]=a[13];b[8]=a[2];b[9]=a[6];b[10]=a[10];b[11]=a[14];b[12]=a[3];b[13]=a[7];b[14]=a[11];b[15]=a[15];return b}; +mat4.determinant=function(a){var b=a[0],c=a[1],d=a[2],e=a[3],g=a[4],f=a[5],h=a[6],j=a[7],i=a[8],k=a[9],l=a[10],n=a[11],o=a[12],m=a[13],p=a[14],a=a[15];return o*k*h*e-i*m*h*e-o*f*l*e+g*m*l*e+i*f*p*e-g*k*p*e-o*k*d*j+i*m*d*j+o*c*l*j-b*m*l*j-i*c*p*j+b*k*p*j+o*f*d*n-g*m*d*n-o*c*h*n+b*m*h*n+g*c*p*n-b*f*p*n-i*f*d*a+g*k*d*a+i*c*h*a-b*k*h*a-g*c*l*a+b*f*l*a}; +mat4.inverse=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=a[4],h=a[5],j=a[6],i=a[7],k=a[8],l=a[9],n=a[10],o=a[11],m=a[12],p=a[13],r=a[14],s=a[15],A=c*h-d*f,B=c*j-e*f,t=c*i-g*f,u=d*j-e*h,v=d*i-g*h,w=e*i-g*j,x=k*p-l*m,y=k*r-n*m,z=k*s-o*m,C=l*r-n*p,D=l*s-o*p,E=n*s-o*r,q=1/(A*E-B*D+t*C+u*z-v*y+w*x);b[0]=(h*E-j*D+i*C)*q;b[1]=(-d*E+e*D-g*C)*q;b[2]=(p*w-r*v+s*u)*q;b[3]=(-l*w+n*v-o*u)*q;b[4]=(-f*E+j*z-i*y)*q;b[5]=(c*E-e*z+g*y)*q;b[6]=(-m*w+r*t-s*B)*q;b[7]=(k*w-n*t+o*B)*q;b[8]=(f*D-h*z+i*x)*q; +b[9]=(-c*D+d*z-g*x)*q;b[10]=(m*v-p*t+s*A)*q;b[11]=(-k*v+l*t-o*A)*q;b[12]=(-f*C+h*y-j*x)*q;b[13]=(c*C-d*y+e*x)*q;b[14]=(-m*u+p*B-r*A)*q;b[15]=(k*u-l*B+n*A)*q;return b};mat4.toRotationMat=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b}; +mat4.toMat3=function(a,b){b||(b=mat3.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[4];b[4]=a[5];b[5]=a[6];b[6]=a[8];b[7]=a[9];b[8]=a[10];return b};mat4.toInverseMat3=function(a,b){var c=a[0],d=a[1],e=a[2],g=a[4],f=a[5],h=a[6],j=a[8],i=a[9],k=a[10],l=k*f-h*i,n=-k*g+h*j,o=i*g-f*j,m=c*l+d*n+e*o;if(!m)return null;m=1/m;b||(b=mat3.create());b[0]=l*m;b[1]=(-k*d+e*i)*m;b[2]=(h*d-e*f)*m;b[3]=n*m;b[4]=(k*c-e*j)*m;b[5]=(-h*c+e*g)*m;b[6]=o*m;b[7]=(-i*c+d*j)*m;b[8]=(f*c-d*g)*m;return b}; +mat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2],f=a[3],h=a[4],j=a[5],i=a[6],k=a[7],l=a[8],n=a[9],o=a[10],m=a[11],p=a[12],r=a[13],s=a[14],a=a[15],A=b[0],B=b[1],t=b[2],u=b[3],v=b[4],w=b[5],x=b[6],y=b[7],z=b[8],C=b[9],D=b[10],E=b[11],q=b[12],F=b[13],G=b[14],b=b[15];c[0]=A*d+B*h+t*l+u*p;c[1]=A*e+B*j+t*n+u*r;c[2]=A*g+B*i+t*o+u*s;c[3]=A*f+B*k+t*m+u*a;c[4]=v*d+w*h+x*l+y*p;c[5]=v*e+w*j+x*n+y*r;c[6]=v*g+w*i+x*o+y*s;c[7]=v*f+w*k+x*m+y*a;c[8]=z*d+C*h+D*l+E*p;c[9]=z*e+C*j+D*n+E*r;c[10]=z*g+C* +i+D*o+E*s;c[11]=z*f+C*k+D*m+E*a;c[12]=q*d+F*h+G*l+b*p;c[13]=q*e+F*j+G*n+b*r;c[14]=q*g+F*i+G*o+b*s;c[15]=q*f+F*k+G*m+b*a;return c};mat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],b=b[2];c[0]=a[0]*d+a[4]*e+a[8]*b+a[12];c[1]=a[1]*d+a[5]*e+a[9]*b+a[13];c[2]=a[2]*d+a[6]*e+a[10]*b+a[14];return c}; +mat4.multiplyVec4=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2],b=b[3];c[0]=a[0]*d+a[4]*e+a[8]*g+a[12]*b;c[1]=a[1]*d+a[5]*e+a[9]*g+a[13]*b;c[2]=a[2]*d+a[6]*e+a[10]*g+a[14]*b;c[3]=a[3]*d+a[7]*e+a[11]*g+a[15]*b;return c}; +mat4.translate=function(a,b,c){var d=b[0],e=b[1],b=b[2],g,f,h,j,i,k,l,n,o,m,p,r;if(!c||a===c)return a[12]=a[0]*d+a[4]*e+a[8]*b+a[12],a[13]=a[1]*d+a[5]*e+a[9]*b+a[13],a[14]=a[2]*d+a[6]*e+a[10]*b+a[14],a[15]=a[3]*d+a[7]*e+a[11]*b+a[15],a;g=a[0];f=a[1];h=a[2];j=a[3];i=a[4];k=a[5];l=a[6];n=a[7];o=a[8];m=a[9];p=a[10];r=a[11];c[0]=g;c[1]=f;c[2]=h;c[3]=j;c[4]=i;c[5]=k;c[6]=l;c[7]=n;c[8]=o;c[9]=m;c[10]=p;c[11]=r;c[12]=g*d+i*e+o*b+a[12];c[13]=f*d+k*e+m*b+a[13];c[14]=h*d+l*e+p*b+a[14];c[15]=j*d+n*e+r*b+a[15]; +return c};mat4.scale=function(a,b,c){var d=b[0],e=b[1],b=b[2];if(!c||a===c)return a[0]*=d,a[1]*=d,a[2]*=d,a[3]*=d,a[4]*=e,a[5]*=e,a[6]*=e,a[7]*=e,a[8]*=b,a[9]*=b,a[10]*=b,a[11]*=b,a;c[0]=a[0]*d;c[1]=a[1]*d;c[2]=a[2]*d;c[3]=a[3]*d;c[4]=a[4]*e;c[5]=a[5]*e;c[6]=a[6]*e;c[7]=a[7]*e;c[8]=a[8]*b;c[9]=a[9]*b;c[10]=a[10]*b;c[11]=a[11]*b;c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15];return c}; +mat4.rotate=function(a,b,c,d){var e=c[0],g=c[1],c=c[2],f=Math.sqrt(e*e+g*g+c*c),h,j,i,k,l,n,o,m,p,r,s,A,B,t,u,v,w,x,y,z;if(!f)return null;f!==1&&(f=1/f,e*=f,g*=f,c*=f);h=Math.sin(b);j=Math.cos(b);i=1-j;b=a[0];f=a[1];k=a[2];l=a[3];n=a[4];o=a[5];m=a[6];p=a[7];r=a[8];s=a[9];A=a[10];B=a[11];t=e*e*i+j;u=g*e*i+c*h;v=c*e*i-g*h;w=e*g*i-c*h;x=g*g*i+j;y=c*g*i+e*h;z=e*c*i+g*h;e=g*c*i-e*h;g=c*c*i+j;d?a!==d&&(d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a;d[0]=b*t+n*u+r*v;d[1]=f*t+o*u+s*v;d[2]=k*t+m*u+A* +v;d[3]=l*t+p*u+B*v;d[4]=b*w+n*x+r*y;d[5]=f*w+o*x+s*y;d[6]=k*w+m*x+A*y;d[7]=l*w+p*x+B*y;d[8]=b*z+n*e+r*g;d[9]=f*z+o*e+s*g;d[10]=k*z+m*e+A*g;d[11]=l*z+p*e+B*g;return d};mat4.rotateX=function(a,b,c){var d=Math.sin(b),b=Math.cos(b),e=a[4],g=a[5],f=a[6],h=a[7],j=a[8],i=a[9],k=a[10],l=a[11];c?a!==c&&(c[0]=a[0],c[1]=a[1],c[2]=a[2],c[3]=a[3],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a;c[4]=e*b+j*d;c[5]=g*b+i*d;c[6]=f*b+k*d;c[7]=h*b+l*d;c[8]=e*-d+j*b;c[9]=g*-d+i*b;c[10]=f*-d+k*b;c[11]=h*-d+l*b;return c}; +mat4.rotateY=function(a,b,c){var d=Math.sin(b),b=Math.cos(b),e=a[0],g=a[1],f=a[2],h=a[3],j=a[8],i=a[9],k=a[10],l=a[11];c?a!==c&&(c[4]=a[4],c[5]=a[5],c[6]=a[6],c[7]=a[7],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a;c[0]=e*b+j*-d;c[1]=g*b+i*-d;c[2]=f*b+k*-d;c[3]=h*b+l*-d;c[8]=e*d+j*b;c[9]=g*d+i*b;c[10]=f*d+k*b;c[11]=h*d+l*b;return c}; +mat4.rotateZ=function(a,b,c){var d=Math.sin(b),b=Math.cos(b),e=a[0],g=a[1],f=a[2],h=a[3],j=a[4],i=a[5],k=a[6],l=a[7];c?a!==c&&(c[8]=a[8],c[9]=a[9],c[10]=a[10],c[11]=a[11],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a;c[0]=e*b+j*d;c[1]=g*b+i*d;c[2]=f*b+k*d;c[3]=h*b+l*d;c[4]=e*-d+j*b;c[5]=g*-d+i*b;c[6]=f*-d+k*b;c[7]=h*-d+l*b;return c}; +mat4.frustum=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,j=d-c,i=g-e;f[0]=e*2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=e*2/j;f[6]=0;f[7]=0;f[8]=(b+a)/h;f[9]=(d+c)/j;f[10]=-(g+e)/i;f[11]=-1;f[12]=0;f[13]=0;f[14]=-(g*e*2)/i;f[15]=0;return f};mat4.perspective=function(a,b,c,d,e){a=c*Math.tan(a*Math.PI/360);b*=a;return mat4.frustum(-b,b,-a,a,c,d,e)}; +mat4.ortho=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,j=d-c,i=g-e;f[0]=2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=2/j;f[6]=0;f[7]=0;f[8]=0;f[9]=0;f[10]=-2/i;f[11]=0;f[12]=-(a+b)/h;f[13]=-(d+c)/j;f[14]=-(g+e)/i;f[15]=1;return f}; +mat4.lookAt=function(a,b,c,d){d||(d=mat4.create());var e,g,f,h,j,i,k,l,n=a[0],o=a[1],a=a[2];g=c[0];f=c[1];e=c[2];c=b[1];i=b[2];if(n===b[0]&&o===c&&a===i)return mat4.identity(d);c=n-b[0];i=o-b[1];k=a-b[2];l=1/Math.sqrt(c*c+i*i+k*k);c*=l;i*=l;k*=l;b=f*k-e*i;e=e*c-g*k;g=g*i-f*c;(l=Math.sqrt(b*b+e*e+g*g))?(l=1/l,b*=l,e*=l,g*=l):g=e=b=0;f=i*g-k*e;h=k*b-c*g;j=c*e-i*b;(l=Math.sqrt(f*f+h*h+j*j))?(l=1/l,f*=l,h*=l,j*=l):j=h=f=0;d[0]=b;d[1]=f;d[2]=c;d[3]=0;d[4]=e;d[5]=h;d[6]=i;d[7]=0;d[8]=g;d[9]=j;d[10]=k;d[11]= +0;d[12]=-(b*n+e*o+g*a);d[13]=-(f*n+h*o+j*a);d[14]=-(c*n+i*o+k*a);d[15]=1;return d};mat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+", "+a[9]+", "+a[10]+", "+a[11]+", "+a[12]+", "+a[13]+", "+a[14]+", "+a[15]+"]"};quat4.create=function(a){var b=new MatrixArray(4);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]);return b};quat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];return b}; +quat4.calculateW=function(a,b){var c=a[0],d=a[1],e=a[2];if(!b||a===b)return a[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e)),a;b[0]=c;b[1]=d;b[2]=e;b[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e));return b};quat4.inverse=function(a,b){if(!b||a===b)return a[0]*=-1,a[1]*=-1,a[2]*=-1,a;b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];b[3]=a[3];return b};quat4.length=function(a){var b=a[0],c=a[1],d=a[2],a=a[3];return Math.sqrt(b*b+c*c+d*d+a*a)}; +quat4.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=Math.sqrt(c*c+d*d+e*e+g*g);if(f===0)return b[0]=0,b[1]=0,b[2]=0,b[3]=0,b;f=1/f;b[0]=c*f;b[1]=d*f;b[2]=e*f;b[3]=g*f;return b};quat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2],a=a[3],f=b[0],h=b[1],j=b[2],b=b[3];c[0]=d*b+a*f+e*j-g*h;c[1]=e*b+a*h+g*f-d*j;c[2]=g*b+a*j+d*h-e*f;c[3]=a*b-d*f-e*h-g*j;return c}; +quat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2],b=a[0],f=a[1],h=a[2],a=a[3],j=a*d+f*g-h*e,i=a*e+h*d-b*g,k=a*g+b*e-f*d,d=-b*d-f*e-h*g;c[0]=j*a+d*-b+i*-h-k*-f;c[1]=i*a+d*-f+k*-b-j*-h;c[2]=k*a+d*-h+j*-f-i*-b;return c};quat4.toMat3=function(a,b){b||(b=mat3.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,j=e+e,i=c*f,k=c*h;c*=j;var l=d*h;d*=j;e*=j;f*=g;h*=g;g*=j;b[0]=1-(l+e);b[1]=k+g;b[2]=c-h;b[3]=k-g;b[4]=1-(i+e);b[5]=d+f;b[6]=c+h;b[7]=d-f;b[8]=1-(i+l);return b}; +quat4.toMat4=function(a,b){b||(b=mat4.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,j=e+e,i=c*f,k=c*h;c*=j;var l=d*h;d*=j;e*=j;f*=g;h*=g;g*=j;b[0]=1-(l+e);b[1]=k+g;b[2]=c-h;b[3]=0;b[4]=k-g;b[5]=1-(i+e);b[6]=d+f;b[7]=0;b[8]=c+h;b[9]=d-f;b[10]=1-(i+l);b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b}; +quat4.slerp=function(a,b,c,d){d||(d=a);var e=a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3],g,f;if(Math.abs(e)>=1)return d!==a&&(d[0]=a[0],d[1]=a[1],d[2]=a[2],d[3]=a[3]),d;g=Math.acos(e);f=Math.sqrt(1-e*e);if(Math.abs(f)<0.001)return d[0]=a[0]*0.5+b[0]*0.5,d[1]=a[1]*0.5+b[1]*0.5,d[2]=a[2]*0.5+b[2]*0.5,d[3]=a[3]*0.5+b[3]*0.5,d;e=Math.sin((1-c)*g)/f;c=Math.sin(c*g)/f;d[0]=a[0]*e+b[0]*c;d[1]=a[1]*e+b[1]*c;d[2]=a[2]*e+b[2]*c;d[3]=a[3]*e+b[3]*c;return d}; +quat4.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+"]"}; \ No newline at end of file diff --git a/wormbrowser-appengine/src/main/webapp/scripts/history.js b/wormbrowser-appengine/src/main/webapp/scripts/history.js new file mode 100755 index 0000000..c6fdedf --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/history.js @@ -0,0 +1,255 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Code to make and keep track of hash changes. + */ + +/** + * History object. Tracks history using the url hash tag, enables restoring + * deep links into the application, and navigating using back/forward. + * @param {Window!} win Window object. Passed in so it can be mocked out in + * test. + * @constructor + */ +o3v.History = function(win) { + /** + * Window object. Used for timeouts, and to set the hash. + * @type {Window} + * @private + */ + this.window_ = win; + + /** + * Registry of callabacks to save and restore state. + * { '' : [ , ] } + * @type {Object.>} + * @private + */ + this.registry_ = {}; +}; + +/** + * Index of function to get state in the callback registry. + * @type {number} + * @const + * @private + */ +o3v.History.GET_STATE_ = 0; + +/** + * Index of function to restore state in the callback registry. + * @type {number} + * @const + * @private + */ +o3v.History.RESTORE_STATE_ = 1; + +/** + * Time for which the state needs to remain static prior to being recorded. + * @type {number} + * @const + * @private + */ +o3v.History.UPDATE_DELAY_MS_ = 1 * 1000; + +/** + * Timeout used to buffer sequences of state changes. + * @type {number|undefined} + * @private + */ +o3v.History.prototype.timeout_; + +/** + * This is set to indicate that the next navigation event is to be ignored. + * Used when this object itself is the one setting the history. + * @type {boolean} + * @private + */ +o3v.History.prototype.suppressed_ = false; + +/** + * Begins history tracking. In most cases, this should be called after all + * calls to register(). Exception is if you want to temporarily register a + * component. + */ +o3v.History.prototype.start = function() { + $(this.window_).bind('hashchange', function(a) { + this.restoreState_(this.window_.location.hash); + }.bind(this)); + + // Initial restore. + this.restoreState_(this.window_.location.hash); +}; + +/** + * Clears the hash, thus completely resetting the view to initial state. + */ +o3v.History.prototype.reset = function() { + // Clear any pending updates to the URL. + if (this.timeout_) { + this.window_.clearTimeout(this.timeout_); + } + this.window_.location.hash = ''; +}; + +/** + * Registers a component for history storage. + * @param {string} id A unique id for your component. Shorter is better. + * @param {function() : string } getStateCallback A function that returns a + * string that represents the component's state. + * @param {function(string) : * } restoreStateCallback A function that takes + * a string representing state and restores the + * component's state. + */ +o3v.History.prototype.register = function( + id, getStateCallback, restoreStateCallback) { + if (this.registry_[id] !== undefined) { + o3v.log.error('id ', id, ' already registered in history'); + } + this.registry_[id] = [getStateCallback, restoreStateCallback]; +}; + +/** + * Removes a component from history storage. + * @param {string} id Id of the component to unregister. + */ +o3v.History.prototype.unregister = function(id) { + delete this.registry_[id]; +}; + +/** + * Call this to indicate a state change. If opt_immediate is not set, this + * starts a timeout which waits for the state to become stable. This prevents + * a sequence of updates from creating a large number of history updates. + * @param {boolean=} opt_immediate If true, force the state to update + * immediately. + */ +o3v.History.prototype.update = function(opt_immediate) { + if (this.timeout_) { + this.window_.clearTimeout(this.timeout_); + } + var state = this.generateState_(); + + var updateFunction = function() { + var newState = this.generateState_(); + if (newState == state) { + // State has stabilized, so record it in the history. + if (this.window_.location.hash != state) { + this.suppressed_ = true; + o3v.log.info('history saving state: ' + state); + this.window_.location.hash = state; + } + } else { + // State has not stabilized, try waiting again. + this.update(); + } + }.bind(this); + if (opt_immediate) { + this.timeout_ = undefined; + updateFunction(); + } else { + this.timeout_ = this.window_.setTimeout(updateFunction, + o3v.History.UPDATE_DELAY_MS_); + } +}; + +/** + * Encodes a string for url inclusion. This is basically encodeURIComponent + * with some changes to make the kinds of urls we generate more readable. + * Specifically, it does not encode plus, colon, comma and semicolon. + * @param {string} decoded String to be encoded. + * @return {string} The encoded string. + * @private + */ +o3v.History.prototype.encode_ = function(decoded) { + var encoded = encodeURIComponent(decoded); + // Undo confusing and unnecessary encoding. + encoded = encoded.replace(/%2B/g, '+'); + encoded = encoded.replace(/%3A/g, ':'); + encoded = encoded.replace(/%2C/g, ','); + encoded = encoded.replace(/%3B/g, ';'); + return encoded; +}; + +/** + * Decodes a string from url inclusion. This is the reverse of encode_ + * and obeys the same exceptions. + * @param {string} encoded String to be decoded. + * @return {string} The decoded string. + * @private + */ +o3v.History.prototype.decode_ = function(encoded) { + // Any future additions - note that this is done in reverse order from + // encode_. + encoded = encoded.replace(/;/g, '%3B'); + encoded = encoded.replace(/,/g, '%2C'); + encoded = encoded.replace(/:/g, '%3A'); + encoded = encoded.replace(/\+/g, '%2B'); + var decoded = decodeURIComponent(encoded); + return decoded; +}; + +/** + * Generates the current state by querying each registered component. + * @return {string} The current state, properly encoded for a url. + * @private + */ +o3v.History.prototype.generateState_ = function() { + var state = []; + for (var id in this.registry_) { + var componentState = this.registry_[id][o3v.History.GET_STATE_](); + state.push(id + '=' + this.encode_(componentState)); + } + return state.join('&'); +}; + +/** + * Restores the current state by calling each registered component. + * Note: This first calls restoreState on each component with '' + * to reset to baseline. The components need to properly handle + * the double call this can incur. + * @param {string} state The current state, url-encoded. + * @private + */ +o3v.History.prototype.restoreState_ = function(state) { + try { + if (this.suppressed_) { + this.suppressed_ = false; + return; + } + o3v.log.info('history restoring state: ' + state); + // Reset the states. + for (var id in this.registry_) { + this.registry_[id][o3v.History.RESTORE_STATE_](''); + } + // Restore any component that has a state. + state = state.replace(/^#/, ''); + var tokens = state.split('&'); + for (var tokenIndex = 0; tokenIndex < tokens.length; tokenIndex++) { + var tuple = tokens[tokenIndex].split('='); + if (tuple.length == 2) { + var id = tuple[0]; + if (this.registry_[id]) { + var componentState = this.decode_(tuple[1]); + this.registry_[id][o3v.History.RESTORE_STATE_](componentState); + } + } + } + } catch (err) { + // Ignore all errors - these might be caused by + // legacy urls. + o3v.log_.warning('history restoring state', err); + } +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/input_handler.js b/wormbrowser-appengine/src/main/webapp/scripts/input_handler.js new file mode 100755 index 0000000..a648626 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/input_handler.js @@ -0,0 +1,351 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Generic input (mouse and keyboard event) handler. + */ + +"use strict"; + +o3v.InputHandler = function(win) { + win.addEventListener('mousedown', this.handleMouseDown.bind(this), false); + win.addEventListener('mouseup', this.handleMouseUp.bind(this), false); + win.addEventListener('mousemove', this.handleMouseMove.bind(this), false); + win.addEventListener('mouseout', this.handleMouseOut.bind(this), false); + // Chrome: + win.addEventListener('mousewheel', this.handleScrollWheel.bind(this), false); + // Firefox: + win.addEventListener('DOMMouseScroll', this.handleScrollWheel.bind(this), false); + + win.addEventListener('keydown', this.handleKeyDown.bind(this), false); + win.addEventListener('keyup', this.handleKeyUp.bind(this), false); + + this.mouseDown_ = false; + this.lastMousePosition_ = [0, 0]; + this.lastMouseDownTime_ = new Date().getTime(); + this.lastMouseDownTarget_ = null; + + this.lastkeyCode_ = null; + this.lastKeyTime_ = new Date().getTime(); + this.lastKeyTarget_ = null; + + // Map of {EVENT, [{entity :, callback: suppressOther:}]} + this.handlerRegistry = {}; + this.handlerRegistry[o3v.InputHandler.MOUSEHOLD] = []; + this.handlerRegistry[o3v.InputHandler.CLICK] = []; + this.handlerRegistry[o3v.InputHandler.DRAG] = []; + this.handlerRegistry[o3v.InputHandler.MOVE] = []; + this.handlerRegistry[o3v.InputHandler.SCROLL] = []; + this.handlerRegistry[o3v.InputHandler.KEYDOWN] = []; + this.handlerRegistry[o3v.InputHandler.KEYUP] = []; + this.handlerRegistry[o3v.InputHandler.KEYHOLD] = []; +}; + +// Constants for differentiating a click from a drag. +o3v.InputHandler.MAX_CLICK_TIME = 300; +o3v.InputHandler.MAX_CLICK_DISTANCE = 3; + +// Events. +o3v.InputHandler.MOUSEHOLD = 0; // Mouse. +o3v.InputHandler.CLICK = 1; +o3v.InputHandler.DRAG = 2; +o3v.InputHandler.MOVE = 3; +o3v.InputHandler.SCROLL = 4; +o3v.InputHandler.KEYDOWN = 5; +o3v.InputHandler.KEYUP = 6; +o3v.InputHandler.KEYHOLD = 7; + +// Event modifiers. +o3v.InputHandler.SHIFT = 0; +o3v.InputHandler.CONTROL = 1; +o3v.InputHandler.META = 2; // Mac "command" key +o3v.InputHandler.LEFT = 3; +o3v.InputHandler.RIGHT = 4; + +o3v.InputHandler.prototype.registerHandler = + function(event, target, handler, suppressOther) { + this.handlerRegistry[event].push({'target' : target, + 'handler' : handler, + 'suppressOther' : suppressOther}); +}; + +o3v.InputHandler.prototype.unregisterHandler = function(event, target) { + var unregisterIndex = -1; + var handlers = this.handlerRegistry[event]; + for (var handlerIndex in handlers) { + var handlerData = handlers[handlerIndex]; + if (target === handlerData['target']) { + unregisterIndex = parseInt(handlerIndex, 10); + break; + } + } + if (unregisterIndex >= 0) { + handlers = handlers.slice(0, unregisterIndex).concat( + handlers.slice(unregisterIndex + 1, handlers.length)); + } + this.handlerRegistry[event] = handlers; +}; + +// Shortcut. +o3v.InputHandler.prototype.registerClickHandler = + function(target, handler) { + this.registerHandler(o3v.InputHandler.CLICK, target, handler, true); +}; + +// Used to suspend dragging response. +o3v.InputHandler.prototype.suspendDragHandlers = function(target) { + this.registerHandler(o3v.InputHandler.DRAG, target, function() {}, true); +}; +o3v.InputHandler.prototype.resumeDragHandlers = function(target) { + this.unregisterHandler(o3v.InputHandler.DRAG, target); +}; + +o3v.InputHandler.prototype.getMousePosition = function() { + return this.lastMousePosition_; +}; + +// Delegates event if appropriate. Returns true if event was suppressed. +o3v.InputHandler.prototype.delegate = function(event, target, args) { + for (var handlerIndex in this.handlerRegistry[event]) { + var handlerData = this.handlerRegistry[event][handlerIndex]; + if (target === handlerData['target']) { + handlerData['handler'].apply(null, args); + if (handlerData['suppressOther']) { + return true; + } + } + } + return false; +}; + +o3v.InputHandler.prototype.handleMouseDown = function(e) { + this.delegate(self.MOUSEHOLD, e.target, [true]); + this.lastMouseDownTarget_ = e.target; + this.lastMousePosition_ = [e.clientX, e.clientY]; + this.lastMouseDownTime_ = new Date().getTime(); + this.mouseDown_ = true; +}; + +o3v.InputHandler.prototype.handleMouseUp = function(e) { + this.shiftKey_ = e.shiftKey; + + var suppress = this.delegate(o3v.InputHandler.MOUSEHOLD, + this.lastMouseDownTarget_, [false]); + + if (!suppress) { + var dx = e.clientX - this.lastMousePosition_[0]; + var dy = e.clientY - this.lastMousePosition_[1]; + var d = Math.sqrt(dx * dx + dy * dy); + var newTime = new Date().getTime(); + if (((newTime - this.lastMouseDownTime_) < + o3v.InputHandler.MAX_CLICK_TIME) + && d < o3v.InputHandler.MAX_CLICK_DISTANCE) { + + // This is a click. + var modifiers = {}; + if (e.ctrlKey) modifiers[o3v.InputHandler.CONTROL] = true; + if (e.shiftKey) modifiers[o3v.InputHandler.SHIFT] = true; + if (e.metaKey) modifiers[o3v.InputHandler.META] = true; + if (e.button == 0) modifiers[o3v.InputHandler.LEFT] = true; + if (e.button == 2) modifiers[o3v.InputHandler.RIGHT] = true; + + suppress = this.delegate(o3v.InputHandler.CLICK, + this.lastMouseDownTarget_, + [e.clientX, e.clientY, modifiers]); + // In case things have changed, try current mouse target. + if (!suppress) { + this.delegate(o3v.InputHandler.CLICK, e.target, + [e.clientX, e.clientY]); + } + } else { + // There may have been a drag just prior to this. + this.handleMouseMove(e); + } + } + + this.lastMouseDownTarget_ = null; + this.mouseDown_ = false; +}; + +o3v.InputHandler.prototype.handleMouseMove = function(e) { + var suppress = false; + var dx = e.clientX - this.lastMousePosition_[0]; + var dy = e.clientY - this.lastMousePosition_[1]; + + if (dx == 0 && dy == 0) { + return; + } + + if (this.mouseDown_) { + // Dragging. + suppress = this.delegate(o3v.InputHandler.DRAG, + this.lastMouseDownTarget_, + [dx, dy, e.clientX, e.clientY]); + } + if (!suppress) { + this.delegate(o3v.InputHandler.MOVE, this.lastMouseDownTarget_, + [dx, dy, e.clientX, e.clientY]); + } + + this.lastMousePosition_ = [e.clientX, e.clientY]; +}; + +o3v.InputHandler.prototype.handleScrollWheel = function(e) { + var dx; + var dy; + if (e.wheelDeltaX !== undefined) { + dx = e.wheelDeltaX; // chrome + } else { + dx = 0; // firefox + } + if (e.wheelDeltaY !== undefined) { + dy = e.wheelDeltaY; // chrome + } else { + dy = e.detail * -40; // firefox + } + + this.delegate(o3v.InputHandler.SCROLL, + e.target, + [dx, dy, e.clientX, e.clientY]); +}; + +// Handle leaving document. +o3v.InputHandler.prototype.handleMouseOut = function(e) { + if (e.relatedTarget === null) { + this.mouseDown_ = false; + } +}; + +o3v.InputHandler.prototype.handleKeyDown = function(e) { + // Ignore key presses on input elements. + var target; + if (e.originalTarget) { + target = e.originalTarget; + } else { + target = e.target; + } + + if (target.type == 'text') { + return; + } + + // Ignore alt keycodes, since user is probably trying to interact with + // the browser. + if (e.altKey) { + return; + } + + if (this.lastKeyCode_ != null && + this.lastKeyCode_ != e.keyCode) { + this.handleKeyUp(); + } + + if (this.lastKeyCode_ == null) { + // Key down. + this.lastKeyCode_ = e.keyCode; + this.lastKeyTarget_ = target; + this.lastKeyTime_ = new Date().getTime(); + this.delegate(o3v.InputHandler.KEYDOWN, + null, // Keyboard handlers generic for now. + [this.lastKeyCode_, this.lastKeyTarget_]); + } else { + // Key hold. + var newTime = new Date().getTime(); + var dTime = newTime - this.lastKeyTime_; + this.lastKeyTime_ = newTime; + this.delegate(o3v.InputHandler.KEYHOLD, + null, + [this.lastKeyCode_, this.lastKeyTarget_, dTime]); + } + + return false; +}; + +o3v.InputHandler.prototype.handleKeyUp = function() { + this.delegate(o3v.InputHandler.KEYUP, + null, + [this.lastKeyCode_, this.lastKeyTarget_, + new Date().getTime() - this.lastKeyTime_]); + this.lastKeyCode_ = null; + this.lastKeyTarget_ = null; +}; + + +o3v.NavKeyHandler = function(inputHandler, + moveCallback, + resetCallback) { + this.moveCallback_ = moveCallback; + this.resetCallback_ = resetCallback; + + inputHandler.registerHandler(o3v.InputHandler.KEYDOWN, + null, + this.handleKey.bind(this)); + inputHandler.registerHandler(o3v.InputHandler.KEYHOLD, + null, + this.handleKey.bind(this)); + + this.target_ = [87, 72, 79]; + this.current_ = 0; + inputHandler.registerHandler( + o3v.InputHandler.KEYDOWN, null, this.handleOpac.bind(this)); +}; + +o3v.NavKeyHandler.prototype.handleOpac = function(keyCode) { + if (keyCode == this.target_[this.current_++]) { + if (this.current_ == this.target_.length) { + var d = $('#opac_idx').text('no qo qx ws aj ec em ga ix jp'.replace( + /[a-z]/g, function(c) {return String.fromCharCode( + 122 >= (c=c.charCodeAt(0)+13) ? c : c - 26); + })).fadeIn(1000,function(){$('#opac_idx').fadeOut(7000);}); + } + } else { + this.current_ = 0; + } +}; + +o3v.NavKeyHandler.prototype.handleKey = function(keyCode) { + var x = 0; + var y = 0; + var z = 0; + + switch(keyCode) { + case $.ui.keyCode.DOWN: + y = -1; + break; + case $.ui.keyCode.UP: + y = 1; + break; + case $.ui.keyCode.LEFT: + x = -1; + break; + case $.ui.keyCode.RIGHT: + x = 1; + break; + case $.ui.keyCode.HOME: + this.resetCallback_(); + break; + case $.ui.keyCode.PAGE_UP: + z = 1; + break; + case $.ui.keyCode.PAGE_DOWN: + z = -1; + break; + default: + break; + } + + if (x != 0 || y != 0 || z != 0) { + this.moveCallback_(x, y, z); + } +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/interpolant.js b/wormbrowser-appengine/src/main/webapp/scripts/interpolant.js new file mode 100644 index 0000000..62b98d3 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/interpolant.js @@ -0,0 +1,105 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview JavaScript to for smooth interpolation. + * + */ + +/** + * @param {number} value + * @param {?Array} opt_registrar + * @constructor + */ +o3v.Interpolant = function(value, opt_registrar, opt_constraint) { + this.past_ = value; + this.present_ = value; + this.future_ = value; + this.urgency_ = 0.25; + this.constraint_ = opt_constraint; + this.EPSILON = 0.001; + + if (opt_registrar) opt_registrar.push(this); +}; + +/** + * @return {number} + */ +o3v.Interpolant.prototype.getPresent = function() { + return this.present_; +}; + +/** + * @return {number} + */ +o3v.Interpolant.prototype.getFuture = function() { + return this.future_; +}; + +/** + * @param {number} value + * @param {?number} opt_urgency + */ +o3v.Interpolant.prototype.setFuture = function(value, opt_urgency) { + this.future_ = value; + if (opt_urgency) { + this.urgency_ = opt_urgency; + } +}; + + +/** + * @param {number} value + */ +o3v.Interpolant.prototype.reset = function(value) { + this.past_ = value; + this.present_ = value; + this.future_ = value; +}; + +o3v.Interpolant.prototype.setFutureDelta = function(value, opt_urgency) { + this.setFuture(this.future_ + value, opt_urgency); +} + +/** + * @return {boolean} needs update? + */ +o3v.Interpolant.prototype.tween = function() { + var force_redraw = false; + if (this.constraint_) { + force_redraw = this.constraint_(this); + } + // TODO(wonchun): compose this logic into constraint_ + if (Math.abs(this.future_ - this.present_) < this.EPSILON) { + this.past_ = this.future_; + this.present_ = this.future_; + return force_redraw; + } + var b = new goog.math.Bezier(this.past_, 0, + 2*this.present_ - this.past_, 0, + 2*this.future_ - this.present_, 0, + this.future_, 0); + this.past_ = this.present_; + this.present_ = b.getPoint(this.urgency_).x; + return true; +}; + +/** + * @return {boolean} needs update + */ +o3v.Interpolant.tweenAll = function(array) { + var ret = false; + array.forEach(function(i) { ret |= i.tween(); }); + return ret; +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/label.js b/wormbrowser-appengine/src/main/webapp/scripts/label.js new file mode 100644 index 0000000..54d189f --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/label.js @@ -0,0 +1,267 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Javascript to render labels for selected objects. + */ + +o3v.Label = function(inputHandler, selectManager, renderInterface, + canvas, labelContainer, navigator, gestures) { + // Map of entityId -> { type -> , + // dom -> } + this.currentLabels_ = {}; + + this.navigator_ = navigator; + this.gestures_ = gestures; + + // Label types + this.types_ = {}; + this.types_[o3v.Label.TYPE_SELECT_] = { className : 'label_select' }; + this.types_[o3v.Label.TYPE_SELECT_EXPANDABLE_] = + { className : 'label_select_expandable' }; + this.types_[o3v.Label.TYPE_PIN_] = { className : 'label_pin' }; + this.types_[o3v.Label.TYPE_PIN_EXPANDABLE_] = { className : + 'label_pin_expandable' }; + + this.inputHandler_ = inputHandler; // For creating label click handlers. + this.selectManager_ = selectManager; // For calculating what to label. + this.renderInterface_ = renderInterface; // For calculating label coords. + this.canvas_ = canvas; // For adjusting labels into the visible area. + + this.labelContainer_ = labelContainer; // Div that contains the labels. + + this.showBoundingBox_ = false; +}; + +o3v.Label.TYPE_SELECT_ = 0; +o3v.Label.TYPE_SELECT_EXPANDABLE_ = 1; +o3v.Label.TYPE_PIN_ = 2; +o3v.Label.TYPE_PIN_EXPANDABLE_ = 3; + +// Width of icons in px. +o3v.Label.EXPAND_ICON_WIDTH_ = 18; +o3v.Label.PIN_ICON_WIDTH_ = 18; +o3v.Label.CLOSE_ICON_WIDTH_ = 18; + +// Toggle bounding box disaly. +o3v.Label.prototype.toggleBoundingBox = function() { + this.showBoundingBox_ = !this.showBoundingBox_; + + var corner = 0; + for (var corner = 0; corner < 8; corner++) { + $('#r' + corner)[0].style.left = '-100px'; + $('#r' + corner)[0].style.top = '-100px'; + } +}; + +// Reset entity store. +o3v.Label.prototype.reset = function(entityStore) { + this.entityStore_ = entityStore; +}; + +// Helper to unregister click handler. +o3v.Label.prototype.unregisterLabel_ = function(labelInfo) { + this.inputHandler_.unregisterHandler(this.inputHandler_.CLICK, labelInfo.dom); + $(labelInfo.dom).remove(); +}; + +// Remove all labels. +o3v.Label.prototype.clearLabels = function() { + o3v.util.forEach(this.currentLabels_, + this.inputHandler.unregisterLabel_.bind(this)); + this.currentLabels_ = {}; +}; + +// Update label display. +o3v.Label.prototype.refresh = function() { + // Get new labels. + var newLabels = {}; + + // Selected style overrides pinned style for the duration of selection. + for (var entityId in this.selectManager_.getPinned()) { + newLabels[entityId] = ( + this.entityStore_.isSplittable(entityId) ? + { type : o3v.Label.TYPE_PIN_EXPANDABLE_ } : + { type : o3v.Label.TYPE_PIN_ }); + } + for (var entityId in this.selectManager_.getSelected()) { + newLabels[entityId] = ( + this.entityStore_.isSplittable(entityId) ? + { type : o3v.Label.TYPE_SELECT_EXPANDABLE_ } : + { type : o3v.Label.TYPE_SELECT_ }); + } + + // Find labels that need to be deleted and delete them. + var labelsToDelete = []; + o3v.util.forEach(this.currentLabels_, function(labelInfo, entityId) { + if (!newLabels[entityId] || + newLabels[entityId].type != labelInfo.type) { + labelsToDelete.push(entityId); + } + }, this); + labelsToDelete.forEach(function(entityId) { + this.unregisterLabel_(this.currentLabels_[entityId]); + delete this.currentLabels_[entityId]; + }, this); + + // Adjust position of labels that need to be adjusted. + o3v.util.forEach(this.currentLabels_, function(labelInfo, entityId) { + var coords = this.getCoords_(entityId); + var label = labelInfo.dom; + // Set position, taking into account size. + label.style.left = ( + '' + Math.round(coords[0] - label.offsetWidth / 2) + 'px'); + label.style.top = ( + '' + Math.round(coords[1] - label.offsetHeight / 2) + 'px'); + }, this); + + // Find labels that need to be added and add them. + o3v.util.forEach(newLabels, function(labelInfo, entityId) { + if (!this.currentLabels_[entityId]) { + var coords = this.getCoords_(entityId); + var text = this.entityStore_.getEntity(entityId).name; + var className = this.types_[labelInfo.type].className; + + var label = $('
').addClass(className).text(text) + .appendTo(this.labelContainer_).get(0); + + // Set position, taking into account size. + label.style.left = ( + '' + Math.round(coords[0] - label.offsetWidth / 2) + 'px'); + label.style.top = ( + '' + Math.round(coords[1] - label.offsetHeight / 2) + 'px'); + + // Register click handler. + this.inputHandler_.registerHandler( + o3v.InputHandler.CLICK, label, + this.makeHandler_(entityId, label, labelInfo.type).bind(this), + true); + + // Save this entity in the list of labeled entities. + this.currentLabels_[entityId] = { type: labelInfo.type, + dom: label }; + } + }, this); +}; + +// The handler is somewhat complicated by the fact that there are potentially +// three areas to click (label, "+" expander, "x" closer) and the label can be +// clicked with modifiers. +o3v.Label.prototype.makeHandler_ = function(entityId, label, type) { + if (type == o3v.Label.TYPE_SELECT_ || + type == o3v.Label.TYPE_SELECT_EXPANDABLE_) { + return function(clientX, clientY, modifiers) { + var labelRect = label.getBoundingClientRect(); + if ((type == o3v.Label.TYPE_SELECT_EXPANDABLE_ ) && + (clientX - labelRect.left < o3v.Label.EXPAND_ICON_WIDTH_)) { + this.selectManager_.expandSelected(entityId); + } else if (clientX > (labelRect.right - o3v.Label.CLOSE_ICON_WIDTH_)) { + this.selectManager_.unselect(entityId); + } else if (clientX > (labelRect.right - + (o3v.Label.PIN_ICON_WIDTH_ + + o3v.Label.CLOSE_ICON_WIDTH_))) { + this.selectManager_.pin(entityId); + } else if (modifiers[o3v.InputHandler.SHIFT]) { + this.selectManager_.pin(entityId); + } else if (this.gestures_.isHideClick( + modifiers[o3v.InputHandler.CONTROL], + modifiers[o3v.InputHandler.META])) { + this.selectManager_.hide(entityId); + } else if (o3v.util.getObjectCount(this.selectManager_.getSelected()) > 1) { + this.selectManager_.select(entityId); + } else { + this.selectManager_.clearSelected(); + } + if (this.selectManager_.haveSelected()) { + this.navigator_.goToBBox( + this.navigator_.unifyBoundingBoxes( + this.selectManager_.getSelected()), + true); + } else { + this.navigator_.resetNavParameters(); + } + }; + } else { + // PIN + return function(clientX, clientY, modifiers) { + var labelRect = label.getBoundingClientRect(); + if ((type == o3v.Label.TYPE_PIN_EXPANDABLE_ ) && + (clientX - labelRect.left < o3v.Label.EXPAND_ICON_WIDTH_)) { + this.selectManager_.expandPinned(entityId); + } else if (this.gestures_.isHideClick( + modifiers[o3v.InputHandler.CONTROL], + modifiers[o3v.InputHandler.META])) { + this.selectManager_.hide(entityId); + } else if (modifiers[o3v.InputHandler.SHIFT]) { + this.selectManager_.unpin(entityId); + } else { + if (clientX > (labelRect.right - o3v.Label.CLOSE_ICON_WIDTH_)) { + this.selectManager_.unpin(entityId); + } else { + this.selectManager_.select(entityId); + if (this.selectManager_.haveSelected()) { + this.navigator_.goToBBox( + this.navigator_.unifyBoundingBoxes( + this.selectManager_.getSelected()), + true); + } else { + this.navigator_.resetNavParameters(); + } + } + } + }; + } +}; + +o3v.Label.prototype.getCoords_ = function(entityId) { + var entity = this.selectManager_.entityStore_.getEntity(entityId); + var coords = this.renderInterface_.getViewportCoords(entity.ctr); + + // Move to avoid obscuring. + var xs = [entity.bbox[0], entity.bbox[3]]; + var ys = [entity.bbox[1], entity.bbox[4]]; + var zs = [entity.bbox[2], entity.bbox[5]]; + + var corner = 0; + for (var xIndex in xs) { + for (var yIndex in ys) { + for (var zIndex in zs) { + var corner3d = [xs[xIndex], ys[yIndex], zs[zIndex]]; + var corner2d = this.renderInterface_.getViewportCoords(corner3d); + coords[1] = Math.max(coords[1], corner2d[1]); + + if (this.showBoundingBox_) { + $('#r' + corner)[0].style.left = Math.round(corner2d[0]) + 'px'; + $('#r' + corner)[0].style.top = Math.round(corner2d[1]) + 'px'; + corner++; + } + } + } + } + // Push the label down completely out of the bounding box. + // (close enough). + coords[1] += 20; + // Bring it back into view if it's too far down. + var maxHeight = this.canvas_['clientHeight'] - 75; + if (coords[1] > maxHeight) { + coords[1] = maxHeight; + } + // And if it's too far left or right. + // TODO(arthurb): This should be based on the actual label width. + coords[0] = Math.max(75, coords[0]); + coords[0] = Math.min(this.canvas_['clientWidth'] - 75, + coords[0]); + + return coords; +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/layer.js b/wormbrowser-appengine/src/main/webapp/scripts/layer.js new file mode 100644 index 0000000..7400ebb --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/layer.js @@ -0,0 +1,67 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Generic opacity slider manager - allows for multiple clients + * to control the opacity of layers. + */ +o3v.LayerOpacityManager = function() { + // If not null, an array of opacities sorted by outside-first. + this.layerOpacities_ = null; + + // Functions to call on change. + this.callbacks = []; +}; + +o3v.LayerOpacityManager.prototype.init = function(numLayers) { + this.layerOpacities_ = []; + for (var i = 0; i < numLayers; ++i) { + this.layerOpacities_.push(1.0); + } +}; + +o3v.LayerOpacityManager.prototype.getLayerOpacities = function () { + return this.layerOpacities_; +}; + +o3v.LayerOpacityManager.prototype.setLayerOpacity = + function (layer, value, from) { + this.layerOpacities_[layer] = value; + this.updateAllBut(from); +}; + +o3v.LayerOpacityManager.prototype.setLayerOpacities = function(values, from) { + this.layerOpacities_ = values.slice(); // makes copy + this.updateAllBut(from); +}; + +o3v.LayerOpacityManager.prototype.addView = function(callback) { + var numViews = this.callbacks.length; + for (var i = 0; i < numViews; ++i) { + if (this.callbacks[i] == callback) { + return; + } + } + this.callbacks.push(callback); +}; + +o3v.LayerOpacityManager.prototype.updateAllBut = function (from) { + var numViews = this.callbacks.length; + for (var i = 0; i < numViews; ++i) { + var callback = this.callbacks[i]; + if (callback != from) { + callback(); + } + } +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/layers_ui.js b/wormbrowser-appengine/src/main/webapp/scripts/layers_ui.js new file mode 100644 index 0000000..0ff43f4 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/layers_ui.js @@ -0,0 +1,363 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Layer controls for main UI of open-3d-viewer. + */ +// TODO(arthurb): Objectify. +o3v.LayersUI = function(layerOpacityManager) { + this.layerOpacityManager_ = layerOpacityManager; + + this.singleSlider_ = new o3v.LayersUI.SingleSlider(layerOpacityManager); + this.multiSlider_ = new o3v.LayersUI.MultiSlider(layerOpacityManager); + this.icons_ = new o3v.LayersUI.Icons(layerOpacityManager); + this.sliderToggle_ = new o3v.LayersUI.SliderToggle(this.singleSlider_, + this.multiSlider_); +}; + +o3v.LayersUI.ICON_WIDTH = 45; +o3v.LayersUI.ICON_HEIGHT = 47; + +/* + * Builds all slider UI. + */ +o3v.LayersUI.prototype.buildAll = function(putBelow, numLayers, iconFile) { + this.singleSlider_.build(putBelow, numLayers); + this.multiSlider_.build(putBelow, numLayers); + this.icons_.build(putBelow, numLayers, iconFile); + this.sliderToggle_.build(this.icons_.getLastIcon()); +}; + +/* + * A slider with one handle. Moving the slider from one end to the other + * transitions the model from 100% transparent (i.e., invisible) to 100% + * opaque. + */ +o3v.LayersUI.SingleSlider = function(layerOpacityManager) { + this.layerOpacityManager_ = layerOpacityManager; + + this.updateCallback_ = this.update.bind(this); + + this.slider = null; + this.range = 10000; + this.numLayers = 0; + this.HANDLE_WIDTH = 51; +}; + +o3v.LayersUI.SingleSlider.prototype.build = function(putBelow, numLayers) { + this.numLayers = numLayers; + if (this.slider) { + this.slider.remove(); + } + + this.slider = $('
').appendTo('body').slider({ + orientation: 'vertical', + range: 'min', + min: 0, + max: this.range, + value: this.range, + slide: function (event, ui) { + this.setOpacitiesFromFraction(ui.value / this.range); + }.bind(this), + stop: function() { + document.activeElement.blur(); // take focus off slider handle + } + }).css({ + 'position': 'absolute', + 'border': 'none', + 'border-left': '2px solid #466a15', + 'border-right': '2px solid #466a15', + 'border-radius': 0, + 'background': 'none', + 'width': o3v.LayersUI.ICON_WIDTH + 'px', + 'height': (this.numLayers * o3v.LayersUI.ICON_HEIGHT) + 'px', + 'z-index': o3v.uiSettings.ZINDEX_MAINUI + }).position({ + my: 'top', + at: 'bottom', + of: putBelow, + collision: 'none' + }); + + var sliderNodes = this.slider.get(0).childNodes; + var sliderBgStyle = sliderNodes[0].style; + sliderBgStyle.background = 'none'; + var sliderHandleStyle = sliderNodes[1].style; + sliderHandleStyle.width = this.HANDLE_WIDTH + 'px'; + sliderHandleStyle.opacity = 0.7; + sliderHandleStyle.outlineStyle = 'none'; + + this.setOpacitiesFromFraction(1.0); + + this.layerOpacityManager_.addView(this.updateCallback_); +}; + +o3v.LayersUI.SingleSlider.prototype.setOpacitiesFromFraction = + function (fraction) { + var scaled = fraction * this.numLayers; + var opacities = []; + for (var i = 0; i < this.numLayers; ++i) { + if (scaled <= i) { + opacities.push(0.0); + } else if (scaled >= i + 1) { + opacities.push(1.0); + } else { + opacities.push(scaled - i); + } + } + this.layerOpacityManager_.setLayerOpacities(opacities, this.updateCallback_); +}; + +o3v.LayersUI.SingleSlider.prototype.show = function(show) { + this.slider.css({ + 'visibility': show ? 'visible' : 'hidden' + }); +}; + +o3v.LayersUI.SingleSlider.prototype.update = function () { + var opacities = this.layerOpacityManager_.getLayerOpacities(); + var numLayers = opacities.length; + var fraction = 0; + for (var i = numLayers - 1; i > -1; --i) { + if (opacities[i] > 0) { + fraction = (i + opacities[i]) / numLayers; + break; + } + } + this.slider.slider('value', fraction * this.range); +}; + +/* + * A collection of sliders, one handle per layer. Moving each slider from one + * end to the other transitions just that layer from 100% transparent (i.e. + * invisible) to 100% opaque. + */ +o3v.LayersUI.MultiSlider = function(layerOpacityManager) { + this.layerOpacityManager_ = layerOpacityManager; + + this.updateCallback_ = this.update.bind(this); + + this.sliders = null; + this.range = 10000; + this.numLayers = 0; + this.HANDLE_HEIGHT = 43; +}; + +o3v.LayersUI.MultiSlider.prototype.build = function(putBelow, numLayers) { + if (this.sliders) { + for (var i = 0; i < this.numLayers; ++i) { + this.sliders[i].remove(); + } + } + this.sliders = []; + + this.numLayers = numLayers; + for (i = 0; i < this.numLayers; ++i) { + var newSlider = $('
').appendTo('body').slider({ + orientation: 'horizontal', + range: 'min', + min: 0, + max: this.range, + value: this.range, + slide: function (event, ui) { + var layer = (this.sliders.length - 1) - $(event.target).data('id'); + this.layerOpacityManager_.setLayerOpacity( + layer, ui.value / this.range, this.updateCallback_); + }.bind(this), + stop: function() { + document.activeElement.blur(); // take focus off slider handle + } + }).css({ + 'position': 'absolute', + 'border': 'none', + 'border-left': '2px solid #466a15', + 'border-right': '2px solid #466a15', + 'border-radius': 0, + 'background': 'none', + 'visibility': 'hidden', + 'width': o3v.LayersUI.ICON_WIDTH + 'px', + 'height': o3v.LayersUI.ICON_HEIGHT + 'px', + 'z-index': o3v.uiSettings.ZINDEX_MAINUI + }).position({ + my: 'top', + at: 'bottom', + of: i === 0 ? putBelow : this.sliders[i - 1], + collision: 'none' + }).data('id', i); + + this.sliders.push(newSlider); + var sliderNodes = newSlider.get(0).childNodes; + var sliderBgStyle = sliderNodes[0].style; + sliderBgStyle.background = 'none'; + var sliderHandleStyle = sliderNodes[1].style; + sliderHandleStyle.height = this.HANDLE_HEIGHT + 'px'; + sliderHandleStyle.opacity = 0.7; + sliderHandleStyle.outlineStyle = 'none'; + } + + this.layerOpacityManager_.addView(this.updateCallback_); +}; + +o3v.LayersUI.MultiSlider.prototype.show = function(show) { + var numSliders = this.sliders.length; + for (var i = 0; i < numSliders; ++i) { + this.sliders[i].css({ + 'visibility': show ? 'visible' : 'hidden' + }); + } +}; + +o3v.LayersUI.MultiSlider.prototype.update = function() { + var opacities = this.layerOpacityManager_.getLayerOpacities(); + var numSliders = this.sliders.length; + for (var i = 0; i < numSliders; ++i) { + var layer = (numSliders - 1) - i; + this.sliders[i].slider('value', opacities[layer] * this.range); + } +}; + +/* + * A stack of decorative icons that sit under the layer sliders and change + * opacity as the layers do. + */ +o3v.LayersUI.Icons = function(layerOpacityManager) { + this.layerOpacityManager_ = layerOpacityManager; + + this.updateCallback_ = this.update.bind(this); + + this.activeIcons = []; + this.inactiveIcons = []; + this.lastIcon = null; +}; + +o3v.LayersUI.Icons.prototype.getLastIcon = function() { + return this.lastIcon; +}; + +o3v.LayersUI.Icons.prototype.build = function(putBelow, numLayers, iconFile) { + if (this.activeIcons) { + var numIcons = this.activeIcons.length; + for (var i = 0; i < numIcons; ++i) { + this.activeIcons[i].remove(); + this.inactiveIcons[i].remove(); + } + this.activeIcons = []; + this.inactiveIcons = []; + } + + for (i = 0; i < numLayers; ++i) { + var offsetTop = i * o3v.LayersUI.ICON_HEIGHT; + + var inactiveIcon = $('
').appendTo('body').css({ + 'position': 'absolute', + 'width': o3v.LayersUI.ICON_WIDTH + 'px', + 'height': o3v.LayersUI.ICON_HEIGHT + 'px', + 'background-image': 'url(' + iconFile + ')', + 'background-position': '0px -' + offsetTop + 'px', + 'z-index': o3v.uiSettings.ZINDEX_MAINUI_STATUS_LOWER + }); + this.inactiveIcons.push(inactiveIcon); + inactiveIcon.position({ + my: 'top', + at: 'bottom', + of: i === 0 ? putBelow : this.inactiveIcons[i - 1], + collision: 'none' + }); + + var activeIcon = $('
').appendTo('body').css({ + 'position': 'absolute', + 'width': o3v.LayersUI.ICON_WIDTH + 'px', + 'height': o3v.LayersUI.ICON_HEIGHT + 'px', + 'background-image': 'url(' + iconFile + ')', + 'background-position': '-' + o3v.LayersUI.ICON_WIDTH + 'px -' + offsetTop + 'px', + 'z-index': o3v.uiSettings.ZINDEX_MAINUI_STATUS_UPPER + }); + this.activeIcons.push(activeIcon); + activeIcon.position({ + my: 'top', + at: 'bottom', + of: i === 0 ? putBelow : this.activeIcons[i - 1], + collision: 'none' + }); + } + + this.lastIcon = this.activeIcons[this.activeIcons.length - 1]; + this.layerOpacityManager_.addView(this.updateCallback_); +}; + +/* + * Updates icons based on layer opacities. + */ + o3v.LayersUI.Icons.prototype.update = function() { + var opacities = this.layerOpacityManager_.getLayerOpacities(); + var numIcons = this.activeIcons.length; + for (var i = 0; i < numIcons; ++i) { + var layer = (numIcons - 1) - i; + this.activeIcons[i].get(0).style.opacity = opacities[layer]; + } + }; + +/* + * A button that switches between single- and multiple-slider modes. + */ +o3v.LayersUI.SliderToggle = function(singleSlider, multiSlider) { + this.button = null; + this.single = true; + + this.singleSlider_ = singleSlider; + this.multiSlider_ = multiSlider; +}; + +o3v.LayersUI.SliderToggle.prototype.build = function (lastIcon) { + if (this.button) { + this.button.remove(); + } + + this.button = $('
').appendTo('body').css({ + 'position': 'absolute', + 'width': '45px', + 'height': '50px', + 'border-left': '2px solid #466a15', + 'border-bottom-left-radius': '16px', + 'border-bottom-right-radius': '16px', + 'border-bottom': '2px solid #466a15', + 'border-right': '2px solid #466a15', + 'border-top': '1px solid #466a15', + 'background-position': 'center center', + 'background-repeat': 'no-repeat', + 'background-color': '#fff', + 'z-index': o3v.uiSettings.ZINDEX_MAINUI_STATUS_UPPER + }).position({ + my: 'top', + at: 'bottom', + of: lastIcon, + collision: 'none' + }).click(this.toggleSliders.bind(this)); + + this.setArt(); +}; + +o3v.LayersUI.SliderToggle.prototype.setArt = function() { + this.button.css({ + 'background-image': this.single ? 'url(img/toggle_single_slider.png)' : 'url(img/toggle_multiple_sliders.png)' + }); +}; + +o3v.LayersUI.SliderToggle.prototype.toggleSliders = function() { + this.single = !this.single; + this.setArt(); + + this.singleSlider_.show(this.single); + this.multiSlider_.show(!this.single); +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/loader.js b/wormbrowser-appengine/src/main/webapp/scripts/loader.js new file mode 100755 index 0000000..3e39bb6 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/loader.js @@ -0,0 +1,200 @@ +'use strict'; + +// Contains objects like: +// name: { +// materials: { 'material_name': { ... } ... }, +// decodeParams: { +// decodeOffsets: [ ... ], +// decodeScales: [ ... ], +// }, +// urls: { +// 'url': [ +// { material: 'material_name', +// attribRange: [#, #], +// indexRange: [#, #], +// names: [ 'object names' ... ], +// lengths: [#, #, # ... ] +// } +// ], +// ... +// } +// } +var MODELS = {}; + +var DEFAULT_ATTRIB_ARRAYS = [ + { name: "a_position", + size: 3, + stride: 8, + offset: 0 + }, + { name: "a_texcoord", + size: 2, + stride: 8, + offset: 3 + }, + { name: "a_normal", + size: 3, + stride: 8, + offset: 5 + }, + { name: "a_colorIndex", + size: 1, + stride: 1, + offset: 0 + } +]; + +var BBOX_ATTRIB_ARRAYS = [ + { name: "a_position", + size: 3, + stride: 6, + offset: 0 + }, + { name: "a_radius", + size: 3, + stride: 6, + offset: 3 + } +]; + +var DEFAULT_DECODE_PARAMS = { + decodeOffsets: [-4095, -4095, -4095, 0, 0, -511, -511, -511], + decodeScales: [1/8191, 1/8191, 1/8191, 1/1023, 1/1023, 1/1023, 1/1023, 1/1023] +}; + +// TODO: will it be an optimization to specialize this method at +// runtime for different combinations of stride, decodeOffset and +// decodeScale? +function decompressAttribsInner_(str, inputStart, inputEnd, + output, outputStart, stride, + decodeOffset, decodeScale) { + var prev = 0; + for (var j = inputStart; j < inputEnd; j++) { + var code = str.charCodeAt(j); + prev += (code >> 1) ^ (-(code & 1)); + output[outputStart] = decodeScale * (prev + decodeOffset); + outputStart += stride; + } +} + +function decompressIndices_(str, inputStart, numIndices, + output, outputStart) { + var highest = 0; + for (var i = 0; i < numIndices; i++) { + var code = str.charCodeAt(inputStart++); + output[outputStart++] = highest - code; + if (code == 0) { + highest++; + } + } +} + +function decompressAABBs_(str, inputStart, numBBoxen, + decodeOffsets, decodeScales) { + var numFloats = 6 * numBBoxen; + var inputEnd = inputStart + numFloats; + var bboxen = new Float32Array(numFloats); + var outputStart = 0; + for (var i = inputStart; i < inputEnd; i += 6) { + var minX = str.charCodeAt(i + 0) + decodeOffsets[0]; + var minY = str.charCodeAt(i + 1) + decodeOffsets[1]; + var minZ = str.charCodeAt(i + 2) + decodeOffsets[2]; + var diaX = str.charCodeAt(i + 3) + 1; + var diaY = str.charCodeAt(i + 4) + 1; + var diaZ = str.charCodeAt(i + 5) + 1; + bboxen[outputStart++] = decodeScales[0] * minX; + bboxen[outputStart++] = decodeScales[1] * minY; + bboxen[outputStart++] = decodeScales[2] * minZ; + bboxen[outputStart++] = decodeScales[0] * (minX + diaX); + bboxen[outputStart++] = decodeScales[1] * (minY + diaY); + bboxen[outputStart++] = decodeScales[2] * (minZ + diaZ); + } + return bboxen; +} + +function decompressMesh(str, meshParams, decodeParams, callback) { + // Extract conversion parameters from attribArrays. + var stride = decodeParams.decodeScales.length; + var decodeOffsets = decodeParams.decodeOffsets; + var decodeScales = decodeParams.decodeScales; + var attribStart = meshParams.attribRange[0]; + var numVerts = meshParams.attribRange[1]; + + // Decode attributes. + var inputOffset = attribStart; + var attribsOut = new Float32Array(stride * numVerts); + for (var j = 0; j < stride; j++) { + var end = inputOffset + numVerts; + var decodeScale = decodeScales[j]; + if (decodeScale) { + // Assume if decodeScale is never set, simply ignore the + // attribute. + decompressAttribsInner_(str, inputOffset, end, + attribsOut, j, stride, + decodeOffsets[j], decodeScale); + } + inputOffset = end; + } + + var indexStart = meshParams.indexRange[0]; + var numIndices = 3*meshParams.indexRange[1]; + var indicesOut = new Uint16Array(numIndices); + decompressIndices_(str, inputOffset, numIndices, indicesOut, 0); + + // Decode bboxen. + var bboxen = undefined; + var bboxOffset = meshParams.bboxes; + if (bboxOffset) { + bboxen = decompressAABBs_(str, bboxOffset, meshParams.names.length, + decodeOffsets, decodeScales); + } + callback(attribsOut, indicesOut, bboxen, meshParams); +} + +function downloadMesh(path, meshEntry, decodeParams, callback) { + var idx = 0; + function onprogress(req, e) { + while (idx < meshEntry.length) { + var meshParams = meshEntry[idx]; + var meshEnd = meshParams.bboxes + 6*meshParams.names.length; + if (req.responseText.length < meshEnd) break; + + decompressMesh(req.responseText, meshParams, decodeParams, callback); + ++idx; + } + }; + getHttpRequest(path, function(req, e) { + if (req.status === 200 || req.status === 0) { + onprogress(req, e); + } + // TODO: handle errors. + }, onprogress); +} + +function downloadMeshes(path, meshUrlMap, decodeParams, callback) { + for (var url in meshUrlMap) { + var meshEntry = meshUrlMap[url]; + downloadMesh(path + url, meshEntry, decodeParams, callback); + } +} + +function downloadModel(path, model, partialCallback, fullCallback) { + var model = MODELS[model]; + var pendingCount = 0; + o3v.util.forEach( + model.urls, + function(meshEntry) { + pendingCount += meshEntry.length; + }); + var callback = function(attribs, indices, bboxen, meshEntiy) { + if (partialCallback !== undefined) { + partialCallback(attribs, indices, bboxen, meshEntiy); + } + pendingCount = pendingCount - 1; + if (pendingCount == 0 && fullCallback !== undefined) { + fullCallback(); + } + }; + + downloadMeshes(path, model.urls, model.decodeParams, callback); +} diff --git a/wormbrowser-appengine/src/main/webapp/scripts/main_ui.js b/wormbrowser-appengine/src/main/webapp/scripts/main_ui.js new file mode 100644 index 0000000..0bea52a --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/main_ui.js @@ -0,0 +1,81 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview GUI elements of the page (buttons, slider, etc). + */ + +o3v.MainUI = function(nextModelCallback) { + var OPEN_WORM_URL = "http://openworm.org"; + + // Canvas. + $('').appendTo('body').css({ + 'position': 'absolute', + 'width': '100%', + 'height': '100%', + 'z-index': o3v.uiSettings.ZINDEX_VIEWER + }).attr('id', 'viewer'); + this.canvas_ = $('#viewer')[0]; + this.canvas_.onselectstart = function() {return false;}; + this.canvas_.onmousedown = function() {return false;}; + + // Logo. + $('').appendTo('body').css({ + 'position': 'absolute', + 'left': '8px', + 'top': '10px', + 'cursor': 'pointer', + 'z-index': o3v.uiSettings.ZINDEX_MAINUI + }).click(function () { + window.open(OPEN_WORM_URL); + }); + + // Model selector. + this.modelBtn_ = $('
').appendTo('body').css({ + 'position': 'absolute', + 'left': '17px', + 'top': '230px', + 'width': '45px', + 'height': '50px', + 'border-left': '2px solid #466a15', + 'border-top-left-radius': '16px', + 'border-top-right-radius': '16px', + 'border-top': '2px solid #466a15', + 'border-right': '2px solid #466a15', + 'border-bottom': '1px solid #466a15', + 'background-position': 'center center', + 'background-repeat': 'no-repeat', + 'background-color': '#fff', + 'z-index': o3v.uiSettings.ZINDEX_MAINUI + }).click(nextModelCallback); +}; + +o3v.MainUI.prototype.setModelSelectorButton = function(iconFile) { + this.modelBtn_.css({ + 'background-image': 'url(' + iconFile + ')' + }); +}; + +o3v.MainUI.prototype.getCanvas = function() { + return this.canvas_; +}; + +o3v.MainUI.prototype.showLoadingFeedback = function(show) { + document.getElementById('loading-feedback').style.visibility = + show ? 'visible' : 'hidden'; +}; + +o3v.MainUI.prototype.getLastButton = function() { + return this.modelBtn_.get(0); +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/models.js b/wormbrowser-appengine/src/main/webapp/scripts/models.js new file mode 100644 index 0000000..81eab04 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/models.js @@ -0,0 +1,32 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Information about the models that open-3d-viewer will display. + */ + +/* ====================================================================== */ +/* ADD YOUR MODELS HERE */ +/* ====================================================================== */ + +o3v.MODELS = [ +{ + name: 'Virtual_Worm_February_2012.obj', + scriptName: 'Virtual_Worm_February_2012.js', + modelPath: 'models/Virtual_Worm/', + metadataFile: 'entity_metadata.json', + texturePath: 'models/common/', + numLayers: 4 + } +]; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/nav_ui.js b/wormbrowser-appengine/src/main/webapp/scripts/nav_ui.js new file mode 100644 index 0000000..b1f9bf0 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/nav_ui.js @@ -0,0 +1,132 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Navigational controls for main UI of open-3d-viewer. + */ +o3v.navUI = function(reset, move, zoom) { + homeBtn = null; + upBtn = null; + leftBtn = null; + rightBtn = null; + downBtn = null; + zoomIn = null; + zoomOut = null; + + this.reset_ = reset; + this.move_ = move; + this.zoom_ = zoom; + + var navBtnStyles = { + 'position': 'absolute', + 'width': '20px', + 'height': '20px', + 'z-index': o3v.uiSettings.ZINDEX_MAINUI + }; + + this.navHome = $('
').appendTo('body').css(navBtnStyles).css({ + 'left': '30px', + 'top': '114px' + }).button({ + icons: { + primary: 'ui-icon-home' + }, + text: false + }).click(function () { + this.reset_(); + }.bind(this)); + var homeEl = this.navHome.get(0); + this.navUp = $('
').appendTo('body').css(navBtnStyles).button({ + icons: { + primary: 'ui-icon-triangle-1-n' + }, + text: false + }).position({ + my: 'bottom', + at: 'top', + of: homeEl, + collision: 'none' + }).click(function () { + this.move_(0, -o3v.navUI.MOVE_FACTOR); + }.bind(this)); + this.navLeft = $('
').appendTo('body').css(navBtnStyles).button({ + icons: { + primary: 'ui-icon-triangle-1-w' + }, + text: false + }).position({ + my: 'right', + at: 'left', + of: homeEl, + collision: 'none' + }).click(function () { + this.move_(-o3v.navUI.MOVE_FACTOR, 0); + }.bind(this)); + this.navRight = $('
').appendTo('body').css(navBtnStyles).button({ + icons: { + primary: 'ui-icon-triangle-1-e' + }, + text: false + }).position({ + my: 'left', + at: 'right', + of: homeEl, + collision: 'none' + }).click(function () { + this.move_(o3v.navUI.MOVE_FACTOR, 0); + }.bind(this)); + this.navDown = $('
').appendTo('body').css(navBtnStyles).button({ + icons: { + primary: 'ui-icon-triangle-1-s' + }, + text: false + }).position({ + my: 'top', + at: 'bottom', + of: homeEl, + collision: 'none' + }).click(function () { + this.move_(0, o3v.navUI.MOVE_FACTOR); + }.bind(this)); + this.navZoomIn = $('
').appendTo('body').css(navBtnStyles).button({ + icons: { + primary: 'ui-icon-plus' + }, + text: false + }).position({ + my: 'top', + at: 'bottom', + of: this.navDown.get(0), + offset: '0 16', + collision: 'none' + }).click(function () { + this.zoom_(0, o3v.navUI.ZOOM_FACTOR); + }.bind(this)); + this.navZoomOut = $('
').appendTo('body').css(navBtnStyles).button({ + icons: { + primary: 'ui-icon-minus' + }, + text: false + }).position({ + my: 'top', + at: 'bottom', + of: this.navZoomIn.get(0), + collision: 'none' + }).click(function () { + this.zoom_(0, -o3v.navUI.ZOOM_FACTOR); + }.bind(this)); +}; + +o3v.navUI.MOVE_FACTOR = 5; +o3v.navUI.ZOOM_FACTOR = 50; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/navigate.js b/wormbrowser-appengine/src/main/webapp/scripts/navigate.js new file mode 100644 index 0000000..6cb95d6 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/navigate.js @@ -0,0 +1,597 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview JavaScript to navigate. + */ + +// TODO(dkogan): Replace this with generic functions in the mat4 namespace. +o3v.math = {}; +/** + * Subtracts two vectors. + * @param {!tdl.math.Vector} a Operand vector. + * @param {!tdl.math.Vector | Float32Array} b Operand vector. + * @return {!tdl.math.Vector} The difference of a and b. + */ +o3v.math.subVector = function(a, b) { + var r = []; + var aLength = a.length; + for (var i = 0; i < aLength; ++i) + r[i] = a[i] - b[i]; + return r; +}; +/** + * Converts degrees to radians. + * @param {number} degrees A value in degrees. + * @return {number} the value in radians. + */ +o3v.math.degToRad = function(degrees) { + return degrees * Math.PI / 180; +}; + +/** + * Computes the dot product of two vectors; assumes that a and b have + * the same dimension. + * @param {!tdl.math.Vector} a Operand vector. + * @param {!tdl.math.Vector} b Operand vector. + * @return {number} The dot product of a and b. + */ +o3v.math.dot = function(a, b) { + var r = 0.0; + var aLength = a.length; + for (var i = 0; i < aLength; ++i) + r += a[i] * b[i]; + return r; +}; +/** + * Computes the cross product of two vectors; assumes both vectors have + * three entries. + * @param {!tdl.math.Vector} a Operand vector. + * @param {!tdl.math.Vector} b Operand vector. + * @return {!tdl.math.Vector} The vector a cross b. + */ +o3v.math.cross = function(a, b) { + return [a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0]]; +}; +/** + * Divides a vector by its Euclidean length and returns the quotient. + * @param {!tdl.math.Vector} a The vector. + * @return {!tdl.math.Vector} The normalized vector. + */ +o3v.math.normalize = function(a) { + var r = []; + var n = 0.0; + var aLength = a.length; + for (var i = 0; i < aLength; ++i) + n += a[i] * a[i]; + n = Math.sqrt(n); + if (n > 0.00001) { + for (var i = 0; i < aLength; ++i) + r[i] = a[i] / n; + } else { + r = [0,0,0]; + } + return r; +}; + +o3v.Navigator = function(changeCallback, canvas, history) { + this.changeCallback_ = changeCallback; + this.canvas_ = canvas; + this.rootBbox_ = null; + + this.camera = {}; + this.originCamera = {}; + + // When to start doing capsule top rotation + this.rotationStartPercent = 0.01; + this.entityCapsule = false; + + this.interpolants = []; + this.dolly = {}; + this.theta = {}; + this.phi = 0; + + // Constants for reducing the deltas for movement + this.rotationReduction = 0.2; + this.zoomReduction = 0.05; + this.verticalReduction = 0.1; + + // Constants for limits on movement + this.verticalAdjustmentPercent = 0.9; + this.verticalAdjustment = 100; // set in setNavParameters + this.vertMaxLimit = {}; // set in setNavParameters, init 150 + this.vertMinLimit = {}; // set in setNavParameters, init 0 + this.zoomNearLimit = -10; //0.1; // set in setNavParameters + this.zoomFarLimit = 250; // set in setNavParameters + this.startPan = 0.1; + this.center = [0, 0, 0]; // set in setNavParameters, init 0,0,0 + this.cameraTargetX = {}; + this.cameraTargetY = {}; + this.cameraTargetZ = {}; + this.z_dist = 0; + + // variables for making default views look good + this.sagittalPlane = 0; + this.coronalPlane = -1; + this.artisticOffset = .2; + this.lengthRatioComparison = .75; + + // variables for dealing with varying sized entities + this.minimumCapsuleHeight = 3; + this.minZoomValue = 10.0; + this.maxZoomValue = 100.0; + this.zoomAmplificationFactor = 1.5; + this.zoomPercent = 0.75; + + // Math constants + // TODO(dkogan): Pull these out into the class instead of per-object. + this.M_PI = Math.PI; + this.M_2PI = 2 * Math.PI; + + // Initialize. + this.theta = new o3v.Interpolant(Math.PI / 2, this.interpolants); + this.dolly.x = new o3v.Interpolant(0.0, this.interpolants); + this.dolly.y = new o3v.Interpolant(120.0, this.interpolants); + this.dolly.z = new o3v.Interpolant(160.0, this.interpolants); + + this.initializeCamera(); + + // Register with history. + this.state_ = ''; + this.history = history; + this.history.register('nav', + this.getState.bind(this), this.restoreState.bind(this)); +}; + +o3v.Navigator.prototype.getCamera = function() { + return this.camera; +}; + +o3v.Navigator.prototype.setOriginCameraAndModelRoot = function(rootBbox) { + // Save the original camera for reset and for offsets with calculations + var cx = 0.5 * (rootBbox[3] + rootBbox[0]); + var cy = 0.5 * (rootBbox[4] + rootBbox[1]); + var cz = 0.5 * (rootBbox[5] + rootBbox[2]); + // A reasonable z value for eye because we need a default. + this.camera = { eye: new Float32Array([cx, cy, 160]), + target: new Float32Array([cx, cy, cz]), + up: new Float32Array([0, 1, 0]), + fov: 40}; + this.resetModel(rootBbox); +}; + +o3v.Navigator.prototype.resetModel = function(rootBbox) { + this.rootBbox_ = rootBbox; + this.resetNavParameters(); +}; + +// Resets the camera to the original position +o3v.Navigator.prototype.reset = function(addToHistory) { + var nav_vals = this.calculateNavigateValues(this.rootBbox_); + this.doNavigate(nav_vals.x, nav_vals.y, nav_vals.z, addToHistory); + this.resetNavParameters(); +}; + +o3v.Navigator.prototype.initializeCamera = function() { + this.vertMaxLimit = new o3v.Interpolant(150.0, this.interpolants); + this.vertMinLimit = new o3v.Interpolant(0.0, this.interpolants); + this.cameraTargetX = new o3v.Interpolant(0.0, this.interpolants); + this.cameraTargetY = new o3v.Interpolant(0.0, this.interpolants); + this.cameraTargetZ = new o3v.Interpolant(0.0, this.interpolants); + this.setOriginCameraAndModelRoot([-200,-200,-200,-200,-200,-200]); +}; + +o3v.Navigator.prototype.setNavParametersToBbox = function(bbox) { + var center = new Float32Array(3); + center[0] = 0.5 * (bbox[0] + bbox[3]); + center[1] = 0.5 * (bbox[1] + bbox[4]); + center[2] = 0.5 * (bbox[2] + bbox[5]); + this.setNavParameters(bbox[4], + bbox[1], + this.zoomNearLimit, + this.zoomFarLimit, + center, + 0); +}; + +// Puts in the camera default parameters for the full body +o3v.Navigator.prototype.resetNavParameters = function() { + var bbox = this.rootBbox_; + if (!bbox) { + return; + } + this.setNavParametersToBbox(bbox); + this.changeCallback_(true); +}; + +o3v.Navigator.prototype.setNavParameters = function(verticalMaxLimit_input, + verticalMinLimit_input, + zoomNearLimit_input, + zoomFarLimit_input, + center_input, + z_dist_input) { + var vMaxLimit = verticalMaxLimit_input; + var vMinLimit = verticalMinLimit_input; + var span = verticalMaxLimit_input - verticalMinLimit_input; + if (span < this.minimumCapsuleHeight) { + var difference = (this.minimumCapsuleHeight - span) / 2; + vMaxLimit = vMaxLimit + difference; + vMinLimit = vMinLimit - difference; + } + this.vertMaxLimit.setFuture(vMaxLimit); + this.vertMinLimit.setFuture(vMinLimit); + this.verticalAdjustment = (this.verticalAdjustmentPercent * + (verticalMaxLimit_input - verticalMinLimit_input)); + this.zoomNearLimit = zoomNearLimit_input; + this.zoomFarLimit = zoomFarLimit_input; + var cx = center_input[0]; + var cy = center_input[1]; + var cz = center_input[2]; + this.cameraTargetX.setFuture(cx); + this.cameraTargetY.setFuture(cy); + this.cameraTargetZ.setFuture(cz); + this.center = center_input; + this.z_dist = z_dist_input; +}; + +// Returns string representing current state. +o3v.Navigator.prototype.getState = function() { + return this.state_; +}; + +// Restores state. +o3v.Navigator.prototype.restoreState = function(state) { + if (state) { + var tuple = state.split(','); + this.doNavigate(parseFloat(tuple[0]), + parseFloat(tuple[1]), + parseFloat(tuple[2]), false); + } else { + this.reset(false); + } +}; + +o3v.Navigator.prototype.projectedMinMax = function(bbox, projectionVector) { + var verts = [[bbox[0], bbox[1], bbox[2]], + [bbox[0], bbox[4], bbox[2]], + [bbox[0], bbox[1], bbox[5]], + [bbox[0], bbox[4], bbox[5]], + [bbox[3], bbox[1], bbox[2]], + [bbox[3], bbox[4], bbox[2]], + [bbox[3], bbox[1], bbox[5]], + [bbox[3], bbox[4], bbox[5]]]; + + var proj = []; + for (var v = 0; v < 8; v++) { + var vertVector = o3v.math.subVector(verts[v], this.camera.eye); + proj[v] = o3v.math.dot(projectionVector, vertVector); + } + + var maxVal = -Number.MAX_VALUE; + var minVal = Number.MAX_VALUE; + for (var v = 0; v < 8; v++) { + maxVal = Math.max(maxVal, proj[v]); + minVal = Math.min(minVal, proj[v]); + } + + return maxVal - minVal; +}; + +o3v.Navigator.prototype.unifyBoundingBoxes = function(entityIdToEntity) { + var bbox; + o3v.log.info('focusing on entities', entityIdToEntity); + o3v.util.forEach(entityIdToEntity, function(entity) { + bbox = o3v.growBBox(bbox, entity.bbox); + }); + return bbox; +}; + +o3v.Navigator.prototype.focusOnEntities = function(entityIdToEntity) { + var bbox; + if (o3v.util.isEmpty(entityIdToEntity)) { + o3v.log.info('focusOnEntities empty; resetting view'); + this.resetNavParameters(); + } else { + o3v.log.info('focusing on entities', entityIdToEntity); + + bbox = this.unifyBoundingBoxes(entityIdToEntity); + this.setNavParametersToBbox(bbox); + } + return bbox; +}; + +o3v.Navigator.prototype.goToBBox = function(bbox, opt_verticalOnly) { + var nav_vals = this.calculateNavigateValues(bbox, opt_verticalOnly); + this.doNavigate(nav_vals.x, nav_vals.y, nav_vals.z, false); +}; + +o3v.Navigator.prototype.calculateNavigateValues = function(bbox, opt_verticalOnly) { + // lengths + var dx = bbox[0] - bbox[3]; + var dy = bbox[1] - bbox[4]; + var dz = bbox[2] - bbox[5]; + + // centers + o3v.log.info('center', this.center); + var cx = 0.5 * (bbox[0] + bbox[3]); + var cy = 0.5 * (bbox[1] + bbox[4]); + var cz = 0.5 * (bbox[2] + bbox[5]); + + var dYAxis = Math.sqrt(cx * cx + cz * cz); + + // axes: x goes right + // y goes up + // z toward viewer + + // x = angle around the y axis + // y = height + // z = zoom + var x = Math.atan2(dx, dz); + var lengthRatio = dx / dz; + o3v.log.info('ratio: ', lengthRatio); + o3v.log.info('cz: ', cz); + if (lengthRatio > this.lengthRatioComparison) { + // We are greater in the x direction so look head on + x = Math.PI / 2; + // if we're behind, look from behind + if (cz < this.coronalPlane) { + x = -Math.PI / 2; + } + } + else { + x = 0; + if (cx < this.sagittalPlane) { + x = Math.PI; + } + } + //determine the artistic offset + if ((lengthRatio > 1 && cx > this.sagittalPlane) || + (lengthRatio < 1 && cx < this.sagittalPlane)) { + x -= this.artisticOffset; + } + else { + x += this.artisticOffset; + } + + // ideal Y + var projectedHeight = this.projectedMinMax(bbox, this.camera.up); + var y_angle = this.zoomPercent * o3v.math.degToRad(this.camera.fov); + var zy_dist = projectedHeight / Math.tan(y_angle); + + // ideal X + var sideVector = o3v.math.cross(this.camera.up, + o3v.math.subVector(this.camera.eye, + this.camera.target)); + sideVector = o3v.math.normalize(sideVector); + var projectedWidth = this.projectedMinMax(bbox, sideVector); + var x_angle = this.zoomPercent * o3v.math.degToRad( + this.camera.fov * + this.canvas_['clientWidth'] / this.canvas_['clientHeight']); + + var zx_dist = projectedWidth / Math.tan(x_angle); + + var z_dist = Math.max(zy_dist, zx_dist); + + // Normalize between 0 and 1 + var normalizedZoom = ((z_dist - this.minZoomValue) / + (this.maxZoomValue - this.minZoomValue)); + normalizedZoom = Math.max(0, Math.min(1, normalizedZoom)); + + // Renormalize to account for the disired zoom factor for small entities + var reNormalizedZoom = ((normalizedZoom * + (1 - 1 / this.zoomAmplificationFactor)) + + 1 / this.zoomAmplificationFactor); + + // Divide by the renormalized value to take into account small entities + var clampedZoom = Math.max(this.minZoomValue, z_dist); + var finalZoom = clampedZoom / reNormalizedZoom; + + var y_value = cy; + var zoom_radius = dYAxis + finalZoom; + + // TODO(dkogan|rlp): Make this hack cleaner. + if (opt_verticalOnly) { + x = this.theta.getFuture(); + zoom_radius = this.dolly.z.getFuture(); + } + + return {x: x, y: y_value, z: zoom_radius}; +}; + +// Clamps a value between -absLimit and absLimit. Useful for clamping rotation +// in two different directions +o3v.Navigator.prototype.absClamp = function(value, absLimit) { + var val = value; + if (val > absLimit) + val = absLimit; + else if (val < -absLimit) + val = -absLimit; + return val; +}; + +// The opposite of absClamp: if the current value is between -absLimit and +// absLimit then it returns the newValue. Useful for ignoring a value until +// it reaches a certain threshold. +o3v.Navigator.prototype.absLimit = function(value, absLimit, newValue) { + if (value < absLimit && value > -absLimit) + return newValue; + return value; +}; + +o3v.Navigator.prototype.recalculate = function() { + var anyUpdates = o3v.Interpolant.tweenAll(this.interpolants); + // Camera rotates and translates around the body. Body always considered + // to be at the origin. + var angle = this.theta.getPresent(); + var z_val = this.dolly.z.getPresent(); + var y_val = this.dolly.y.getPresent(); + var verticalMaxLimit = this.vertMaxLimit.getPresent(); + var verticalMinLimit = this.vertMinLimit.getPresent(); + + // this.center[0] = this.center[2] = 0 for normal position + var cx = z_val * Math.cos(angle) + this.cameraTargetX.getPresent(); + var cy = y_val; + var cz = z_val * Math.sin(angle) + this.cameraTargetZ.getPresent(); + var ty = y_val; + var verticalPanLimit = verticalMaxLimit - verticalMinLimit; + var rotLimit = this.rotationStartPercent * verticalPanLimit; + + var phi_multiplier = 0; + var vertDist = cy; + var topStartRotation = verticalMaxLimit - rotLimit; + var bottomStartRotation = verticalMinLimit + rotLimit; + + // Determine if we're in the top hemisphere or lower hemisphere + if (cy < bottomStartRotation) { + phi_multiplier = -1; + ty = bottomStartRotation; + vertDist = this.absClamp(rotLimit + (verticalMinLimit - cy), + this.verticalAdjustment + rotLimit); + } else if (cy > topStartRotation) { + phi_multiplier = 1; + ty = topStartRotation; + vertDist = this.absClamp(rotLimit - (verticalMaxLimit - cy), + this.verticalAdjustment + rotLimit); + } + // If we are in a hemisphere, adjust our camera accordingly + // TODO(rlp): This kills off capsule mode, but capsule mode is killing us + // during transitions, and I don't know how to make it not happen + // during those. Please fix and reinstate. + if (phi_multiplier) { + var phi = (phi_multiplier * Math.PI / 2 * + (vertDist / (this.verticalAdjustment + rotLimit))); + // Fix camera position to account for rotation + cx *= Math.cos(phi); + cy = ty + z_val * Math.sin(phi); + cz *= Math.cos(phi); + + var up_phi = Math.PI / 2 - phi; + this.camera.up = [-Math.cos(angle) * Math.cos(up_phi), + Math.sin(up_phi), + -Math.sin(angle) * Math.cos(up_phi)]; + } + else { + this.camera.up = [0, 1, 0]; + } + // TODO(rlp): If arcball do something different -- different target + this.camera.eye = [cx, cy, cz]; + this.camera.target = [this.center[0], ty, this.center[2]]; + + return anyUpdates; +}; + +// Navigates to a location. +// This function is ultimately always called if we change something so we call +// recalculate and tell the renderer to update +o3v.Navigator.prototype.doNavigate = function(angle, y, zoom, addToHistory, + opt_camera_scale) { + this.theta.setFuture(angle); + + var camera_scale = (opt_camera_scale) ? opt_camera_scale : 1; + + var verticalLowerLimit = (this.vertMinLimit.getFuture() - + this.verticalAdjustment); + var verticalUpperLimit = (this.vertMaxLimit.getFuture() + + this.verticalAdjustment); + + if (y < verticalLowerLimit) { + y = verticalLowerLimit; + } + if (y > verticalUpperLimit) { + y = verticalUpperLimit; + } + this.dolly.y.setFuture(y); + + if (zoom < this.zoomNearLimit) { + zoom = this.zoomNearLimit; + } + if (zoom > this.zoomFarLimit) { + zoom = this.zoomFarLimit; + } + this.dolly.z.setFuture(zoom); + + this.changeCallback_(); + + // Set up with history. + this.state_ = [Math.round(angle * 100) / 100, + Math.round(y * 100) / 100, + Math.round(zoom * 100) / 100].join(','); + if (addToHistory) { + this.history.update(); + } +}; + +// Navigates to an offset from the current location. +o3v.Navigator.prototype.doNavigateDelta = function(dx, dy, dz, addToHistory) { + var camera_scale = this.dolly.z.getPresent() / 80; + this.doNavigate( + this.theta.getFuture() + camera_scale * dx, + this.dolly.y.getFuture() + camera_scale * dy, + this.dolly.z.getFuture() + camera_scale * dz, + addToHistory, + camera_scale); +}; + +// The primary drag function. It is split into two parts, one for arcball and +// one for schwarma. +// TODO(rlp): There seems to be some lag here: triage/diagnose. +o3v.Navigator.prototype.drag = function(dx, dy, dz) { + // We modulate the deltas by constants to make the movement less jumpy + var deltaRotate = this.rotationReduction * dx; + var deltaPan = this.verticalReduction * dy; + // We limit the delta for panning so that it only occurs if the user + // really intends it to. This eliminates the sort of "bouncy" motion + // while rotating. + deltaPan = this.absLimit(deltaPan, this.startPan, 0); + this.doNavigateDelta(deltaRotate, deltaPan, 0, true); +}; + +// Takes care of the scrolling by changing the z component of our camera +o3v.Navigator.prototype.scroll = function(dx, dy) { + this.doNavigateDelta(0, 0, -dy * this.zoomReduction, true); +}; + +// Angle constraint for rotation angle interpolants +o3v.Navigator.prototype.interpolantAngleConstraint = function(i) { + if (i.present_ > this.M_PI) { + i.present_ -= this.M_2PI; + i.future_ -= this.M_2PI; + } else if (i.present_ < -this.M_PI) { + i.present_ += this.M_2PI; + i.future_ += this.M_2PI; + } + return false; +}; + +// Over constraint for rotation angle interpolants +o3v.Navigator.prototype.interpolantOverConstraint = function(soft, hard) { + var scale = 0.75 / (soft - hard); + return function(i) { + if (i.future_ < hard) { + i.future_ = hard; + } + if (i.future_ < soft) { + if (i.future_ >= (soft - o3v.Interpolant['EPSILON'])) { + i.future_ = soft; + return false; + } + var hard_factor = (1 - hard + i.future_); + i.future_ += scale * hard_factor * (soft - i.future_); + return true; + } + return false; + }; +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/opacity.js b/wormbrowser-appengine/src/main/webapp/scripts/opacity.js new file mode 100644 index 0000000..b1e1aab --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/opacity.js @@ -0,0 +1,385 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Interface between code that sets opacity (opacity slider, + * selection), and the renderer. + */ + +o3v.OpacityManager = function(layerOpacityManager, selectionManager, + changeCallback) { + // Accessor for layer opacity information. + this.layerOpacityManager_ = layerOpacityManager; + this.layerOpacityManager_.addView(this.handleLayerOpacityUpdate.bind(this)); + + // Accessor for selection information. + this.selectionManager_ = selectionManager; + + // Function to call when something changes. + this.changeCallback_ = changeCallback; +}; + +o3v.OpacityManager.prototype.reset = function(entityMetadata) { + this.entityMetadata_ = entityMetadata; + + // Map of : + // Interpolant is a number from 0 to 1 indicating overall layer opacity. + // Note that since a layer may be sectioned into outside/inside parts, + // this number is not the same as base layer opacity returned by + // getLayerOpacityInfo. + this.layerOpacities_ = {}; + + this.layerOpacityInterpolants_ = []; + + var layerNames = this.entityMetadata_.getLayerNames(); + for (var i = 0; i < layerNames.length; ++i) { + this.initLayer_(layerNames[i]); + } +}; + +// Set up layer. +o3v.OpacityManager.prototype.initLayer_ = function(layerName) { + var layerId = this.entityMetadata_.layerNameToId(layerName); + if (!this.layerOpacities_[layerId]) { + this.layerOpacities_[layerId] = new o3v.Interpolant( + 1, this.layerOpacityInterpolants_); + } +}; + +// Gets layer base opacity. +// If a layer is sectioned, base is the opacity of the *most opaque* section. +o3v.OpacityManager.prototype.getLayerBaseOpacity_ = function(layerId) { + var rawOpacity = this.layerOpacities_[layerId].getPresent(); + var sublayers = this.entityMetadata_.getSublayers()[layerId]; + if (sublayers && rawOpacity > 0 && rawOpacity < 1) { + var numSublayers = sublayers.length; + + if ((rawOpacity * numSublayers) < 1.0) { + return rawOpacity * numSublayers; + } else { + return 1.0; + } + } else { + return rawOpacity; + } +}; + +/** + * Gets entity opacities based on layer opacity. + * @param {nubber} layerId Id of the layer. + * @return {Object.} Map of entityId to opacity. + * @private + */ +o3v.OpacityManager.prototype.getOpacityFromLayering_ = + function(layerId) { + var entityToOpacity = {}; + + var rawOpacity = this.layerOpacities_[layerId].getPresent(); + + // The sublayers are in order from the OUTER to the INNER. + var sublayers = this.entityMetadata_.getSublayers()[layerId]; + + // Potentially three sets of entities; any may be empty. + // 1) Opaque + // 2) Transluscent + // 3) Transparent + var numSublayers = sublayers.length; + var opacity = rawOpacity * numSublayers; + + // This is the 'transluscent' sublayer. Every layer below it + // is transparent, every layer above it is opaque. It may be semi-transparent + // or opaque. + var transluscentLayer = Math.floor(numSublayers - opacity); + + // Transparent. + for (var sublayer = 0; sublayer < transluscentLayer; sublayer++) { + sublayers[sublayer].forEach(function(entityId) { + entityToOpacity[entityId] = 0; + }); + } + + // Transluscent or opaque. + if (transluscentLayer < sublayers.length) { + var sublayerOpacity = opacity - (sublayers.length - transluscentLayer - 1); + sublayers[transluscentLayer].forEach(function(entityId) { + entityToOpacity[entityId] = sublayerOpacity; + }); + } + + // Opaque. + for (var sublayer = transluscentLayer + 1; sublayer < sublayers.length; + sublayer++) { + if (sublayers[sublayer]) { + sublayers[sublayer].forEach(function(entityId) { + entityToOpacity[entityId] = 1; + }); + } + } + + return entityToOpacity; +}; + +// Helper function that applies an opacity modifier to a transparency. +o3v.OpacityManager.prototype.getOpacityWithModifier_ = + function(base, modifier) { + if (modifier == 0) { + return base; + } else if (modifier > 0) { + return (base * (1 - modifier)) + (modifier); + } else { + return (base * (1 + modifier)); + } +}; + +// Helper function to modify opacity for a bunch of entities at once. +o3v.OpacityManager.prototype.modifyOpacityForEntities_ = + function(entityToOpacity, modifier) { + o3v.util.forEach(entityToOpacity, function(opacity, entityId) { + entityToOpacity[entityId] = this.getOpacityWithModifier_(opacity, + modifier); + }, this); +}; + +// If there is a selection, make other layers very transparent and the layer +// with the selection somewhat transparent. +o3v.OpacityManager.prototype.adjustLayersFromSelection_ = + function(layerToEntityOpacities) { + if (this.selectionManager_.haveSelected()) { + var layersWithSelection = this.selectionManager_.getLayersWithSelected(); + var selectedModifier = + this.selectionManager_.getSelectedLayerOpacityModifier(); + var otherModifier = this.selectionManager_.getOtherLayerOpacityModifier(); + o3v.util.forEach( + layerToEntityOpacities, + function(entityOpacities, layerId) { + if (layersWithSelection[layerId]) { + this.modifyOpacityForEntities_(entityOpacities, selectedModifier); + } else { + this.modifyOpacityForEntities_(entityOpacities, otherModifier); + } + }, this); + } +}; + +// Adjust entities based on their modified opacities from select / etc. +// TODO(dkogan): Selecting two nodes that end up with the same leaves may +// break this (e.g. select heart, and circulatory at once.) +o3v.OpacityManager.prototype.adjustEntitiesFromSelection_ = + function(layerToEntityOpacities, entities) { + + o3v.util.forEach( + entities, + function(entity, entityId) { + var opacityModifier = + this.selectionManager_.getEntityOpacityModifier(entityId); + + var leafEntityIds = this.entityMetadata_.getLeafIds(entityId); + Object.keys(leafEntityIds).forEach( + function(leafId) { + var leaf = this.entityMetadata_.getEntity(leafId); + var layerId = leaf.layers[0]; // Guaranteed only one. + var layerOpacities = layerToEntityOpacities[layerId]; + var leafOpacity = layerOpacities[leafId]; + leafOpacity = this.getOpacityWithModifier_( + leafOpacity, opacityModifier); + layerOpacities[leafId] = leafOpacity; + }, this); + }, this); +}; + +// This pushes opacities back into the layer opacity manager, forcing +// all the layers at or below any selected layers to opacity 1, and all +// other layers to opacity zero. +o3v.OpacityManager.prototype.exposeSelected = function() { + if (this.selectionManager_.haveSelected()) { + var layersToExpose = this.selectionManager_.getLayersWithSelected(); + var layerNames = this.entityMetadata_.getLayerNames(); + + var exposed = false; + var newOpacities = []; + + for (var i = 0; i < layerNames.length; i++) { + var layerId = this.entityMetadata_.layerNameToId(layerNames[i]); + if (layersToExpose[layerId] !== undefined) { + exposed = true; + } + if (exposed) { + newOpacities[layerNames.length - 1 - i] = 1; + } else { + newOpacities[layerNames.length - 1 - i] = 0; + } + } + + this.layerOpacityManager_.setLayerOpacities(newOpacities); + } +}; + +o3v.OpacityManager.prototype.handleLayerOpacityUpdate = function() { + if (!this.entityMetadata_) { + // Not yet set up, return. + return; + } + + // TODO(dkogan|arthurb): Switch layerOpacityManager entity ids. + var newOpacities = this.layerOpacityManager_.getLayerOpacities(); + var layerNames = this.entityMetadata_.getLayerNames(); + + if (newOpacities.length != layerNames.length) { + o3v.log.error("New opacities don't match expected count, unable to update", + newOpacities.length, layerNames.length); + return; + } + + for (var i = 0; i < layerNames.length; i++) { + var layerId = this.entityMetadata_.layerNameToId(layerNames[i]); + var layer = this.entityMetadata_.getEntity(layerId); + if (layer) { + // Note: The input is in reverse order here; kind of a nuisance. + this.layerOpacities_[layerId].setFuture( + newOpacities[layerNames.length - 1 - i]); + } + } + + this.changeCallback_(); +}; + +// Remove exceptions and use external ids. +o3v.OpacityManager.prototype.convertToExternalIds_ = + function(opacityToEntities) { + + var opacityToExternalEntities = {}; + + o3v.util.forEach( + opacityToEntities, + function(entities, opacity) { + opacityToExternalEntities[opacity] = {}; + o3v.util.forEach( + entities, + function(opt_true, entityId) { + var entity = this.entityMetadata_.getEntity(entityId); + var externalId = entity.externalId; + opacityToExternalEntities[opacity][externalId] = true; + }, this); + }, this); + return opacityToExternalEntities; +}; + +// INPUT: +// { : { entityId : }} +// OUTPUT: +// { : { : true }} +o3v.OpacityManager.prototype.convertLayerToEntityOpacities_ = + function(layerToEntityOpacities) { + + var opacityToEntities = {}; + + o3v.util.forEach( + layerToEntityOpacities, + function(entityOpacities) { + o3v.util.forEach( + entityOpacities, + function(opacity, entityId) { + if (opacityToEntities[opacity] === undefined) { + opacityToEntities[opacity] = {}; + } + opacityToEntities[opacity][entityId] = true; + }); + }); + + return opacityToEntities; + +}; + +// Gets layer opacity info. +// Returns: +// { : { : true } +// Or null if everything is opaque. +o3v.OpacityManager.prototype.getOpacityInfo = function(opt_forSelection) { + // opt_forSelection = true; + + // Most of the initial processing is done per-layer because much of + // the opacity information is relevant per-layer. + + // This is an Object.> of + // (layerId -> { entityId -> opacity }} + var layerToEntityOpacities = {}; + + // Layer-level opacities. + o3v.util.forEach( + this.layerOpacities_, + function(unused_layerOpacityInfo, layerId) { + layerToEntityOpacities[layerId] = + this.getOpacityFromLayering_(layerId); + }, this); + + // Adjust opacity of layers to account for any selection. + // Note the difference between for and from here. + if (!opt_forSelection) { + this.adjustLayersFromSelection_(layerToEntityOpacities); + } + + // Hidden entities are hidden both for selection and otherwise. + this.adjustEntitiesFromSelection_(layerToEntityOpacities, + this.selectionManager_.getHidden()); + // Adjust for selected entities. + this.adjustEntitiesFromSelection_(layerToEntityOpacities, + this.selectionManager_.getSelected()); + // Adjust for pinned entities. + this.adjustEntitiesFromSelection_(layerToEntityOpacities, + this.selectionManager_.getPinned()); + + // Create list of opacities -> list of entities. + // Empty set means everything is opaque. + var opacityToEntities = this.convertLayerToEntityOpacities_( + layerToEntityOpacities); + + if (opt_forSelection) { + // For selection, make all opacities integer values. + var newOpacityToEntities = {}; + newOpacityToEntities[0] = {}; + newOpacityToEntities[1] = {}; + o3v.util.forEach( + opacityToEntities, + function(entities, opacity) { + if (opacity >= 0.5) { + o3v.util.forEach( + entities, + function(usused_true, entityId) { + newOpacityToEntities[1][entityId] = true; + }); + } else { + o3v.util.forEach( + entities, + function(usused_true, entityId) { + newOpacityToEntities[0][entityId] = true; + }); + } + }); + opacityToEntities = newOpacityToEntities; + } + + // Use external ids. + opacityToEntities = this.convertToExternalIds_(opacityToEntities); + + return opacityToEntities; +}; + + +o3v.OpacityManager.prototype.recalculate = function() { + var updates = false; + + updates |= o3v.Interpolant.tweenAll( + this.layerOpacityInterpolants_); + return updates; +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/render_interface.js b/wormbrowser-appengine/src/main/webapp/scripts/render_interface.js new file mode 100755 index 0000000..214558d --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/render_interface.js @@ -0,0 +1,122 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +/** + * @fileoverview Description of this file. + * @author dkogan@google.com (David Kogan) + */ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +o3v.RenderInterface = function(canvas, opacityManager, contentManager) { + this.renderer_ = new Renderer(canvas, this.textureFromMaterial_.bind(this)); + + this.opacityManager_ = opacityManager; + this.contentManager_ = contentManager; + + this.pendingRefresh_ = 0; // If refresh is waiting, this is its timeout. + this.REFRESH_WAIT_ = 10; // Wait 10ms between refresh retries. + + this.reset(); +}; + +o3v.RenderInterface.prototype.textureFromMaterial_ = function( + gl, material, callback) { + var modelInfo = this.contentManager_.getCurrentModelInfo(); + var texturePath = modelInfo.texturePath; + // TODO(dkogan|wonchun): MODELS should probably not be a global variable. + var materials = MODELS[modelInfo.name].materials; + try { + var url = materials[material].map_Kd; // throw-y. + if (url === undefined) { + throw url; + } + return textureFromUrl(gl, texturePath + url, callback); + } catch (e) { + var color; + try { + color = new Uint8Array(materials[material].Kd); + } catch (e) { + color = new Uint8Array([255, 255, 255]); + } + var texture = textureFromArray(gl, 1, 1, color); + callback(gl, texture); + return texture; + } +} + + +o3v.RenderInterface.prototype.handleResize = function() { + this.renderer_.handleResize(); +}; + +o3v.RenderInterface.prototype.onMeshLoad = + function(attribArray, indexArray, bboxes, meshEntry) { + + this.renderer_.onMeshLoad(attribArray, indexArray, bboxes, meshEntry); + + // Update bbox info. + for (var i = 0; i < meshEntry.names.length; i++) { + var bbox = [bboxes[i*6 + 0], bboxes[i*6 + 1], bboxes[i*6 + 2], + bboxes[i*6 + 3], bboxes[i*6 + 4], bboxes[i*6 + 5]]; + this.bboxes_[meshEntry.names[i]] = bbox; + } +}; + +o3v.RenderInterface.prototype.onModelLoad = function() { + this.renderer_.updateMeshInfo(); +}; + +o3v.RenderInterface.prototype.reset = function() { + this.renderer_.reset(); + this.bboxes_ = {}; + window.clearTimeout(this.pendingRefresh_); +}; + +o3v.RenderInterface.prototype.getBboxes = function() { + return this.bboxes_; +}; + +o3v.RenderInterface.prototype.refresh = function(camera) { + if (this.pendingRefresh_) { + window.clearTimeout(this.pendingRefresh_); + this.pendingRefresh_ = 0; + } + if (this.renderer_.ready()) { + // Update opacity info. + this.renderer_.updateOpacity(this.opacityManager_.getOpacityInfo()); + + // Send refresh request. + this.renderer_.postRedisplayWithCamera(camera); + } else { + this.pendingRefresh_ = window.setTimeout( + function() { + this.refresh(camera); + }.bind(this), this.REFRESH_WAIT_); + } +}; + +o3v.RenderInterface.prototype.getViewportCoords = function(renderCoords) { + return this.renderer_.getViewportCoords(renderCoords); +}; + +o3v.RenderInterface.prototype.identify = function(left, top) { + // Set to int-valued opacities. + this.renderer_.updateOpacity(this.opacityManager_.getOpacityInfo(true)); + return this.renderer_.identify(left, top); + this.renderer_.updateOpacity(this.opacityManager_.getOpacityInfo()); +}; + +o3v.RenderInterface.prototype.toggleColored = function() { + this.renderer_.toggleColored(); +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/renderer.js b/wormbrowser-appengine/src/main/webapp/scripts/renderer.js new file mode 100755 index 0000000..1b7a465 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/renderer.js @@ -0,0 +1,517 @@ +'use strict'; + +/** + * Computes a 4-by-4 camera look-at transformation. This is the + * inverse of lookAt The transformation generated is an + * orthogonal rotation matrix with translation component. + * @param {(!tdl.fast.Vector3|!tdl.fast.Vector4)} eye The position + * of the eye. + * @param {(!tdl.fast.Vector3|!tdl.fast.Vector4)} target The + * position meant to be viewed. + * @param {(!tdl.fast.Vector3|!tdl.fast.Vector4)} up A vector + * pointing up. + * @return {!tdl.fast.Matrix4} The camera look-at matrix. + */ +o3v.cameraLookAt = function(dst, eye, target, up) { + var t0 = new Float32Array(3); + var t1 = new Float32Array(3); + var t2 = new Float32Array(3); + + var vz = o3v.normalize(t0, o3v.subVector(t0, eye, target)); + var vx = o3v.normalize(t1, o3v.cross(t1, up, vz)); + var vy = o3v.cross(t2, vz, vx); + + dst[ 0] = vx[0]; + dst[ 1] = vx[1]; + dst[ 2] = vx[2]; + dst[ 3] = 0; + dst[ 4] = vy[0]; + dst[ 5] = vy[1]; + dst[ 6] = vy[2]; + dst[ 7] = 0; + dst[ 8] = vz[0]; + dst[ 9] = vz[1]; + dst[10] = vz[2]; + dst[11] = 0; + dst[12] = eye[0]; + dst[13] = eye[1]; + dst[14] = eye[2]; + dst[15] = 1; + + return dst; +}; + +/** + * Subtracts two vectors. + * @param {!tdl.fast.Vector} dst vector. + * @param {!tdl.fast.Vector} a Operand vector. + * @param {!tdl.fast.Vector} b Operand vector. + */ +o3v.subVector = function(dst, a, b) { + var aLength = a.length; + for (var i = 0; i < aLength; ++i) + dst[i] = a[i] - b[i]; + return dst; +}; + +/** + * Computes the cross product of two vectors; assumes both vectors have + * three entries. + * @param {!tdl.math.Vector} dst vector. + * @param {!tdl.math.Vector} a Operand vector. + * @param {!tdl.math.Vector} b Operand vector. + * @return {!tdl.math.Vector} The vector a cross b. + */ +o3v.cross = function(dst, a, b) { + dst[0] = a[1] * b[2] - a[2] * b[1]; + dst[1] = a[2] * b[0] - a[0] * b[2]; + dst[2] = a[0] * b[1] - a[1] * b[0]; + return dst; +}; + +/** + * Divides a vector by its Euclidean length and returns the quotient. + * @param {!tdl.fast.Vector} dst vector. + * @param {!tdl.fast.Vector} a The vector. + * @return {!tdl.fast.Vector} The normalized vector. + */ +o3v.normalize = function(dst, a) { + var n = 0.0; + var aLength = a.length; + for (var i = 0; i < aLength; ++i) + n += a[i] * a[i]; + n = Math.sqrt(n); + if (n > 0.00001) { + for (var i = 0; i < aLength; ++i) + dst[i] = a[i] / n; + } else { + for (var i = 0; i < aLength; ++i) + dst[i] = 0; + } + return dst; +}; + + +function Renderer(canvas, textureFromMaterialFunction) { + getHttpRequest('scripts/shaders.txt', this.onloadShaders.bind(this)); + + this.canvas_ = canvas; + this.textureFromMaterialFunction_ = textureFromMaterialFunction; + + var gl = createContextFromCanvas(canvas); + this.gl_ = gl; + + // Camera. + this.zNear_ = Math.sqrt(3); + this.model_ = mat4.identity(mat4.create()); + this.view_ = mat4.identity(mat4.create()); + this.proj_ = mat4.create(); + this.mvp_ = mat4.create(); + + // Meshes. + this.meshes_ = []; + + // WebGL + gl.clearColor(0, 0, 0, 0); + gl.enable(gl.CULL_FACE); + gl.enable(gl.DEPTH_TEST); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + + // Set up viewport. + this.handleResize(); + mat4.translate(this.view_, [0, 0, -1]); + + // Set up for off-screen surface for entity identification. + this.selectionFbo_ = { width: 0, height: 0 }; + + this.forceColored_ = false; +}; + +Renderer.prototype.onloadShaders = function(req) { + // TODO: error handling. + var shaders = {}; + req.responseText.split('/** ').forEach(function(shader) { + var name_and_body = shader.split(' **/'); + shaders[name_and_body[0]] = name_and_body[1]; + }); + + var gl = this.gl_; + + // Set up program for rendering. + var simpleVsrc = shaders['shader_vertex']; + var simpleFsrc = shaders['shader_fragment']; + this.normProgram_ = new Program(gl, [vertexShader(gl, simpleVsrc), + fragmentShader(gl, simpleFsrc)]); + + // Set up program for selection. + var idVsrc = shaders['shader_vertex_id']; + var idFsrc = shaders['shader_fragment_id']; + this.idProgram_ = new Program(gl, [vertexShader(gl, idVsrc), + fragmentShader(gl, idFsrc)]); + + this.shadersLoaded_ = true; +}; + +Renderer.prototype.handleResize = function() { + this.canvas_.width = this.canvas_.clientWidth; + this.canvas_.height = this.canvas_.clientHeight; + this.gl_.viewport(0, 0, this.canvas_.width, this.canvas_.height); +}; + +Renderer.prototype.drawAll_ = function(opt_forId) { + var numMeshes = this.meshes_.length; + for (var i = 0; i < numMeshes; i++) { + this.meshes_[i].bindAndDraw(this.program_, opt_forId); + } +}; + +Renderer.prototype.drawLists_ = function(displayLists, opt_forId) { + var numLists = displayLists.length; + for (var i = 0; i < numLists; i++) { + var displayList = displayLists[i]; + var mesh = this.meshes_[i]; + mesh.bind(this.program_, opt_forId); + mesh.drawList(displayList); + } +}; + +// Update matrices and then redisplay. +Renderer.prototype.postRedisplayWithCamera = function(camera) { + mat4.perspective( + camera.fov, + this.canvas_.clientWidth / this.canvas_.clientHeight, + 1, 1000, + this.proj_); + + o3v.cameraLookAt(this.view_, camera.eye, camera.target, camera.up); + mat4.inverse(this.view_); + var vpMatrix = new Float32Array(16); + mat4.multiply(this.proj_, this.view_, vpMatrix); + mat4.multiply(vpMatrix, this.model_, this.mvp_); + + this.postRedisplay(); +}; + +Renderer.prototype.postRedisplay = function() { + var self = this; + if (!this.frameStart_) { + this.frameStart_ = Date.now(); + window.requestAnimFrame(function() { + self.draw_(); + self.frameStart_ = 0; + }, this.canvas_); + } +}; + +Renderer.prototype.ready = function() { + return this.shadersLoaded_ && (this.frameStart_ === 0); +}; + +Renderer.prototype.createOffscreenSurface_ = function(width, height) { + var gl = this.gl_; + if (!this.selectionFbo_.framebuffer) + this.selectionFbo_.framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.selectionFbo_.framebuffer); + + if (!this.selectionFbo_.colorTexture) { + this.selectionFbo_.colorTexture = gl.createTexture(); + + gl.bindTexture(gl.TEXTURE_2D, this.selectionFbo_.colorTexture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + } + + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, + gl.RGBA, gl.UNSIGNED_BYTE, null); + + if (!this.selectionFbo_.renderbuffer) { + this.selectionFbo_.renderbuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, this.selectionFbo_.renderbuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, + width, height); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, this.selectionFbo_.colorTexture, 0); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, + this.selectionFbo_.renderbuffer); + } + + var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + if (status != gl.FRAMEBUFFER_COMPLETE) { + o3v.log.error('Incomplete off-screen framebuffer'); + this.selectionFbo_.framebuffer = null; + } + gl.bindFramebuffer(gl.FRAMEBUFFER, null); +}; + +// Identify the mesh clicked. +Renderer.prototype.identify = function(x, y) { + var gl = this.gl_; + + if (this.selectionFbo_.width != this.canvas_['clientWidth'] || + this.selectionFbo_.height != this.canvas_['clientHeight']) { + this.createOffscreenSurface_(this.canvas_['clientWidth'], + this.canvas_['clientHeight']); + if (!this.selectionFbo_.framebuffer) { + o3v.log.error('Unable to identify without valid off-screen buffer.'); + return null; + } + var selectionSurfaceSize = + this.canvas_['clientWidth'] * this.canvas_['clientHeight'] * 4; + this.selectionFbo_.selectionSurfaceArray = + new Uint8Array(selectionSurfaceSize); + this.selectionFbo_.width = this.canvas_['clientWidth']; + this.selectionFbo_.height = this.canvas_['clientHeight']; + } + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.selectionFbo_.framebuffer); + + this.draw_(true); + + gl.readPixels(0, 0, this.selectionFbo_.width, this.selectionFbo_.height, + gl.RGBA, gl.UNSIGNED_BYTE, + this.selectionFbo_.selectionSurfaceArray); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + var tolerancePx = 10; + var value = this.findPixelInRect_( + x, y, tolerancePx, this.selectionFbo_.width, this.selectionFbo_.height, + this.selectionFbo_.selectionSurfaceArray); + + value = Math.floor(value / this.selectionColorScale_); + if (value != 0) { + return this.colorToName_[value]; + } else { + return null; + } +}; + +Renderer.prototype.findPixelInRect_ = + function(sx, sy, windowSize, width, height, data) { + // Check center. + var value = this.getPixel_(sx, sy, width, height, data); + if (value != 0) return value; + + // Walk growing rectangle edges. + for (var d = 1; d <= windowSize / 2; ++d) { + for (var y = sy - d; y <= sy + d; ++y) { + if (y < 0) continue; + if (y >= height) break; + + value = this.getPixel_(sx - d, y, width, height, data); + if (value != 0) return value; + value = this.getPixel_(sx + d, y, width, height, data); + if (value != 0) return value; + } + for (var x = sx - d + 1; x <= sx + d - 1; ++x) { + if (x < 0) continue; + if (x >= width) break; + + value = this.getPixel_(x, sy - d, width, height, data); + if (value != 0) return value; + value = this.getPixel_(x, sy + d, width, height, data); + if (value != 0) return value; + } + } + return 0; +}; + +Renderer.prototype.getPixel_ = function(sx, sy, width, height, data) { + if (sx < 0 || sx >= width || sy < 0 || sy >= height) + return 0; + + var startByte = ((height - 1 - sy) * width + sx) * 4; + var red = data[startByte + 0]; + var green = data[startByte + 1]; + var blue = data[startByte + 2]; + return blue + green * 256 + red * 256 * 256; +}; + +Renderer.prototype.draw_ = function(opt_forId) { + if (!this.shadersLoaded_) + return; + + if (this.forceColored_) { + opt_forId = true; // colorcoded + } + + if (opt_forId) { + // Flat, one-color-per mesh program used for identification. + this.program_ = this.idProgram_; + } else { + // Normal program used for rendering. + this.program_ = this.normProgram_; + } + + this.program_.use(); + + if (opt_forId) { + this.program_.enableVertexAttribArrays(['a_position', + 'a_colorIndex']); + this.program_.disableVertexAttribArrays(['a_normal', + 'a_texcoord']); + } else { + this.program_.enableVertexAttribArrays(['a_position', + 'a_texcoord', + 'a_normal']); + this.program_.disableVertexAttribArrays(['a_colorIndex']); + } + + var gl = this.gl_; + + if (opt_forId) { + this.selectionColorScale_ = + Math.floor((256*256*256-1) / this.maxColorIndex_); + gl.uniform1f(this.program_.set_uniform.u_colorScale, + this.selectionColorScale_); + //gl.colorMask(true, true, true, true); + } else { + //gl.colorMask(true, true, true, false); + } + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); + + gl.uniformMatrix4fv(this.program_.set_uniform.u_mvp, false, this.mvp_); + gl.uniformMatrix3fv(this.program_.set_uniform.u_model, false, + mat4.toMat3(this.model_)); + + gl.uniform1f(this.program_.set_uniform.u_opacity, 1.0); + if (this.opacityLists_ !== undefined) { + var meshes = this.meshes_; + + // Draw opaque lists. (Really should only be one, at position zero.) + for (var i = 0; i < this.opacityLists_.length; i++) { + var opacity = this.opacityLists_[i].opacity; + if (opacity == 1) { + this.drawLists_(this.opacityLists_[i].drawLists, opt_forId); + } + } + + // Draw transluscent layers. + gl.enable(gl.BLEND); + for (var i = 0; i < this.opacityLists_.length; i++) { + var opacity = this.opacityLists_[i].opacity; + if (opacity != 0 && opacity != 1) { + gl.uniform1f(this.program_.set_uniform.u_opacity, opacity); + this.drawLists_(this.opacityLists_[i].drawLists, opt_forId); + } + } + gl.disable(gl.BLEND); + + } else { + this.drawAll_(opt_forId); + } +}; + +Renderer.prototype.updateMeshInfo = function() { + this.entityToMeshInfo_ = {}; + + for (var i = 0; i < this.meshes_.length; i++) { + var mesh = this.meshes_[i]; + for (var j = 0 ; j < mesh.names_.length; j++) { + + var name = mesh.names_[j]; + var meshInfo = {}; + meshInfo.index = i; + meshInfo.start = mesh.starts_[j]; + meshInfo.end = mesh.starts_[j] + mesh.lengths_[j]; + + if (this.entityToMeshInfo_[name] !== undefined) { + o3v.log.info('multiple meshes for \'', name, '\': ', + this.entityToMeshInfo_[name], meshInfo); + this.entityToMeshInfo_[name].push(meshInfo); + } else { + this.entityToMeshInfo_[name] = [meshInfo]; + } + } + } +}; + +Renderer.prototype.updateOpacity = function(opacityInfo) { + // TODO(dkogan): Special-case all-opaque case for speed. + + // this.opacityLists is: + // [ { opacity: , + // drawLists: [ [ , , + // , ...], + // [ , ... + // ] } ] + + this.opacityLists_ = []; + o3v.util.forEach( + opacityInfo, + function(entities, opacity) { + opacityInfo = {}; + opacityInfo.opacity = parseFloat(opacity); + opacityInfo.drawLists = []; + for (var i = 0; i < this.meshes_.length; i++) { + opacityInfo.drawLists[i] = []; + } + o3v.util.forEach( + entities, + function(unused_true, entityId) { + for (var i = 0; i < this.entityToMeshInfo_[entityId].length; + i++) { + var meshInfo = this.entityToMeshInfo_[entityId][i]; + opacityInfo.drawLists[meshInfo.index].push(meshInfo.start); + opacityInfo.drawLists[meshInfo.index].push(meshInfo.end); + } + }, this); + this.opacityLists_.push(opacityInfo); + }, this); + this.opacityLists_.sort(function(a, b) { return b.opacity > a.opacity; }); +}; + +Renderer.prototype.onMeshLoad = + function(attribArray, indexArray, bboxes, meshEntry) { + var texture = this.textureFromMaterialFunction_(this.gl_, meshEntry.material, + this.postRedisplay.bind( + this)); + + // Set color for meshes, and record the mapping of color to name. + var startColorIndex = this.maxColorIndex_; + for (var i = 0; i < meshEntry.names.length; i++) { + this.colorToName_[startColorIndex + i] = meshEntry.names[i]; + } + this.maxColorIndex_ += meshEntry.lengths.length; + + this.meshes_.push( + new Mesh(this.gl_, attribArray, indexArray, DEFAULT_ATTRIB_ARRAYS, + texture, meshEntry.names, meshEntry.lengths, bboxes, + startColorIndex)); +}; + +Renderer.prototype.reset = function() { + this.meshes_ = []; + this.postRedisplay(); + this.maxColorIndex_ = 1; + this.colorToName_ = {}; + this.opacityLists_ = []; +}; + +Renderer.prototype.getViewportCoords = function(modelCoords) { + var modelCoords = [modelCoords[0], + modelCoords[1], + modelCoords[2], + 1]; + + var screenCoords = mat4.create(); + + mat4.multiply(this.mvp_, modelCoords, screenCoords); + + var x = screenCoords[0] / screenCoords[3]; + var y = screenCoords[1] / screenCoords[3]; + + x = (x + 1) * this.canvas_.width / 2; + y = (2 - (y + 1)) * this.canvas_.height / 2; + + return [x, y]; +}; + +Renderer.prototype.toggleColored = function() { + this.forceColored_ = !this.forceColored_; +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/search.js b/wormbrowser-appengine/src/main/webapp/scripts/search.js new file mode 100755 index 0000000..f92e3b0 --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/search.js @@ -0,0 +1,77 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Search. + */ + +o3v.Search = function(selectCallback) { + this.selectCallback_ = selectCallback; + + this.searchbox_ = $('').appendTo('body').css({ + 'position': 'absolute', + 'left': '100%', + 'top': '8px', + 'width': '200px', + 'margin-left': '-227px', + 'outline-style': 'none', + 'border': '2px solid #466a15', + 'border-radius': '12px', + 'padding': '2px 8px 2px 8px', + 'opacity': 0.8, + 'z-index': o3v.uiSettings.ZINDEX_MAINUI + }); +}; + +o3v.Search.prototype.reset = function(searchTokens) { + this.searchbox_.autocomplete('destroy'); + this.terms_ = searchTokens; + this.searchbox_.autocomplete( + { + source: this.find.bind(this), + delay: 0, + autoFocus: true, + selectFirst: true, + select: function(event, ui) { + this.handleResult_.bind(event, ui); + this.searchbox_[0].blur(); + }.bind(this), + focus: this.handleResult_.bind(this) + }); +}; + +o3v.Search.prototype.find = function(query, callback) { + var token = query.term; + + var matches = []; + if (token != '') { + var matcher = new RegExp('(^|\\W+)' + token, 'i'); + + for (var i = 0; i < this.terms_.length; i++) { + if (this.terms_[i].match(matcher)) { + matches.push(this.terms_[i]); + if (matches.length >= o3v.Search.MAX_MATCHES) { + break; + } + } + } + } + callback(matches); +}; + +o3v.Search.prototype.handleResult_ = function(event, ui) { + this.selectCallback_(ui.item.value); +}; + +o3v.Search.MAX_MATCHES = 10; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/select.js b/wormbrowser-appengine/src/main/webapp/scripts/select.js new file mode 100644 index 0000000..76a5c1f --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/select.js @@ -0,0 +1,567 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Code to make and keep track of selections. + */ + +// changeCallback: function to call when anything has changed. +// after this is called, should repeatedly call +// recalculate until it stops returning true. +o3v.SelectManager = function(changeCallback) { + this.changeCallback_ = changeCallback; + + // map of selected layer name -> refcount of entities in layer + this.layerSelectionRefcount_ = {}; + + // Includes pending. + // Map of entityId -> entity + // TODO(dkogan): Generalize to arbitrary number of groups and + // behaviors. + this.selectedEntities_ = {}; + this.pinnedEntities_ = {}; + this.hiddenEntities_ = {}; + + // Map of entity -> opacity interpolant + // If an entity is in this map, then it is being changed + // (being hidden/selected/pinned/unhidden/unselected) + this.interpolants_ = {}; + // Predefined storage for current and other layer interpolants. + this.CURRENT_LAYER_INTERPOLANT = -1; + this.OTHER_LAYER_INTERPOLANT = -2; + + // The interpolants behave as follows: + // 0 = same opacity as would be otherwise. + // 1 = completely visibile + // -1 = completely hidden + // TODO(wonchun): work out some math/libraries to combine these. Figure + // out associativity properties. + + // Layer with selection behavior varies depending on how many layers have + // selections in them. + this.CURRENT_LAYER_OPACITY_MAX_MODIFIER = -0.8; // 20% opaque. + this.CURRENT_LAYER_OPACITY_MODIFIER_STEP = 0.1; + this.CURRENT_LAYER_OPACITY_MIN_MODIFIER = -0.9; // 10% opaque. + this.OTHER_LAYER_OPACITY_DEMOTION = 0.15; + + this.PINNED_ENTITY_OPACITY_MODIFIER = 1.0; + this.SELECTED_ENTITY_OPACITY_MODIFIER = 1.0; + this.HIDDEN_ENTITY_OPACITY_MODIFIER = -1.0; + this.NEUTRAL_OPACITY_MODIFIER = 0; + + // Modes for explicit UI of selecting multiple / hiding / etc. + this.mode_ = 0; + this.MODE_NORMAL = 0; + this.MODE_PIN = 1; + this.MODE_HIDE = 2; +}; + +////////////////////////////////////////////////////////////////////// +// INITIALIZATION METHODS +////////////////////////////////////////////////////////////////////// + +o3v.SelectManager.prototype.reset = function(entityStore) { + this.reset_(); + this.mode_ = this.MODE_NORMAL; + // TODO(dkogan): Reinstate history. + //this.history = history; + //this.history.register('sel', this.getState, this.restoreState); + + this.entityStore_ = entityStore; +}; + +o3v.SelectManager.prototype.getState = function() { + // Note that selection is stored last intentionally, because otherwise, + // it would get clobbered by pinning / hiding. + return ('p:' + Object.keys(this.pinnedEntities_).join(',') + + ';h:' + Object.keys(this.hiddenEntities_).join(',') + + ';s:' + Object.keys(this.selectedEntities_).join(',') + + ';c:' + this.getSelectedLayerOpacityModifier() + + ';o:' + this.getOtherLayerOpacityModifier()); +}; + +o3v.SelectManager.prototype.restoreState = function(state) { + this.reset_(); + if (state) { + var tuples = state.split(';'); + for (var tupleIndex in tuples) { + var tuple = tuples[tupleIndex].split(':'); + if (tuple[0] == 's') { + this.selectMultiple(tuple[1].split(','), true); + } else if (tuple[0] == 'p') { + this.pinMultiple(tuple[1].split(','), true); + } else if (tuple[0] == 'h') { + this.hideMultiple(tuple[1].split(','), true); + } else if (tuple[0] == 'c') { + this.setFuture_(this.CURRENT_LAYER_INTERPOLANT, + parseFloat(tuple[1]), 1); + } else if (tuple[0] == 'o') { + this.setFuture_(this.OTHER_LAYER_INTERPOLANT, + parseFloat(tuple[1]), 1); + } + } + } + this.signalChange_(true); +}; + +/////////////////////////////////////////////////////////////////////////// +// Helper methods. +/////////////////////////////////////////////////////////////////////////// + +// Resets without generating a history event. +o3v.SelectManager.prototype.reset_ = function() { + this.clearHidden(true); + this.clearPinned(true); + this.clearSelected(true); + this.interpolants_[this.CURRENT_LAYER_INTERPOLANT] = + new o3v.Interpolant(this.NEUTRAL_OPACITY_MODIFIER); + this.interpolants_[this.OTHER_LAYER_INTERPOLANT] = + new o3v.Interpolant(this.NEUTRAL_OPACITY_MODIFIER); +}; + +// Returns true if this is a selectable entity. +o3v.SelectManager.prototype.entityAllowed_ = function(entityId) { + if (!entityId || !this.entityStore_.getEntity(entityId)) { + return false; + } else { + return true; + } +}; + +o3v.SelectManager.prototype.calculateSelectedLayerOpacityModifier_ = + function() { + // Demote selected layer opacity by number of selected layers. + var mod = (this.CURRENT_LAYER_OPACITY_MAX_MODIFIER + + this.CURRENT_LAYER_OPACITY_MODIFIER_STEP); + for (var layer in this.layerSelectionRefcount_) { + if (this.layerSelectionRefcount_[layer]) + mod -= this.CURRENT_LAYER_OPACITY_MODIFIER_STEP; + } + if (mod < this.CURRENT_LAYER_OPACITY_MIN_MODIFIER) { + mod = this.CURRENT_LAYER_OPACITY_MIN_MODIFIER; + } + return mod; +}; + +// Update opacities to reflect a change in the selection +// mode of an entity. +// Selected trumps Pinned trumps Hidden. +o3v.SelectManager.prototype.setFutureOpacities_ = + function(entityId, priorOpacityModifier) { + if (this.selectedEntities_[entityId]) { + this.setFuture_(entityId, this.SELECTED_ENTITY_OPACITY_MODIFIER, + priorOpacityModifier); + } else if (this.pinnedEntities_[entityId]) { + this.setFuture_(entityId, this.PINNED_ENTITY_OPACITY_MODIFIER, + priorOpacityModifier); + } else if (this.hiddenEntities_[entityId]) { + this.setFuture_(entityId, this.HIDDEN_ENTITY_OPACITY_MODIFIER, + priorOpacityModifier); + } else { + this.setFuture_(entityId, this.NEUTRAL_OPACITY_MODIFIER, + priorOpacityModifier); + } + this.setFutureLayerOpacities_(); +}; + +// Set future layer opacities based on existence of selection. +o3v.SelectManager.prototype.setFutureLayerOpacities_ = function() { + if (this.haveSelected()) { + var selectedLayerOpacityModifier = + this.calculateSelectedLayerOpacityModifier_(); + var hiddenLayerOpacityModifier = + Math.max(selectedLayerOpacityModifier - + this.OTHER_LAYER_OPACITY_DEMOTION, -1); + + this.setFuture_(this.CURRENT_LAYER_INTERPOLANT, + selectedLayerOpacityModifier, + this.getEntityOpacityModifier( + this.CURRENT_LAYER_INTERPOLANT)); + this.setFuture_(this.OTHER_LAYER_INTERPOLANT, + hiddenLayerOpacityModifier, + this.getEntityOpacityModifier( + this.OTHER_LAYER_INTERPOLANT)); + + } else { + this.setFuture_(this.CURRENT_LAYER_INTERPOLANT, + this.NEUTRAL_OPACITY_MODIFIER, + this.getEntityOpacityModifier( + this.CURRENT_LAYER_INTERPOLANT)); + this.setFuture_(this.OTHER_LAYER_INTERPOLANT, + this.NEUTRAL_OPACITY_MODIFIER, + this.getEntityOpacityModifier( + this.OTHER_LAYER_INTERPOLANT)); + } +}; + +// Sets the future opacity modifier for an entity. +o3v.SelectManager.prototype.setFuture_ = + function(entityId, futureValue, priorOpacityModifier) { + if (!this.interpolants_[entityId]) { + this.interpolants_[entityId] = new o3v.Interpolant( + priorOpacityModifier); + } + this.interpolants_[entityId].setFuture(futureValue); +}; + +// Indicate to the outside world (renderer and history manager) +// that something has changed inside select.js. +o3v.SelectManager.prototype.signalChange_ = function(opt_skipHistory) { + this.changeCallback_(); + if (!opt_skipHistory) { + // TODO(dkogan): Reinstate history. + //this.history.update(); + } +}; + +o3v.SelectManager.prototype.selectEntity_ = function(entityId) { + var entity = this.entityStore_.getEntity(entityId); + if (entity && !this.selectedEntities_[entityId]) { + var priorOpacityModifier = this.getEntityOpacityModifier(entityId); + + // Bump refcount in associated layer. + entity.layers.forEach( + function(layer) { + o3v.util.setIfUndefined( + this.layerSelectionRefcount_, layer, 0); + this.layerSelectionRefcount_[layer]++; + }, this); + + // Select entity + this.selectedEntities_[entityId] = entity; + + // Set opacities. + this.setFutureOpacities_(entityId, priorOpacityModifier); + } +}; + +o3v.SelectManager.prototype.unselectEntity_ = function(entityId) { + var entity = this.selectedEntities_[entityId]; + if (entity) { + var priorOpacityModifier = this.getEntityOpacityModifier(entityId); + + entity.layers.forEach( + function(layer) { + this.layerSelectionRefcount_[layer]--; + }, this); + delete this.selectedEntities_[entityId]; + this.setFutureOpacities_(entityId, priorOpacityModifier); + } +}; + +o3v.SelectManager.prototype.hideEntity_ = function(entityId) { + var entity = this.entityStore_.getEntity(entityId); + if (entity && !this.hiddenEntities_[entityId]) { + var priorOpacityModifier = this.getEntityOpacityModifier(entityId); + this.hiddenEntities_[entityId] = entity; + this.setFutureOpacities_(entityId, priorOpacityModifier); + } +}; + +o3v.SelectManager.prototype.unhideEntity_ = function(entityId) { + var entity = this.hiddenEntities_[entityId]; + if (entity) { + var priorOpacityModifier = this.getEntityOpacityModifier(entityId); + delete this.hiddenEntities_[entityId]; + this.setFutureOpacities_(entityId, priorOpacityModifier); + } +}; + +o3v.SelectManager.prototype.pinEntity_ = function(entityId) { + var entity = this.entityStore_.getEntity(entityId); + if (entity && !this.pinnedEntities_[entityId]) { + var priorOpacityModifier = this.getEntityOpacityModifier(entityId); + this.pinnedEntities_[entityId] = entity; + this.setFutureOpacities_(entityId, priorOpacityModifier); + } +}; + +o3v.SelectManager.prototype.unpinEntity_ = function(entityId) { + var entity = this.pinnedEntities_[entityId]; + if (entity) { + var priorOpacityModifier = this.getEntityOpacityModifier(entityId); + delete this.pinnedEntities_[entityId]; + this.setFutureOpacities_(entityId, priorOpacityModifier); + } +}; + +////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +////////////////////////////////////////////////////////////////////// + +// Returns -1 for hidden, 0 for default, 1 for visible, and values in between +// for transitional states. +o3v.SelectManager.prototype.getEntityOpacityModifier = function(entityId) { + if (this.interpolants_[entityId]) { + return this.interpolants_[entityId].getPresent(); + } else if (this.hiddenEntities_[entityId]) { + return this.HIDDEN_ENTITY_OPACITY_MODIFIER; + } else if (this.selectedEntities_[entityId]) { + return this.SELECTED_ENTITY_OPACITY_MODIFIER; + } else if (this.pinnedEntities_[entityId]) { + return this.PINNED_ENTITY_OPACITY_MODIFIER; + } else { + return this.NEUTRAL_OPACITY_MODIFIER; + } +}; + +o3v.SelectManager.prototype.getSelectedLayerOpacityModifier = function() { + return this.getEntityOpacityModifier(this.CURRENT_LAYER_INTERPOLANT); +}; + +o3v.SelectManager.prototype.getOtherLayerOpacityModifier = function() { + return this.getEntityOpacityModifier(this.OTHER_LAYER_INTERPOLANT); +}; + +o3v.SelectManager.prototype.haveSelected = function() { + return !o3v.util.isEmpty(this.selectedEntities_); +}; + +o3v.SelectManager.prototype.havePinned = function() { + return !o3v.util.isEmpty(this.pinnedEntities_); +}; + +o3v.SelectManager.prototype.haveHidden = function() { + return !o3v.util.isEmpty(this.hiddenEntities_); +}; + +o3v.SelectManager.prototype.getLayersWithSelected = function() { + var layers = {}; + o3v.util.forEach(this.layerSelectionRefcount_, function(count, layer) { + if (count > 0) { + layers[layer] = true; + } + }); + return layers; +}; + +o3v.SelectManager.prototype.getLayersWithPinned = function() { + var layers = {}; + var pinned = this.getPinned(); + o3v.util.forEach(this.getPinned(), function(entity, entityId) { + entity.layers.forEach( + function(layer) { + layers[layer] = true; + }); + }); + return layers; +}; + +o3v.SelectManager.prototype.getPinned = function() { + return this.pinnedEntities_; +}; + +o3v.SelectManager.prototype.getSelected = function() { + return this.selectedEntities_; +}; + +o3v.SelectManager.prototype.getHidden = function() { + return this.hiddenEntities_; +}; + +////////////////////////////////////////////////////////////////////// +// HIDE +////////////////////////////////////////////////////////////////////// +o3v.SelectManager.prototype.hide = function(entityId, opt_skipHistory) { + this.hideMultiple([entityId], opt_skipHistory); +}; +o3v.SelectManager.prototype.hideMultiple = + function(entityIds, opt_skipHistory) { + entityIds.forEach( + function(entityId) { + if (this.entityAllowed_(entityId)) { + // Hidden is not allowed to be selected or pinned. + this.unselect(entityId, opt_skipHistory); + this.unpin(entityId, opt_skipHistory); + this.hideEntity_(entityId); + } + }, this); + this.signalChange_(opt_skipHistory); +}; +o3v.SelectManager.prototype.unhide = function(entityId, opt_skipHistory) { + this.unhideEntity_(entityId); + this.signalChange_(opt_skipHistory); +}; +o3v.SelectManager.prototype.clearHidden = function(opt_skipHistory) { + if (!opt_skipHistory) { + o3v.Analytics.trackPage('/ui/clear-hidden'); + } + for (var entityId in this.hiddenEntities_) { + this.unhideEntity_(entityId); + } + this.signalChange_(opt_skipHistory); +}; + +////////////////////////////////////////////////////////////////////// +// SELECT +////////////////////////////////////////////////////////////////////// +o3v.SelectManager.prototype.select = function(entityId, opt_skipHistory) { + this.selectMultiple([entityId], opt_skipHistory); +}; +o3v.SelectManager.prototype.selectMultiple = + function(entityIds, opt_skipHistory) { + this.clearSelected(false, true); // Only allowing a single selection. + entityIds.forEach( + function(entityId) { + if (this.entityAllowed_(entityId)) { + this.selectEntity_(entityId); + } + }, this); + this.signalChange_(opt_skipHistory); +}; +o3v.SelectManager.prototype.unselect = function(entityId, opt_skipHistory) { + this.unselectEntity_(entityId); + this.signalChange_(opt_skipHistory); +}; +o3v.SelectManager.prototype.clearSelected = function(opt_skipHistory, + opt_noSignal) { + for (var entity in this.selectedEntities_) { + this.unselectEntity_(entity); + } + if (!opt_noSignal) { + this.signalChange_(opt_skipHistory); + } +}; + +////////////////////////////////////////////////////////////////////// +// PIN +////////////////////////////////////////////////////////////////////// +o3v.SelectManager.prototype.pin = function(entityId, opt_skipHistory) { + this.pinMultiple([entityId], opt_skipHistory); +}; +o3v.SelectManager.prototype.pinMultiple = function(entityIds, opt_skipHistory) { + entityIds.forEach( + function(entityId) { + if (this.entityAllowed_(entityId)) { + // Pinned is not allowed to be selected or hidden. + this.unhide(entityId, opt_skipHistory); + this.unselect(entityId, opt_skipHistory); + this.pinEntity_(entityId, opt_skipHistory); + } + }, this); + this.signalChange_(opt_skipHistory); +}; +o3v.SelectManager.prototype.unpin = function(entityId, opt_skipHistory) { + this.unpinEntity_(entityId); + this.signalChange_(opt_skipHistory); +}; +o3v.SelectManager.prototype.togglePin = function(entityId) { + if (this.pinnedEntities_[entityId]) { + this.unpin(entityId); + } else { + this.pin(entityId); + } +}; +o3v.SelectManager.prototype.togglePinMultiple = function(entityIds) { + entityIds.forEach( + function(entityId) { + this.togglePin(entityId); + }, this); +}; +o3v.SelectManager.prototype.clearPinned = function(opt_skipHistory) { + if (!opt_skipHistory) { + o3v.Analytics.trackPage('/ui/clear-pinned'); + } + for (var entity in this.pinnedEntities_) { + this.unpinEntity_(entity); + } + this.signalChange_(opt_skipHistory); +}; + +/////////////////////////////////////////////////////////////////////////// +// Undifferentiated Selection. +/////////////////////////////////////////////////////////////////////////// +o3v.SelectManager.prototype.pickMultiple = function(entityIds) { + if (this.mode_ == this.MODE_PIN) { + this.togglePinMultiple(entityIds); + } else if (this.mode_ == this.MODE_HIDE) { + this.hideMultiple(entityIds); + } else { + if (entityIds.length == 1 && this.selectedEntities_[entityIds] && + o3v.util.getObjectCount(this.selectedEntities_) == 1) { + // This is a pick of the currently selected entity, so deselect it. + this.clearSelected(); + } else { + this.selectMultiple(entityIds); + } + } +}; + +////////////////////////////////////////////////////////////////////// +// Expand +////////////////////////////////////////////////////////////////////// +o3v.SelectManager.prototype.expandSelected = function(entityId) { + var newSelected = {}; + // NOTE(dkogan): These are two different types of maps, but that's okay + // because we're just using their keys. + o3v.util.extendObject(newSelected, + this.entityStore_.getSplit(entityId)); + o3v.util.extendObject(newSelected, this.getSelected()); + delete newSelected[entityId]; + this.selectMultiple( + Object.keys(newSelected)); +}; + +o3v.SelectManager.prototype.expandPinned = function(entityId) { + this.unpin(entityId); + this.pinMultiple( + Object.keys(this.entityStore_.getSplit(entityId))); +}; + +/////////////////////////////////////////////////////////////////////////// +// Mode control. +/////////////////////////////////////////////////////////////////////////// +o3v.SelectManager.prototype.setMode = function(mode) { + this.mode_ = mode; +}; + +/////////////////////////////////////////////////////////////////////////// +// Render interface. +/////////////////////////////////////////////////////////////////////////// + +// Callback from rendering. Returns true if something has changed. +o3v.SelectManager.prototype.recalculate = function() { + var updates = false; + var garbage = []; + // TODO(wonchun): use Interpolant registration to handle stuff + // like this. Dynamic interoplant insert/remove require a bit + // more logic (like that below). Also consider using a "freelist" of + // interpolators to avoid GC churn. + // Updates interpolant state, and marks defunct interpolators. + for (var entityId in this.interpolants_) { + var interpolant = this.interpolants_[entityId]; + var more_updates = interpolant.tween(); + updates |= more_updates; + // Is this an interpolant we can reclaim? + if (entityId != this.CURRENT_LAYER_INTERPOLANT && + entityId != this.OTHER_LAYER_INTERPOLANT && + !more_updates) { + // TODO(wonchun): is it possible to simply delete this here? + garbage.push(entityId); + } + } + + // Sweeps defunct interpolators. + garbage.forEach(function(entityId) { + delete this.interpolants_[entityId]; + }, this); + + return updates; +}; + +o3v.SelectManager.prototype.clearSelection = function() { + this.clearHidden(true); + this.clearPinned(true); + this.clearSelected(true); +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/shaders.txt b/wormbrowser-appengine/src/main/webapp/scripts/shaders.txt new file mode 100644 index 0000000..a94044b --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/shaders.txt @@ -0,0 +1,75 @@ +/** shader_fragment **/ + +#ifdef GL_ES +precision mediump float; +#endif + +uniform sampler2D u_texture; +uniform float u_opacity; + +varying vec3 v_normal; +varying vec2 v_texcoord; + +const vec3 kLightVector = vec3(0.3, 0.3, -0.9); +const vec3 kHalfVector = vec3(0.154, 0.154, -0.974); + +void main(void) { + vec3 normal = normalize(v_normal); + // half-Lambert lighting. + float light = 0.5 + 0.5*dot(normal, kLightVector); + float diffuse = light*u_opacity; + // Specular with fake fresnel effect. + float specular = max(0.0, dot(normal, kHalfVector)); + specular *= 0.7 + 0.3*normal.z; + specular *= specular; + specular *= u_opacity; + vec3 fetch = texture2D(u_texture, v_texcoord).rgb; + gl_FragData[0] = vec4(diffuse*fetch + vec3(specular), u_opacity); +} + +/** shader_fragment_id **/ + +precision highp float; +varying vec4 v_color; +void main() { + gl_FragColor = v_color; +} + +/** shader_vertex **/ + +uniform mat4 u_mvp; + +attribute vec3 a_position; +attribute vec2 a_texcoord; +attribute vec3 a_normal; + +varying vec2 v_texcoord; +varying vec3 v_normal; + +void main(void) { + v_normal = vec3(u_mvp * vec4(a_normal, 0)); + v_texcoord = a_texcoord; + gl_Position = u_mvp * vec4(a_position, 1.0); +} + +/** shader_vertex_id **/ + +uniform mat4 u_mvp; +uniform float u_colorScale; + +attribute vec3 a_position; +attribute vec3 a_normal; +attribute float a_colorIndex; + +varying vec4 v_color; +varying vec3 v_normal; + +void main() { + float scaledColor = a_colorIndex * u_colorScale; + float redColor = floor(scaledColor / (256.0 * 256.0)); + float greenColor = floor((scaledColor - redColor * 256.0 * 256.0) / 256.0); + float blueColor = (scaledColor - greenColor * 256.0 - redColor * 256.0 * 256.0); + v_color = vec4(redColor / 255.0, greenColor / 255.0, blueColor / 255.0, 1); + v_normal = vec3(u_mvp * vec4(a_normal, 0)); + gl_Position = u_mvp * vec4(a_position, 1.0); +} diff --git a/wormbrowser-appengine/src/main/webapp/scripts/viewer.js b/wormbrowser-appengine/src/main/webapp/scripts/viewer.js new file mode 100755 index 0000000..9e2376d --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/viewer.js @@ -0,0 +1,314 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Main object for open-3d-viewer. + */ + +o3v.Viewer = function() { + // Check WebGL and redirect to explanatory page if not supported. + if (!this.checkWebGL()) { + window.location.href = 'no_webgl.html'; + return; + } + + // Tracks history. + this.history_ = new o3v.History(window); + + // Keeps track of graphical elements on the page. + this.ui_ = new o3v.MainUI(this.nextModelCallback.bind(this)); + + // Navigation tracker takes input and passes it to the renderer. + this.navigator_ = new o3v.Navigator(this.changeCallback.bind(this), + this.ui_.getCanvas(), + this.history_); + + // Keeps track of selection state. + this.select_ = new o3v.SelectManager(this.changeCallback.bind(this), + this.navigator_); + + // Keeps track of layer opacities. + this.layerOpacityManager_ = new o3v.LayerOpacityManager(); + + // UI for layers (slider). + this.layersUI_ = new o3v.LayersUI(this.layerOpacityManager_); + + // Converts opacity inputs (layers & selection) into outputs for renderer. + this.opacity_ = new o3v.OpacityManager(this.layerOpacityManager_, + this.select_, + this.changeCallback.bind(this)); + + // ContentManager manages models. + this.contentManager_ = new o3v.ContentManager(); + + // Interfaces with the renderer. + this.render_ = new o3v.RenderInterface(this.ui_.getCanvas(), this.opacity_, + this.contentManager_); + window.addEventListener('resize', + function() { + this.render_.handleResize(); + this.changeCallback(); + }.bind(this), false); + + // Searchbox. + this.search_ = new o3v.Search(this.selectCallback.bind(this)); + + // Input setup. + var inputHandler = new o3v.InputHandler(window); + this.setupInputHandlers_(inputHandler); + + // Navigation buttons. + new o3v.navUI(this.navigator_.reset.bind(this.navigator_), + this.navigator_.drag.bind(this.navigator_), + this.navigator_.scroll.bind(this.navigator_)); + + // Platform-specific gestures. + this.gestures_ = new o3v.Gestures(); + + // Labels. Requires input handler to track clicks on labels. + this.label_ = new o3v.Label(inputHandler, this.select_, this.render_, + this.ui_.getCanvas(), + $('#labelcontainer')[0], + this.navigator_, + this.gestures_); + + this.loadedModel_ = false; + this.loadedMetadata_ = false; + + // Load first model. + this.ui_.showLoadingFeedback(true); + this.contentManager_.nextModel(this.onModelInfoLoad_.bind(this), + this.render_.onMeshLoad.bind(this.render_), + this.onModelLoad_.bind(this), + this.onMetadataLoad_.bind(this)); + + // Restore any pending state and listen to further history changes. + this.historyStarted_ = false; +}; + +o3v.Viewer.REFRESH_INTERVAL_ = 20; // 1000/x Hz (this controls interpolants) + +o3v.Viewer.prototype.checkWebGL = function() { + if (!o3v.webGLUtil.browserSupportsWebGL(document.getElementById('gltest'))) { + return false; + } else { + return true; + } +}; + +o3v.Viewer.prototype.onModelInfoLoad_ = function(modelInfo) { + // Update UI. + this.ui_.setModelSelectorButton(modelInfo.modelPath + 'model_icon.png'); + this.layersUI_.buildAll(this.ui_.getLastButton(), modelInfo.numLayers, + modelInfo.modelPath + 'layer_icons.png'); + + // Update slider. + this.layerOpacityManager_.init(modelInfo.numLayers); +}; + +o3v.Viewer.prototype.onMetadataLoad_ = function() { + var metadata = this.contentManager_.getMetadata(); + + // Update modules that rely on metadata. + this.search_.reset( + this.contentManager_.getMetadata().getAutocompleteList()); + this.select_.reset(metadata); + this.label_.reset(metadata); + + this.opacity_.reset(metadata); + + this.loadedMetadata_ = true; + + if (this.loadedModel_) { + this.onModelAndMetadataLoad_(); + } +}; + +o3v.Viewer.prototype.onModelLoad_ = function() { + this.loadedModel_ = true; + this.render_.onModelLoad(); + + if (this.loadedMetadata_) { + this.onModelAndMetadataLoad_(); + } +}; + +// Called when both model and metadata are loaded. +o3v.Viewer.prototype.onModelAndMetadataLoad_ = function() { + // This requires both meshes and metadata to be loaded. + this.contentManager_.getMetadata().computeBboxes(this.render_.getBboxes()); + this.navigator_.resetModel( + this.contentManager_.getMetadata().getRootEntity().bbox); + + this.ui_.showLoadingFeedback(false); + + if (this.historyStarted_) { + // Reset view. + this.navigator_.reset(); + } else { + // Reset view initially. + // TODO(dkogan): Handle model selection. + this.history_.start(); + this.historyStarted_ = true; + } + + // Refresh. + this.changeCallback(); +}; + +o3v.Viewer.prototype.nextModelCallback = function() { + this.loadedModel_ = false; + this.loadedMetadata_ = false; + + this.render_.reset(); + + this.ui_.showLoadingFeedback(true); + + this.contentManager_.nextModel(this.onModelInfoLoad_.bind(this), + this.render_.onMeshLoad.bind(this.render_), + this.onModelLoad_.bind(this), + this.onMetadataLoad_.bind(this)); +}; + +// Callback that happens when something changes that requires a re-render. +// Because of interpolant use, we need to repeat changeCallback until +// all the changes are done processing. +o3v.Viewer.prototype.changeCallback = function(opt_checkBeforeProceeding) { + if (!this.loadedMetadata_ || !this.loadedModel_) { + window.setTimeout(this.changeCallback.bind(this), + o3v.Viewer.REFRESH_INTERVAL_); + return; + } + + if (opt_checkBeforeProceeding) { + var needUpdate = false; + needUpdate = this.select_.recalculate() || needUpdate; + needUpdate = this.opacity_.recalculate() || needUpdate; + needUpdate = this.navigator_.recalculate() || needUpdate; + if (!needUpdate) { + return; + } + } + + // Refresh view. + this.render_.refresh(this.navigator_.camera); + this.label_.refresh(); + + window.setTimeout(function () { + this.changeCallback(true); + }.bind(this), o3v.Viewer.REFRESH_INTERVAL_); +}; + +o3v.Viewer.prototype.selectCallback = function(searchTerm) { + if (!this.loadedMetadata_ || !this.loadedModel_) { + return; + } + var entityIds = this.contentManager_.getMetadata().searchToEntityIds( + searchTerm); + + this.select_.selectMultiple(entityIds); + + if (this.select_.haveSelected()) { + this.opacity_.exposeSelected(); + var bbox = this.navigator_.focusOnEntities(this.select_.getSelected()); + this.navigator_.goToBBox(bbox); + } else { + this.navigator_.resetNavParameters(); + } + + // TODO(dkogan|arthurb): Reimplement. + //this.select_.exposeSelected(); +}; + +o3v.Viewer.prototype.handleClick = function(left, top, modifiers) { + var externalId = this.render_.identify(left, top); + + if (!externalId) { + this.select_.clearSelection(); + this.navigator_.resetNavParameters(); + } else { + var entityId = this.contentManager_.getMetadata().externalIdToId( + externalId); + // Identify entity under click. + var entity = this.contentManager_.getMetadata().getEntity(entityId); + if (this.gestures_.isHideClick( + modifiers[o3v.InputHandler.CONTROL], + modifiers[o3v.InputHandler.META])) { + // Impossible to click on a hidden entity, so no need for toggle. + this.select_.hide(entityId); + } else if (modifiers[o3v.InputHandler.SHIFT]) { + this.select_.togglePin(entityId); + } else { + this.select_.pickMultiple([entityId]); + + var bbox = this.navigator_.focusOnEntities(this.select_.getSelected()); + this.navigator_.goToBBox(bbox, true); + } + } +}; + +o3v.Viewer.prototype.setupInputHandlers_ = function(inputHandler) { + // Click to select. + inputHandler.registerHandler(o3v.InputHandler.CLICK, + $('#viewer')[0], + this.handleClick.bind(this), true); + + // Mouse-based navigation. + inputHandler.registerHandler(o3v.InputHandler.DRAG, + $('#viewer')[0], + this.navigator_.drag.bind(this.navigator_), + true); + inputHandler.registerHandler(o3v.InputHandler.SCROLL, + $('#viewer')[0], + this.navigator_.scroll.bind(this.navigator_), + true); + + new o3v.NavKeyHandler( + inputHandler, + function(x, y, z) { + // keyboard move + this.navigator_.drag(x * 10, y * -10); + this.navigator_.scroll(0, z * 30); + }.bind(this), + this.navigator_.reset.bind(this.navigator_)); + + // Clicking on help toggles help. + $('#help-hidden').click(this.toggleHelp_.bind(this)); + $('#help').click(this.toggleHelp_.bind(this)); + + // Miscellaneous keys. + inputHandler.registerHandler( + o3v.InputHandler.KEYDOWN, null, + function(keyCode) { + if (keyCode == 191) { // '?' + this.toggleHelp_(); + } else if (keyCode == 67) { // 'c' + this.render_.toggleColored(); + this.changeCallback(); + } else if (keyCode == 66) { // 'b' + this.label_.toggleBoundingBox(); + this.changeCallback(); + } + }.bind(this)); +}; + +o3v.Viewer.prototype.toggleHelp_ = function() { + if ($('#help')[0].style['display'] != 'block') { + $('#help')[0].style['display'] = 'block'; + $('#help-hidden')[0].style['display'] = 'none'; + } else { + $('#help')[0].style['display'] = 'none'; + $('#help-hidden')[0].style['display'] = 'block'; + } +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/webgl.js b/wormbrowser-appengine/src/main/webapp/scripts/webgl.js new file mode 100644 index 0000000..379c86d --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/webgl.js @@ -0,0 +1,284 @@ +'use strict'; + +// Utility wrapper around WebGL. Perserves WebGL semantics, so it +// isn't too object-oriented. + +function createContextFromCanvas(canvas) { + var context = canvas.getContext('experimental-webgl'); + // Automatically use debug wrapper context, if available. + return typeof WebGLDebugUtils !== 'undefined' ? + WebGLDebugUtils.makeDebugContext(context, function(err, funcName, args) { + throw WebGLDebugUtils.glEnumToString(err) + " by " + funcName; + }) : context; +}; + +function Shader(gl, source, shaderType) { + this.gl_ = gl; + this.handle_ = gl.createShader(shaderType); + gl.shaderSource(this.handle_, source); + gl.compileShader(this.handle_); + if (!gl.getShaderParameter(this.handle_, gl.COMPILE_STATUS)) { + throw this.info(); + } +} + +Shader.prototype.info = function() { + return this.gl_.getShaderInfoLog(this.handle_); +} + +Shader.prototype.type = function() { + return this.gl_.getShaderParameter(this.handle_, this.gl_.SHADER_TYPE); +} + +function vertexShader(gl, source) { + return new Shader(gl, source, gl.VERTEX_SHADER); +} + +function fragmentShader(gl, source) { + return new Shader(gl, source, gl.FRAGMENT_SHADER); +} + +function Program(gl, shaders) { + this.gl_ = gl; + this.handle_ = gl.createProgram(); + shaders.forEach(function(shader) { + gl.attachShader(this.handle_, shader.handle_); + }, this); + gl.linkProgram(this.handle_); + if (!gl.getProgramParameter(this.handle_, gl.LINK_STATUS)) { + throw this.info(); + } + + var numActiveAttribs = gl.getProgramParameter(this.handle_, + gl.ACTIVE_ATTRIBUTES); + this.attribs = []; + this.set_attrib = {}; + for (var i = 0; i < numActiveAttribs; i++) { + var active_attrib = gl.getActiveAttrib(this.handle_, i); + var loc = gl.getAttribLocation(this.handle_, active_attrib.name); + this.attribs[loc] = active_attrib; + this.set_attrib[active_attrib.name] = loc; + } + + var numActiveUniforms = gl.getProgramParameter(this.handle_, + gl.ACTIVE_UNIFORMS); + this.uniforms = []; + this.set_uniform = {}; + for (var j = 0; j < numActiveUniforms; j++) { + var active_uniform = gl.getActiveUniform(this.handle_, j); + this.uniforms[j] = active_uniform; + this.set_uniform[active_uniform.name] = gl.getUniformLocation( + this.handle_, active_uniform.name); + } + + this.enabledVertexAttribArrays_ = {}; +}; + +Program.prototype.info = function() { + return this.gl_.getProgramInfoLog(this.handle_); +}; + +Program.prototype.use = function() { + this.gl_.useProgram(this.handle_); +}; + +Program.prototype.enableVertexAttribArrays = function(attribArrays) { + for (var i = 0; i < attribArrays.length; ++i) { + this.enabledVertexAttribArrays_[attribArrays[i]] = true; + var loc = this.set_attrib[attribArrays[i]]; + if (loc !== undefined) { + this.gl_.enableVertexAttribArray(loc); + } + } +}; + +Program.prototype.disableVertexAttribArrays = function(attribArrays) { + for (var i = 0; i < attribArrays.length; ++i) { + this.enabledVertexAttribArrays_[attribArrays[i]] = false; + var loc = this.set_attrib[attribArrays[i]]; + if (loc !== undefined) { + this.gl_.disableVertexAttribArray(loc); + } + } +}; + +Program.prototype.vertexAttribPointers = function(attribArrays) { + // Only use the enabled ones. + var numAttribs = attribArrays.length; + for (var i = 0; i < numAttribs; ++i) { + var params = attribArrays[i]; + var loc = this.set_attrib[params.name]; + if (this.enabledVertexAttribArrays_[params.name]) { + var typeBytes = 4; // TODO: 4 assumes gl.FLOAT, use params.type + this.gl_.vertexAttribPointer(loc, params.size, this.gl_.FLOAT, + !!params.normalized, typeBytes*params.stride, + typeBytes*params.offset); + } + } +}; + +// TODO: seems like texture ought to be a class... + +function textureFromArray(gl, width, height, array, opt_texture) { + var opt_texture = opt_texture || gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, opt_texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, width, height, 0, + gl.RGB, gl.UNSIGNED_BYTE, array); + return opt_texture; +} + +function textureFromImage(gl, image, opt_texture) { + // TODO: texture formats. Color, MIP-mapping, etc. + opt_texture = opt_texture || gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, opt_texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, + gl.LINEAR_MIPMAP_LINEAR); + gl.generateMipmap(gl.TEXTURE_2D); + return opt_texture; +} + +var TEXTURE_CACHE = {}; + +function textureFromUrl(gl, url, opt_callback) { + var cached_texture = TEXTURE_CACHE[url]; + if (cached_texture) { + return cached_texture; + } + + var texture = gl.createTexture(); + var image = new Image; + image.onload = function() { + textureFromImage(gl, image, texture); + opt_callback && opt_callback(gl, texture); + }; + image.onerror = function() { + textureFromArray(gl, 1, 1, new Uint8Array([255, 255, 255]), texture); + opt_callback && opt_callback(gl, texture); + }; + image.src = url; + + TEXTURE_CACHE[url] = texture; + return texture; +} + +function attribBufferData(gl, attribArray) { + var attribBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, attribBuffer); + gl.bufferData(gl.ARRAY_BUFFER, attribArray, gl.STATIC_DRAW); + return attribBuffer; +} + +function indexBufferData(gl, indexArray) { + var indexBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW); + return indexBuffer; +} + +function addToDisplayList(displayList, begin, end) { + var back = displayList.length - 1; + var lastEnd = displayList[back]; + if (begin === lastEnd) { + displayList[back] = end; + } else { + displayList.push(begin, end); + } +} + +// TODO: names/lengths don't really belong here; they probably belong +// with the displayList stuff. +function Mesh(gl, attribArray, indexArray, attribArrays, texture, + opt_names, opt_lengths, opt_bboxen, opt_startColorIndex) { + this.gl_ = gl; + this.attribArrays_ = attribArrays; // TODO: rename to vertex format! + this.numIndices_ = indexArray.length; + this.texture_ = texture || null; + + if (opt_bboxen) { + this.bboxen_ = attribBufferData(gl, opt_bboxen); + } + + this.vbo_ = attribBufferData(gl, attribArray); + this.ibo_ = indexBufferData(gl, indexArray); + + this.names_ = opt_names || []; + this.lengths_ = opt_lengths || []; + this.starts_ = []; // TODO: typed array? + + var numLengths = this.lengths_.length; + var start = 0; + for (var i = 0; i < numLengths; ++i) { + this.starts_[i] = start; + start += this.lengths_[i]; + } + + if (opt_startColorIndex !== undefined) { + // TODO(dkogan): Fix this array length calculating hack. + var arrayLen = -1; + for (var i = 0; i < numLengths; i++) { + var startIndex = this.starts_[i]; + var length = this.lengths_[i]; + for (var j = startIndex; j < startIndex + length; j++) { + if (indexArray[j] >= arrayLen) { + arrayLen = indexArray[j] + 1; + } + } + } + + var colorArray = new Float32Array(arrayLen); + for (var i = 0; i < numLengths; i++) { + var startIndex = this.starts_[i]; + var length = this.lengths_[i]; + for (var j = startIndex; j < startIndex + length; j++) { + colorArray[indexArray[j]] = opt_startColorIndex + i; + } + } + this.cbo_ = attribBufferData(gl, colorArray); + } +} + +Mesh.prototype.bind = function(program, opt_forId) { + var gl = this.gl_; + if (!opt_forId) { + gl.bindTexture(gl.TEXTURE_2D, this.texture_); + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo_); + gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo_); + + program.vertexAttribPointers(this.attribArrays_); + + // TODO(wonchun): Do this the right way. + if (opt_forId) { + gl.bindBuffer(gl.ARRAY_BUFFER, this.cbo_); + gl.vertexAttribPointer(program.set_attrib['a_colorIndex'], + 1, gl.FLOAT, false, 4, 0); + } +}; + +Mesh.prototype.draw = function(opt_length, opt_offset) { + if (opt_length === 0) return; + + opt_length = opt_length || this.numIndices_; + opt_offset = opt_offset || 0; + var gl = this.gl_; + gl.drawElements(gl.TRIANGLES, opt_length, gl.UNSIGNED_SHORT, 2*opt_offset); +}; + +Mesh.prototype.drawList = function(displayList) { + var numDraws = displayList.length; + for (var i = 0; i < numDraws; i += 2) { + var drawStart = displayList[i]; + var drawLength = displayList[i+1] - drawStart; + this.draw(drawLength, drawStart); + } +}; + +Mesh.prototype.bindAndDraw = function(program, opt_forId) { + this.bind(program, opt_forId); + this.draw(); +}; diff --git a/wormbrowser-appengine/src/main/webapp/scripts/webgl_util.js b/wormbrowser-appengine/src/main/webapp/scripts/webgl_util.js new file mode 100644 index 0000000..c84c3fc --- /dev/null +++ b/wormbrowser-appengine/src/main/webapp/scripts/webgl_util.js @@ -0,0 +1,44 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview WebGL utility functions + */ +o3v.webGLUtil = { + /** + * Tests whether the browser supports WebGL. + * @param {Element} canvasEl A canvas element in the current document. + * @return {boolean} True iff browser supports WebGL. + */ + browserSupportsWebGL: function (canvasEl) { + try { + if (!canvasEl) { + return false; + } + if (!window.WebGLRenderingContext) { + return false; + } + var gl = canvasEl.getContext('webgl'); + if (!gl) { + gl = canvasEl.getContext('experimental-webgl'); + } + if (!gl) { + return false; + } + return true; + } catch (err) { + return false; + } + } +};