[
  {
    "path": ".gitignore",
    "content": "/node_modules\n/.idea\n.DS_Store\nbuild/main.js\n*.pyc\nbuild/\n*.swp\n.sass-cache\nnpm-debug.log\n\n*.asd\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to contribute\n\nWe'd love to accept your patches and contributions to this project. There are\njust a few small guidelines you need to follow.\n\n## Contributor License Agreement\n\nContributions to this project must be accompanied by a Contributor License\nAgreement. You (or your employer) retain the copyright to your contribution,\nthis simply gives us permission to use and redistribute your contributions as\npart of the project. Head over to <https://cla.developers.google.com/> to see\nyour current agreements on file or to sign a new one.\n\nYou generally only need to submit a CLA once, so if you've already submitted one\n(even if it was for a different project), you probably don't need to do it\nagain.\n\n## Code reviews\n\nAll submissions, including submissions by project members, require review. We\nuse GitHub pull requests for this purpose. Consult [GitHub Help] for more\ninformation on using pull requests.\n\n[GitHub Help]: https://help.github.com/articles/about-pull-requests/"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "<table>\n  <tr>\n    <td>\n      This project is no longer actively maintained by the Google Creative Lab but remains here in a read-only Archive mode so that it can continue to assist developers that may find the examples helpful. We aren’t able to address all pull requests or bug reports but outstanding issues will remain in read-only mode for reference purposes. Also, please note that some of the dependencies may not be up to date and there hasn’t been any QA done in a while so your mileage may vary.\n      <br><br>\n      For more details on how Archiving affects Github repositories see <a href=\"https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-archiving-repositories\">this documentation </a>.\n      <br><br>\n      <b>We welcome users to fork this repository</b> should there be more useful, community-driven efforts that can help continue what this project began.\n    </td>\n  </tr>\n</table>\n\n# Musical Forest\n\nMusical Forest is a multiplayer musical experiment in virtual reality. It uses copresence —the synchronization of multiple people in a virtual space— to allow people to play music together in VR. WebSockets are used to sync all of the connected players in real-time. \n\nMusical Forest, a [WebVR Experiment](https://webvrexperiments.com/).\n<br>\n<br>\n\n## Basic Interaction \n\nEach of the shapes triggers a note when it’s hit. Users can navigate the space, play notes and also hear and play with all of the other players in the space at the same time. \n<br>\n![alt text](https://forest.webvrexperiments.com/static/img/MusicalForest.gif \"The Musical Forest, mixed reality interaction example\")\n<br>\n<br>\n\n## Objects & Audio\n\nThere are three different shapes of musical objects corresponding to three different sets of sounds. Each set has six notes, chosen from a pentatonic scale. \n\n* Spheres: Percussion (Conga, Woodblock, COWBELL!)\n* Triangular Pyramids: Voice + Flute\n* Cubes: Marimba\n\nSounds are positioned in 3D space using the Web Audio API’s PannerNode. \n<br>\n<br>\n\n## Headsets & Interaction Models\n\nMusical Forest responsively adapts features depending on the capabilities of the VR Hardware. \n\n### Vive/Oculus\n\nPlay: hit the shapes with your controller to hear its sound. The volume of the sound changes depending on how hard you hit it. <br>\nCreate: tap the trigger to create a new shape. Rotate the circular pad to change the note. <br>\nRearrange: place the controller over an existing object, press and hold the trigger to grab it. Move your controller and release to move it to a new space. Hovering your controller over an object and rotating the circular pad will change the type of shape and it’s sound.<br>\nNavigation: move within the bounds of your roomscale environment to interact with the objects within the experience.\n\n### Daydream\n\nPlay: Hit the shapes with your controller to hear their sounds. <br>\nNavigation: point the Daydream controller at the ground and a circle will appear. Press the main button on the controller to teleport to that highlighted spot.\n\n### Cardboard\n\nPlay: gaze at an object and see it glow. Tap the interaction button to hear that object.<br>\nNavigation: gaze at the ground and a circle will appear. Tap the interaction button to teleport to the highlighted spot.\n\n### Magic Window\n\nInteraction: tap any object to hear the sound of that object. <br>\nNavigation: gaze at the ground and a circle will appear. Tap anywhere to teleport to where the reticle is pointing.\n\n### Desktop\n\nInteraction: use the mouse to click any object to hear its sound. The volume of the sound is dictated by the object’s distance from the user.<br>\nNavigation: use the WASD keys on the keyboard. Use the mouse to change the field of view by clicking in empty space and dragging. \n<br>\n<br>\n\n## Technologies Used\n### Frontend\n\nMusical Forest uses [A-Frame](https://aframe.io) which is built with the WebVR standard and [Tone.js](https://github.com/Tonejs/Tone.js/) for sound.\n\n### Backend\n\nThe backend is developed in Node.js. To get a full overview of the technologies and libraries used, see the [backend readme](backend/README.md#Description)\n<br>\n<br>\n\n## Running the Frontend Code\n\nDownload the source code, and install all dependencies by running `npm install`. To run the frontend, run `npm start`, this will start a local webserver using `budo` connecting to the default backend server. If you have a local backend server running, append `?server=localhost` to the url. \n<br>\n<br>\n\n## Acknowledgements\n\n[Manny Tan](https://github.com/mannytan), [Igor Clark](https://github.com/igorclark), [Yotam Mann](https://github.com/tambien), [Alexander Chen](https://github.com/alexanderchen), [Jonas Jongejan](https://github.com/halfdanj), [Jeremy Abel](https://github.com/jeremyabel), [Saad Moosajee](https://github.com/moosajee), Alex Jacobo-Blonder, [Ryan Burke](https://github.com/ryburke), and many others at Google Creative Lab.\n"
  },
  {
    "path": "app.yaml",
    "content": "# Copyright 2017 Google Inc.\n#\n#   Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n#   You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n#   Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#   See the License for the specific language governing permissions and\n# limitations under the License.\n\n###########################################################################\n# DO NOT MODIFY THIS FILE WITHOUT UNDERSTANDING THE SECURITY IMPLICATIONS #\n###########################################################################\n\n# The version is automatically generated based on the current git hash.\n# If there are uncommitted changes, a '-dev' suffix will be added. You do\n# not need to modify it here.\nruntime: python27\napi_version: 1\nthreadsafe: true\n\nhandlers:\n#        - url: /\n#          static_files: \"index.html\"\n#          secure: always\n#          upload: index\\.html\n#          http_headers:\n#            X-Frame-Options: \"DENY\"\n#            Strict-Transport-Security: \"max-age=2592000; includeSubdomains\"\n#            X-Content-Type-Options: \"nosniff\"\n#            X-XSS-Protection: \"1; mode=block\"\n\n        - url: /build/\n          static_dir: build/\n          secure: always\n          http_headers:\n            X-Frame-Options: \"DENY\"\n            Strict-Transport-Security: \"max-age=2592000; includeSubdomains\"\n            X-Content-Type-Options: \"nosniff\"\n            X-XSS-Protection: \"1; mode=block\"\n\n        - url: /static/\n          static_dir: static/\n          secure: always\n          http_headers:\n            X-Frame-Options: \"DENY\"\n            Strict-Transport-Security: \"max-age=2592000; includeSubdomains\"\n            X-Content-Type-Options: \"nosniff\"\n            X-XSS-Protection: \"1; mode=block\"\n\n\n\n# All URLs should be mapped via the *_ROUTES variables in the src/main.py file.\n# See https://webapp-improved.appspot.com/guide/routing.html for information on\n# how URLs are routed in the webapp2 framework. Do not add additional handlers\n# directly here.\n        - url: /.*\n          script: python.main.app\n          secure: always\n\nlibraries:\n        - name: django\n          version: latest\n\n        - name: jinja2\n          version: latest\n\n        - name: webapp2\n          version: latest\n\nskip_files:\n         - ^(.*/)?#.*#$\n         - ^(.*/)?.*~$\n         - ^(.*/)?.*\\.py[co]$\n         - ^(.*/)?.*/RCS/.*$\n         - ^(.*/)?\\..*$\n         - README\n         - util.sh\n         - run_tests.py\n         - .*_test.py\n         - js/.*\n         - backend/.*\n         - ^node_modules/(.*/)?\n         - ^(.*/)?app\\.yaml\n         - ^(.*/)?app\\.yml\n         - ^\\.git/.*\n"
  },
  {
    "path": "backend/.babelrc",
    "content": "{\n  \"presets\": [\"es2015\", \"stage-2\"],\n  \"plugins\": []\n}\n"
  },
  {
    "path": "backend/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# tmp/testing\nscratch\ntmp\n*.bak\n\n# vim swap files\n*.swp\n*.swo\n\n# Mac filesystem entries\n.DS_Store\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# babel-translated es5 output\ndist\n\n# Dependency directories\nnode_modules\njspm_packages\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# vagrant anything\n.vagrant\n\n# allow .gitignore files\n!.gitignore\n"
  },
  {
    "path": "backend/README.md",
    "content": "# webvr-experiments: Musical Forest back-end\n\n## Contents\n\n* [Description](#description)\n* [Running the app locally](#running-the-app-locally)\n* [Deploying in production](#deploying-in-production)\n* [How the app works](#how-the-app-works)\n  * [Basic operation](#basic-operation)\n  * [Managing state](#managing-state)\n  * [Starting the server](#starting-the-server)\n  * [Handling SSL connections](#handling-ssl-connections)\n  * [Managing connections](#managing-connections)\n  * [Managing peer servers](#managing-peer-servers)\n  * [Managing rooms](#managing-rooms)\n  * [Handling client messages](#handling-client-messages)\n* [Connecting clients to the server](#connecting-clients-to-the-server)\n* [Messages](#messages)\n* [URLs](#urls)\n* [Interacting in rooms](#interacting-in-rooms)\n\n---\n\n## Description\n\nThis is the backend server for the Chrome WebVR Experiment [Musical Forest](https://forest.webvrexperiments.com/). It's a dynamic room-based [WebSocket](https://en.wikipedia.org/wiki/WebSocket) server app using Google Cloud [PubSub](https://cloud.google.com/pubsub/) and [Datastore](https://cloud.google.com/datastore/). It's written in Javascript ES6 using [`node.js`](https://nodejs.org/) and the following [`npm`](https://www.npmjs.com/ )modules:\n\n* `uws` websocket server\n\n* `redux` and `javascript-state-machine` for state management\n\n* `redux-sagas` for managing asynchronous/long-running tasks and composite async actions\n\n* `@google-cloud/*` for using GCP services\n\n* `http-proxy` and `hashring` to distribute connections consistently between app instances\n\n* `fast-ratelimit` to hard-limit per-client message rates\n\n* `tracer` for logging\n\n\n---\n\n## Running the app locally\n\n#### Install prerequisites\n\n* [Install gcloud](https://cloud.google.com/sdk/downloads)\n\n* `node.js` LTS version 6 + `npm` (Mac: `brew install node@6 npm` - [notes about installing `node` on Mac with Homebrew](http://apple.stackexchange.com/questions/171530/how-do-i-downgrade-node-or-install-a-specific-previous-version-using-homebrew/207883))\n\n* `gcc` C compiler for native `node.js` modules (Xcode 8.1+ if running on Mac)\n\n* [`jq`](https://stedolan.github.io/jq/) (Mac: `brew install jq`)\n\n\n#### Authenticate with GCP\n\n```\ngcloud auth login\ngcloud config set project <project-id>\ngcloud auth application-default login\n```\n\n#### Install node.js modules\n\n`npm install --no-optional`\n\n#### Run the app\n\nStart the `datastore` & `pubsub` emulators:\n\n`npm run emulators`\n\nIn a new shell, start the app in dev mode using the emulators:\n\n`npm run start_emulated`\n\nYou can now connect to the server from the main web app by appending `?server=ws://localhost:9100` to the URL.\n\n#### Test a connection to the app from command line (optional)\n\nInstall a solid command-line websocket client:\n\n* Install [go](https://golang.org/) (Mac: `brew install go # make sure to set up a ${GOPATH} dir & env var`)\n* `go get -u github.com/hashrocket/ws`\n\nConnect to a websocket:\n\n* `ws ws://localhost:9100/<viewer_type>` (see [URLs](#urls) below regarding viewer types)\n* _paste in a message, e.g. `{\"t\":\"g_s\",\"d\":{\"spId\":\"973c742d-866f-4b25-9ae6-8830e7f13e59\"}}` as above_\n\n#### Run multiple local app instances (optional)\n\nThe app defaults to using ports `8100` for the application server and `9100` for the inter-node connection balancer (see `src/server/server-startup.js` and `src/server/balancer.js`).\n\nTo run multiple instances, `export WS_SERVER_PORT=<other-server-port>` and `export WS_BALANCER_PORT=<other-balancer-port>` in a new shell before running `npm run start_emulated`. Instances will coordinate via the local PubSub and Datastore emulators.\n\n---\n\n## Deploying in production\n\nThe app is designed to be deployed as a series of single-threaded application instances (pods) running in a Docker container cluster via [Kubernetes](https://kubernetes.io/) on [Google Container Engine](https://cloud.google.com/container-engine/). Deploying and running clustered applications is beyond the scope of this document, but the following configuration and setup points are necessary to run this app in such environments.\n\nYou'll need to create a `Dockerfile` for your environment along with an `entrypoint` script to run the application on the pods, and modify the application configuration as follows:\n\n1. Your `entrypoint` script must `export` the following environment variables:\n\n    * `PROJECT_ID` - your Google Cloud Platform project name\n  \n    * `ENVIRONMENT_NAME` - a unique identifier to separate different deployment environments, and keeps Datastore and PubSub resources separate for each deployment within a project. As an example, running the app locally via `package.json` uses your computer's `hostname`; you might want to name a particular deployment by region or type\n  \n    * `LOCAL_IP_ADDRESS` - the private-range IP address by which each pod is accessible and identifies itself to other nodes in the cluster, used by pods to health-check each other when necessary\n\n2. When automatically scaled up or down, application nodes synchronize with new and removed nodes using a PubSub topic. The name of this topic is constructed from your `ENVIRONMENT_NAME` at startup in `src/config.js`\n\n3. Set `PRODUCTION_ENVIRONMENT_PROJECT_ID` and `PRODUCTION_ENVIRONMENT_REQUIRED_ORIGIN` in `src/config.js` to reflect the Google Cloud Platform project name and [WebSocket Origin header](https://en.wikipedia.org/wiki/WebSocket#Protocol_handshake) required for connections to your production cluster\n\nRefer to the [Container Engine](https://cloud.google.com/container-engine/docs/) and [Kubernetes](https://kubernetes.io/docs/home/) documentation for further information on how to set up and manage clusters, deployments, services, etc.\n\n\n---\n\n## How the app works\n\n##### Basic operation\n\nThe app accepts client connections to its websocket server (`src/server/websocket-server.js`), parses the requested URL to work out headset type and whether a specific room has been chosen, and then tries to connect the client to a room. It maintains state about which clients and spheres are in which room, which rooms are full or available, and handles all communications from and to clients in all rooms.\n\n##### Managing state\n\nThe app holds state in a `redux` state store (`src/store.js`), mutated in reducers (`src/*/*-reducer.js`) via messages `dispatch()`'d to the store, and accessed from the store in async or composite action sequences (`src/server/server-sagas.js`, `src/rooms/room-sagas.js`, `src/messages/message-sagas.js`) using methods provided by the `redux-sagas` module.\n\n##### Starting the server\n\nServers are started (`src/server/server-startup.js`) via a series of composite actions (`src/server/server-sagas.js`, `src/server/server-setup.js`) defined as `redux-sagas` generator functions, initiated by a server setup action dispatched to the `redux` store at startup (`src/index.js`).\n\nThe server requires the environment variables `ENVIRONMENT_NAME`, `LOCAL_IP_ADDRESS` and `PROJECT_ID` in order to run. These are set by the `npm` and `gke` startup tasks, according to where the app's being run.\n\n##### Handling SSL connections\n\nIf the environment variable `USE_SSL` is set to `true`, the server requires the environment variable `SSL_CERT_HOST_NAME` to be set too, and retrieves the SSL certificate for that hostname on startup from a [Google Cloud Storage](https://cloud.google.com/storage/) (GCS) bucket in the GCP project, named `<project-name>-ssl`. The full certificate chain file (named `fullchain.pem`) and the private key file (`privkey.pem`) must be stored in a sub-folder of that GCS bucket with the same name as the required hostname.\n\n\n##### Managing connections\nAll connections to a given room are handled by the same instance of the server. The app manages this by running a public/client-facing 'load-balancer' (`src/server/balancer.js`) which proxies client connections either to a local or peer websocket app server, according to which server is associated with the chosen room name in a persistent hash-ring. (See the `hashring` [docs](https://github.com/3rd-Eden/node-hashring) for implementation, and a helpful [explanation](http://blog.plasmaconduit.com/consistent-hashing/).) The hash-ring is stored in the server state (`src/server/server-reducer.js`).\n\nWhen a connection arrives, the server takes the specified room name or chooses one dynamically according to which rooms are available locally (using `avails` in `src/rooms/room-data-reducer.js` and the state machine in `src/rooms/room-state-machine.js'). It then looks up which server should handle the connection in the hash-ring and proxies the connection to that server.\n\nReferences to all websocket connections held by a given server are kept in the server state (`src/server/server-reducer.js`). The only non-store-managed state consists of properties (`id` and current-room info) set directly on websocket connection objects by the websocket server.\n\n##### Managing peer servers\nIn order to maintain a list of peers during automatic scaling of the app in production, the server connects to a PubSub 'sync channel' on startup (`src/server/server-setup.js`), and listens out for sync 'heartbeat' messages from other servers on the channel. It maintains a list of which servers are connected, adding newly-arrived servers to the hash-ring and removing them from it when they time out (stop sending sync heartbeats for a given period).\n\nEach server instance periodically writes information about its state and its PubSub subscription to a ServerSubscriptionInfo record in the Datastore (`src/server/server-sagas.js`), allowing the administrator to retrieve aggregate status info, and enabling the app to check up on timed-out or otherwise dead app instances, removing their status info & PubSub subscriptions as appropriate (also in `src/server/server-sagas.js`).\n\n##### Managing rooms\n\nThe server manages room status info (available, full, setting up, etc) using a state machine (`src/rooms/room-state-machine.js`) for each room, initialised at app startup and accessed via the `redux` store. A series of long-running tasks (`src/rooms/room-sagas.js`) handles activity on the rooms (initialising room content, starting/stopping heartbeats, client activity and sphere hold timeouts) using generator functions managed using the `redux-sagas` module.\n\n##### Handling client messages\n\nClients are restricted to a fixed set of messages which are specified in a JSON schema (`src/messages/message-schema.js`). The websocket server passes received client messages to a message-handler module (`src/messages/message-handler.js`). The message-handler validates incoming messages against the schema (`src/messages/message-validator.js`) and dispatches valid messages to the `redux` state store, using messages constructed from the schema (`src/messages/message-actions.js`). Invalid messages are dropped before getting to dispatch, so any message that gets through at least passes validation.\n\nA series of long-running tasks implemented as `redux-sagas` (`src/messages/message-sagas.js`) handle the validated messages and carry out the requested actions, i.e. sending client position updates and interacting with spheres.\n\n* All possible message interactions between clients and servers are specified in the JSON schema and carried out in the \"sagas\" - any messages not specified like so are silently dropped.\n\n* Any superfluous information in messages received from clients is silently dropped.\n\n* The websocket server rate-limits client messages on both a per-message-type and per-client basis. Default rates are specified in `RATE_LIMIT_INFO` constants (`src/config.js`), and overridden by values retrieved from `PerEnvironmentRateLimitInfo` Datastore records on startup if present (`src/server/server-sagas.js`). You can request the app reload the limit values from Datastore by sending a `SYNC_MESSAGES.LOAD_RATE_LIMITER_INFO` message to the app via PubSub. (That's a deploy/admin task not implemented in the app source, but the mechanism is visible in `sendSyncMessageOfType()` in `src/server/server-sync.js`.) Messages above the thresholds are silently dropped.\n\n* Any messages received from clients with a 'viewer' headset-type are silently dropped.\n\n\n---\n\n## Connecting clients to the server\n\nThe app accepts standard WebSocket connections to \"rooms\" in the forest. The server receives messages over the WebSocket, sends responses and broadcasts messages from other occupants of the room all over the same connection.\n\n----\n\n## Messages\n\nHere's an example of the message format:\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\t// 'from' id of client or server sending the message\n  \"m\": {\t// message\n    \"t\": \"<type>\",\n    \"d\": \"<data>\"\n  }\n}\n```\n\nMessage labels (e.g. `f` or `m` above) are set as constants in the file `src/messages/message-constants.js`. In front-end and back-end code, messages are always constructed and consumed using the labels as property names rather than fixed properties, so that the values used can be changed to friendly names during development if required, and restricted to short strings for use in production.\n\n---\n\n## URLs\n\nClients make connections via URLs of the format `ws://<host>:<port>/<viewer_type>/<room_name>`.\n\n`<viewer_type>` can be `3dof`, `6dof` or `viewer`.\n\nAny action messages sent by `viewer` connections are dropped by the server when running in production.\n\n* To join a specific room, supply both the `<viewer_type>` and `<room_name>` URL components.\n* To have a room chosen for you, supply only the `<viewer_type>` component.\n\nAny other URLs are invalid and connections to them will be closed immediately with an `INVALID_URL` (`i_u`) message:\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"i_u\"\t// invalid_url\n  }\n}\n```\n\n---\n\n## Interacting in rooms\n\n* All interactions with the server are carried out using messages in the above format over the websocket connection.\n* Clients don't need to identify themselves with `<from>` fields, as IDs are associated directly with client websocket connections on the server.\n* The server validates messages received from clients against the appropriate JSON schema for their `<type>`, and immediately drops anything non-conforming, according to the schema in `src/messages/message-schema.js`. It also removes any superfluous content/properties in the messages before storing anything to the room state or broadcasting updates to other clients in the room.\n\n### Joining a room\n\nWhen the first client joins a room, the server kicks off a heartbeat, and sends an incrementing `ROOM_HEARTBEAT` (`r_h`) sync message to all clients present in the room, every 5 seconds:\n\n```\n{\n  \"f\": \"a738cbe0-4dac-4389-aa20-1e6a86c166a9\",\n  \"m\": {\n    \"t\": \"r_h\",\n    \"d\": {\n      \"c\": 3,\t// heartbeat count since room start\n      \"s\": 15\t// heartbeat seconds since room start\n    }\n  }\n}\n```\n\nWhen a new client joins a room, it gets sent a `CONNECTION_INFO` (`c_i`) message containing its ID and the ID of the server it's connected to:\n\n```\n{\n  \"f\": \"a738cbe0-4dac-4389-aa20-1e6a86c166a9\",\n  \"m\": {\n    \"t\": \"c_i\",\n    \"d\": {\n      \"cId\": \"cb439961-3b57-4403-bcc5-208b47565dd1\",    // clientId\n      \"srId\": \"a738cbe0-4dac-4389-aa20-1e6a86c166a9\"    // serverId\n    }\n  }\n}\n```\n\n... followed immediately by a `ROOM_STATUS_INFO` (`r_s_i`) message containing the `ROOM_NAME` (`r_n`), a list of other occupants of the room grouped by viewer type and keyed by their IDs, and a list of spheres in the room with their tones & positions:\n\n```\n{\n  \"f\": \"a738cbe0-4dac-4389-aa20-1e6a86c166a9\",\n  \"m\": {\n    \"t\": \"r_s_i\",\n    \"d\": {\n      \"rn\": \"ctyw\",\t\t// room name\n      \"sb\": 1,\t\t\t// soundbank\n      \"c\": {\t\t\t// clients, grouped by type\n        \"3dof\": [\n          \"cb439961-3b57-4403-bcc5-208b47565dd1\"\n        ]\n      },\n      \"s\": {\t// spheres\n        \"6567cbaa-e6c5-457f-b338-2949b13ff17a\": {\n          \"t\": 4,\t// tone\n          \"p\": {\t// position\n            \"x\": -0.5858704723117225,\n            \"y\": 1.4647926895710155,\n            \"z\": 0.2328255217809887\n          }\n        },\n        \"0e19b0dd-a90b-4fc8-93d5-24a96365ce2c\": {\n          \"t\": 6,\n          \"p\": {\n            \"x\": -0.07124214717156585,\n            \"y\": 0.9642806694401945,\n            \"z\": 1.181824016202171\n          }\n        }\n      }\n    }\n  }\n}\n\n```\n\nExisting clients get a `ROOM_CLIENT_JOIN` (`r_c_j`) message indicating a new client has joined along with its id:\n\n```\n{\n  \"f\": \"a738cbe0-4dac-4389-aa20-1e6a86c166a9\",\n  \"m\": {\n    \"t\": \"r_c_j\",\n    \"d\": {\n      \"cId\": \"6211a260-d86e-4db9-be00-bd2eba1c2794\",   // clientId\n      \"ht\": \"3dof\"                                     // headsetType\n    }\n  }\n}\n```\n\nIf the server is full, overloaded or in the process of scaling, the server sends a `BUSY_TRY_AGAIN` (`b_t_a`) message, in which case the client should just try to connect again:\n\n```\n{\n  \"f\": \"d02922d2-6db0-4ad1-93e1-0d778250461e\",\n  \"m\": {\n    \"t\": \"b_t_a\",\n    \"d\": {}\n  }\n}\n```\n\n### Leaving a room\n\nClients can disconnect by sending a `ROOM_EXIT` (`r_e`) message:\n\n```\n{\n  \"t\": \"e_r\"\n}\n```\n\nin which case the server will send a `ROOM_EXIT_SUCCESS` (`r_e_s`) message and close the connection:\n\n```\n{\n  \"f\": \"d1e313ee-9eac-47e5-9b9f-5daee88a8057\",\n  \"m\": {\n    \"t\": \"r_e_s\",\n    \"d\": {\n      \"rn\": \"batr\"\t// room name\n    }\n  }\n}\n```\n\nThe client can also just close the websocket connection. 😏\n\nWhen a client disconnects, its positional data is removed from the room state and any remaining clients get a `ROOM_CLIENT_EXIT` (`r_c_e`) message telling them it's left:\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"r_c_e\",\n    \"d\": {\n      \"cId\": \"56252c73-2801-4fff-86f9-d214bdd52b94\"\n    }\n  }\n}\n\n```\n\n### In-room actions\n\nOnce connected to a room, clients can send positional info, and interact with spheres in the room. All interactions are carried out using the following messages.\n\n#### `UPDATE_CLIENT_COORDS` (`u_c_c`)\n\nThis is the most frequent message clients send, notifying a change in client position:\n\n```\n{\n  \"t\": \"u_c_c\",\n  \"d\": {\n      \"r\": {\t// right\n        \"r\": {\t\t// rotation\n          \"x\": 9.234324,\n          \"y\": 55.232423,\n          \"z\": 7.234234\n        },\n        \"p\": {\t// position\n          \"x\": 19.54384,\n          \"y\": 25.3323,\n          \"z\": 9.81832\n        }\n      },\n      \"l\": {\t// left\n        \"r\": {\n          \"x\": 9.234324,\n          \"y\": 55.232423,\n          \"z\": 7.234234\n        },\n        \"p\": {\n          \"x\": 19.54384,\n          \"y\": 25.3323,\n          \"z\": 9.81832\n        }\n      },\n      \"h\": {\t// head\n        \"r\": {\n          \"x\": 9.234324,\n          \"y\": 55.232423,\n          \"z\": 7.234234\n        },\n        \"p\": {\n          \"z\": 79.618,\n          \"y\": 0,\n          \"x\": 19.888\n        }\n      }\n    }\n}\n\n```\n\nThese are broadcast to other clients in the same room as `ROOM_CLIENT_COORDS_UPDATED` (`r_c_c_u`), providing rotation (`r`) and position (`p`) coordinates for each of head (`h`), left hand (`l`) and right hand (`r`):\n\n```\n{\n  \"f\": \"41bd2e10-0f67-4b14-acee-84eeed1764b7\",\n  \"m\": {\n    \"t\": \"r_c_c_u\",\n    \"d\": {\n      \"r\": {\n        \"r\": {\n          \"x\": 9.234324,\n          \"y\": 55.232423,\n          \"z\": 7.234234\n        },\n        \"p\": {\n          \"x\": 19.54384,\n          \"y\": 25.3323,\n          \"z\": 9.81832\n        }\n      },\n      \"l\": {\n        \"r\": {\n          \"x\": 9.234324,\n          \"y\": 55.232423,\n          \"z\": 7.234234\n        },\n        \"p\": {\n          \"x\": 19.54384,\n          \"y\": 25.3323,\n          \"z\": 9.81832\n        }\n      },\n      \"h\": {\n        \"r\": {\n          \"x\": 9.234324,\n          \"y\": 55.232423,\n          \"z\": 7.234234\n        },\n        \"p\": {\n          \"z\": 79.618,\n          \"y\": 0,\n          \"x\": 19.888\n        }\n      }\n    }\n  }\n}\n```\n\nClients can optionally include sphere positions in the `UPDATE_CLIENT_COORDS` messages, for any spheres they currently hold (see below). To do so, they add an extra `spheres` object at the same level as the `head`, `left` and `right`:\n\n```\n{\n  \"f\": \"41bd2e10-0f67-4b14-acee-84eeed1764b7\",\n  \"m\": {\n    \"t\": \"r_c_c_u\",\n    \"d\": {\n      \"s\": [\t// spheres\n        {\n          \"spId\": \"df0a769c-653d-44ec-9f17-e5a9d2c0f167\",\n          \"p\": {\n            \"x\": 156,\n            \"y\": 22,\n            \"z\": 10\n          }\n        },\n        {\n          \"spId\": \"69143b9a-1342-4aec-a7da-1daac29cee3d\",\n          \"p\": {\n            \"x\": 2,\n            \"y\": 3,\n            \"z\": 4\n          }\n        }\n      ],\n      \"r\": { [...] },  // right\n      \"l\": { [...] },  // left\n      \"h\": { [...] }  // head\n    }\n  }\n}\n```\n\nClients can interact with spheres in the room by sending messages as follows.\n\n#### `CREATE_SPHERE_OF_TONE_AT_POSITION` (`c_s_o_t_a_p`)\n\n```\n{\n  \"t\": \"c_s_o_t_a_p\",\n  \"d\": {\n    \"t\": 0,\t\t// tone\n    \"p\": {\t\t// position\n      \"x\": 1,\n      \"y\": 1,\n      \"z\": 3\n    }\n  }\n}\n```\n\nOn successful creation, the server sends a `CREATE_SPHERE_SUCESS` (`c_s_s`) message:\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\t// from <server-id>\n  \"m\": {\n    \"t\": \"c_s_s\",\n    \"d\": {\n      \"spId\": \"cad0a7d4-a055-49b2-a3f8-4e08d636dbce\"\n    }\n  }\n}\n```\n\n#### `GRAB_SPHERE` (`g_s`)\n\n```\n{\n  \"t\": \"g_s\",\n  \"d\": {\n    \"spId\": \"090253c9-1c68-44c2-8aac-dba707c6aef7\"\n  }\n}\n```\n\nOn successful grab, server sends `GRAB_SPHERE_SUCCESS` (`g_s_s`):\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"g_s_s\",\n    \"d\": {\n      \"spId\": \"31654801-9e61-4a5e-bdd6-a29e8ecd1001\"\n    }\n  }\n}\n```\n\nBefore accepting a grab, the server checks its state to see whether the sphere exists and whether it's already held by any other clients. Any problems are communicated back to the client. Possible errors are:\n\n`NON_EXISTENT_SPHERE` (`n_e_s`)\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"n_e_s\",\n    \"d\": {\n      \"spId\": \"66b93b19-03d6-4220-b44f-5b33d5d59269\"    // sphereId\n    }\n  }\n}\n```\n\n`CLIENT_HOLDING_SPHERE` (`c_h_s`):\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"c_h_s\",\n    \"d\": {\n      \"spId\": \"29312c56-b1a5-4530-8c7b-2c968e158683\",\n      \"hId\": \"1eec65a7-2c9a-4fe4-a998-55d3f1754f21\"   // holderId - same as client in this case\n    }\n  }\n}\n```\n\n`SPHERE_ALREADY_HELD` (`s_a_h`) by another client:\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"s_a_h\",\n    \"d\": {\n      \"spId\": \"66b93b19-03d6-4220-b44f-5b33d5d59269\",\n      \"hId\": \"055fa916-5e99-44dd-9e23-2f9323bb6f4c\"\n    }\n  }\n}\n```\n\n`CLIENT_HOLDING_MAX_SPHERES` (`c_h_m_s`) - a client can only hold one sphere in each hand:\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"c_h_m_s\",\n    \"d\": {}\n  }\n}\n```\n\nA successful grab creates a \"hold\" on the sphere, stopping other clients from acting on the sphere, and lasting until either the client sends a `RELEASE_SPHERE` (`r_s`) message, or is inactive (sends no messages relating to the held sphere) for a period defined in `roomConstants.SPHERE_INFO.SPHERE_HOLD_TIMEOUT`, in the file `src/rooms/room-data-constants.js`, currently set to 10 seconds. If the hold times out, the server sends a `SPHERE_HOLD_TIMEOUT` (`s_h_t`) to the client:\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"s_h_t\",\n    \"d\": {\n      \"spId\": \"922fbed6-5c57-4c14-bf0f-080abf5fa285\"\n    }\n  }\n}\n```\n\nand a `ROOM_SPHERE_RELEASED` (`r_s_r`) message to any other clients in the room:\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"r_s_r\",\n    \"d\": {\n      \"spId\": \"922fbed6-5c57-4c14-bf0f-080abf5fa285\",\n      \"cId\": \"1fe992a5-05d9-4791-8482-c598f31dbe22\"\n    }\n  }\n}\n```\n\n\n#### `RELEASE_SPHERE` (`r_s`)\n\n```\n{\n  \"t\": \"r_s\",\n  \"d\": {\n    \"spId\": \"6f0e19bd-2451-43a0-be9d-b6e208e65ee2\"\n  }\n}\n```\n\nOn receiving a `RELEASE_SPHERE` message, the server checks that the client is holding it, and if so sends a `RELEASE_SPHERE_SUCCESS` (`r_sp_s`) message to the client:\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"r_sp_s\",\n    \"d\": {\n      \"spId\": \"a62cabcd-e7d9-4687-beac-0c6c7fb9b953\"\n    }\n  }\n}\n```\n\nand a `ROOM_SPHERE_RELEASED` (`r_s_r`) message to any other clients in the room:\n\n```\n{\n  \"f\": \"81270b00-2dde-4c33-ab4d-6cfc76b46c0c\",\n  \"m\": {\n    \"t\": \"r_s_r\",\n    \"d\": {\n      \"spId\": \"a62cabcd-e7d9-4687-beac-0c6c7fb9b953\",\n      \"cId\": \"d8f59526-60b6-47ee-ab86-f39f441b8aaa\"    // released by this client\n    }\n  }\n}\n```\n\nPossible errors are `NON_EXISTENT_SPHERE` (as in `GRAB_SPHERE` above) and `RELEASE_SPHERE_INVALID` (`r_sp_i`):\n\n```\n{\n  \"f\": \"d1e313ee-9eac-47e5-9b9f-5daee88a8057\",\n  \"m\": {\n    \"t\": \"r_sp_i\",\n    \"d\": {\n      \"spId\": \"9c674bab-e2a6-4588-beff-2342d10a97b0\"\n    }\n  }\n}\n```\n\n\n#### `DELETE_SPHERE` (`d_s`)\n\n```\n{\n  \"t\": \"d_s\",\n  \"d\": {\n    \"spId\": \"cfa16485-51e5-4db0-b54a-1cab6b6f003f\"\n  }\n}\n```\n\nOn successful delete the server sends `DELETE_SPHERE_SUCCESS` (`d_s_s`):\n\n```\n{\n  \"f\": \"d1e313ee-9eac-47e5-9b9f-5daee88a8057\",\n  \"m\": {\n    \"t\": \"d_s_s\",\n    \"d\": {\n      \"spId\": \"cfa16485-51e5-4db0-b54a-1cab6b6f003f\"\n    }\n  }\n}\n```\n\nand notifies any other clients with a `ROOM_SPHERE_DELETED` (`r_s_d`) message:\n\n```\n{\n  \"f\": \"5e44fd4e-54cb-42d7-ac41-f381a3844fee\",\n  \"m\": {\n    \"t\": \"r_s_d\",\n    \"d\": {\n      \"spId\": \"9de12a8d-96a4-42e8-842e-4230db33b306\",\n      \"cId\": \"5e44fd4e-54cb-42d7-ac41-f381a3844fee\"\n    }\n  }\n}\n```\n\nPossible errors include `NON_EXISTENT_SPHERE` (as above), `DELETE_SPHERE_DENIED` (`d_s_d`) if another client is holding the sphere:\n\n```\n{\n  \"f\": \"d1e313ee-9eac-47e5-9b9f-5daee88a8057\",\n  \"m\": {\n    \"t\": \"d_s_d\",\n    \"d\": {\n      \"spId\": \"9de12a8d-96a4-42e8-842e-4230db33b306\",\n      \"hId\": \"9847a6ac-ae7c-43a8-8599-83b3079c9f1a\"\n    }\n  }\n}\n```\n\n#### `STRIKE_SPHERE` (`s_s`)\n\n```\n{\n  \"t\": \"s_s\",\n  \"d\": {\n    \"spId\": \"aabb5ecd-1662-4db5-91f9-6bd1d8f2b0f9\",\n    \"v\": 0.5\n  }\n}\n```\nSphere strikes are broadcast to other clients in the room via a `ROOM_SPHERE_STRUCK` (`r_s_s`) message: \n\n```\n{\n  \"f\": \"1a3823f3-79e6-4cd1-9e33-2044d16da300\",\n  \"m\": {\n    \"t\": \"r_s_s\",\n    \"d\": {\n      \"spId\": \"aabb5ecd-1662-4db5-91f9-6bd1d8f2b0f9\",\n      \"v\": 0.5,\n      \"cId\": \"1a3823f3-79e6-4cd1-9e33-2044d16da300\"\n    }\n  }\n}\n```\n\nStrikes sent to non-existent spheres are silently dropped.\n\n\n#### `SET_SPHERE_TONE` (`s_s_t`)\n\n\n```\n{\n  \"t\": \"s_s_t\",\n  \"d\": {\n    \"spId\": \"99c2989c-beba-4c3f-acb9-6f5fe5e4ec33\",\n    \"t\": 10\t// tone\n  }\n}\n```\n\nOn successful set, the server sends a `SET_SPHERE_TONE_SUCCESS` (`s_s_t_s`) message:\n\n```\n{\n  \"f\": \"d1e313ee-9eac-47e5-9b9f-5daee88a8057\",\n  \"m\": {\n    \"t\": \"s_s_t_s\",\n    \"d\": {\n      \"spId\": \"99c2989c-beba-4c3f-acb9-6f5fe5e4ec33\"\n    }\n  }\n}\n```\n\nand notifies other clients of the change in tone with a `ROOM_SPHERE_TONE_SET` (`r_s_t_s`) message:\n\n```\n{\n  \"f\": \"d1e313ee-9eac-47e5-9b9f-5daee88a8057\",\n  \"m\": {\n    \"t\": \"r_s_t_s\",\n    \"d\": {\n      \"spId\": \"cd3aa6cf-f15c-4d7a-be6e-d862912fbd62\",\n      \"t\": 10,  // tone\n      \"cId\": \"13eb8894-330a-4e37-8ce3-645edf3c6773\"\n    }\n  }\n}\n```\n\nClients don't need to hold a sphere to set its tone, but if it's currently held by another client, the server will send a `SET_SPHERE_TONE_DENIED` (`s_s_t_d`) message:\n\n```\n{\n  \"f\": \"d1e313ee-9eac-47e5-9b9f-5daee88a8057\",\n  \"m\": {\n    \"t\": \"s_s_t_d\",\n    \"d\": {\n      \"spId\": \"99c2989c-beba-4c3f-acb9-6f5fe5e4ec33\",\n      \"hId\": \"9e8e4e40-a523-4345-8e21-b274177fe073\"\n    }\n  }\n}\n```\n\nAs above, the server will send a `NON_EXISTENT_SPHERE` message if the specified sphere doesn't exist in the room.\n\n#### `SET_SPHERE_CONNECTIONS` (`s_s_c`)\n\n```\n{\n  \"t\": \"s_s_c\",\n  \"d\": {\n    \"spId\": \"973c742d-866f-4b25-9ae6-8830e7f13e59\",\n    \"c\": [\t// connections\n      \"221c3e00-c0e9-4bd9-bdb3-187c5dcf5499\",\n      \"03d6c324-73e0-4804-9b84-9b203a303a38\"\n    ]\n  }\n}\n```\n\nAs in `SET_SPHERE_TONE`, clients don't need to hold a sphere to set its connections, but if it's currently held by another client, the server will send a `SET_SPHERE_CONNECTIONS_DENIED` (`s_s_c_d`) message:\n\n```\n{\n  \"f\": \"d1e313ee-9eac-47e5-9b9f-5daee88a8057\",\n  \"m\": {\n    \"t\": \"s_s_c_d\",\n    \"d\": {\n      \"spId\": \"973c742d-866f-4b25-9ae6-8830e7f13e59\",\n      \"hId\": \"7bf0b7a2-ee3e-4b76-9d68-675aa4e97f2a\"\n    }\n  }\n}\n```\n\nAs above, the server will send a `NON_EXISTENT_SPHERE` message if the specified sphere doesn't exist in the room.\n\n\n"
  },
  {
    "path": "backend/package.json",
    "content": "{\n  \"name\": \"webvr-musicalforest-backend\",\n  \"version\": \"1.0.0\",\n  \"description\": \"node.js websocket app server for forest.webvrexperiments.com\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"ENVIRONMENT_NAME=$(hostname) LOCAL_IP_ADDRESS=$(/sbin/ifconfig en0|grep -w inet|awk '{print $2}') PROJECT_ID=$(gcloud config list project --format json | jq -r .core.project) babel-node src/index.js\",\n    \"build\": \"babel src -d dist\",\n    \"clean\": \"rm -rf dist\",\n    \"serve\": \"NODE_ENV=production ENVIRONMENT_NAME=$(hostname) LOCAL_IP_ADDRESS=$(/sbin/ifconfig en0|grep -w inet|awk '{print $2}') PROJECT_ID=$(gcloud config list project --format json | jq -r .core.project) node dist/index.js\",\n    \"emulators\": \"parallelshell 'gcloud beta emulators datastore start --no-store-on-disk' 'gcloud beta emulators pubsub start'\",\n    \"start_emulated\": \"$(gcloud beta emulators datastore env-init) && $(gcloud beta emulators pubsub env-init) && NODE_ENV=production npm start\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/googlecreativelab/webvr-musicalforest/\"\n  },\n  \"author\": \"Google Creative Lab\",\n  \"license\": \"Apache-2.0\",\n  \"devDependencies\": {\n    \"babel-cli\": \"6.18.0\",\n    \"babel-polyfill\": \"6.16.0\",\n    \"babel-preset-es2015\": \"6.18.0\",\n    \"babel-preset-stage-2\": \"6.18.0\",\n    \"mocha\": \"3.2.0\",\n    \"nodemon\": \"1.11.0\",\n    \"parallelshell\": \"^2.0.0\"\n  },\n  \"dependencies\": {\n    \"@google-cloud/datastore\": \"0.7.0\",\n    \"@google-cloud/logging\": \"0.7.0\",\n    \"@google-cloud/pubsub\": \"0.8.0\",\n    \"@google-cloud/storage\": \"0.7.0\",\n    \"ajv\": \"4.10.0\",\n    \"arrify\": \"1.0.1\",\n    \"dateformat\": \"2.0.0\",\n    \"express\": \"4.14.0\",\n    \"fast-ratelimit\": \"2.0.6\",\n    \"grpc\": \"1.0.1\",\n    \"hashring\": \"3.2.0\",\n    \"hashtable\": \"2.0.1\",\n    \"http-proxy\": \"1.16.2\",\n    \"javascript-state-machine\": \"2.4.0\",\n    \"node-fetch\": \"1.6.3\",\n    \"object-sizeof\": \"1.1.1\",\n    \"redux\": \"3.6.0\",\n    \"redux-actions\": \"1.1.0\",\n    \"redux-saga\": \"0.14.3\",\n    \"tracer\": \"0.8.7\",\n    \"uuid\": \"3.0.1\",\n    \"uws\": \"0.14.1\"\n  }\n}\n"
  },
  {
    "path": "backend/src/config.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs           from 'fs';\nimport uuid         from 'uuid';\nimport dateformat   from 'dateformat';\nimport storage      from '@google-cloud/storage';\n\nimport messageConstants     from './messages/message-constants';\nimport serverConstants      from './server/server-constants';\n\nimport { formatStringAsGcpResourceName } from './utils/string-utils';\n\nconst logError = ( errorMsg ) => {\n    let ts = dateformat( new Date(), LOG_DATE_FORMAT );\n\tconsole.error( `[${SHORT_SERVER_ID}|${ts}] => `, errorMsg );\n};\n\n\n/*******************************************************************************\n* REQUIRED ENVIRONMENT VARIABLES\n*******************************************************************************/\n\n// nothing will work without a GCP PROJECT_ID\nif( !process.env.PROJECT_ID ) {\n    logError( `PROJECT_ID not specified, exiting.` );\n\tprocess.exit( serverConstants.ERROR_TYPES.BAD_OS_EXIT_ERROR_CODE );\n}\n\nconst PROJECT_ID = process.env.PROJECT_ID;\n\n\n// nothing will work without a LOCAL_IP_ADDRESS\nif( !process.env.LOCAL_IP_ADDRESS ) {\n    logError( `LOCAL_IP_ADDRESS not specified, exiting.` );\n\tprocess.exit( serverConstants.ERROR_TYPES.BAD_OS_EXIT_ERROR_CODE );\n}\n\nconst LOCAL_IP_ADDRESS = process.env.LOCAL_IP_ADDRESS;\n\n// nothing will work without an ENVIRONMENT_NAME\n\nif( !process.env.ENVIRONMENT_NAME ) {\n    logError( `ENVIRONMENT_NAME not specified, exiting.` );\n\tprocess.exit( serverConstants.ERROR_TYPES.BAD_OS_EXIT_ERROR_CODE );\n};\n\nconst ENVIRONMENT_NAME = process.env.ENVIRONMENT_NAME;\n\n\n/*******************************************************************************\n* LOG FORMAT INFO\n*******************************************************************************/\n\n// generate a server id\nconst SERVER_ID             = uuid();\nconst SHORT_SERVER_ID       = SERVER_ID.split( '-' ).slice(0, 2).join( '-' );\nconst LOG_DATE_FORMAT       = \"yyyymmdd|HH:MM:ss.l\";\n\n/*******************************************************************************\n* PUBSUB SYNC CHANNEL INFO\n*******************************************************************************/\n\n// setup PubSub sync channel topic name, distinct per execution group\n// e.g. 'gce', 'gke', 'mymac.config'\nconst syncTopicNameComponents = [\n    serverConstants.SYNC_INFO.SYNC_TOPIC_NAME,\n    '-',\n    ENVIRONMENT_NAME\n];\n\nconst SYNC_TOPIC_NAME = formatStringAsGcpResourceName(\n    syncTopicNameComponents.join( '' )\n);\n\n\n// only log to Stackdriver explicitly if requested\nconst LOG_TO_CLOUD          = process.env.LOG_TO_CLOUD === 'true' ? true : false;\n\n/*******************************************************************************\n* NETWORK IP/PORT INFO\n*******************************************************************************/\n\n// set up some defaults\nconst WS_SERVER_PORT        = process.env.WS_SERVER_PORT        ? Number( process.env.WS_SERVER_PORT ) : 8100;\nconst WS_BALANCER_PORT      = process.env.WS_BALANCER_PORT      ? Number( process.env.WS_BALANCER_PORT ) : 9100;\nconst MAX_CLIENTS_PER_ROOM  = process.env.MAX_CLIENTS_PER_ROOM  ? Number( process.env.MAX_CLIENTS_PER_ROOM ) : 10;\n\n// construct balancer identifier\nconst LOCAL_IP_PORT_STRING  = `${LOCAL_IP_ADDRESS}:${WS_SERVER_PORT}`;\n\n\n/*******************************************************************************\n* SSL INFO FOR HOSTING ON GKE\n*******************************************************************************/\n\n// GKE deploys provide USE_SSL=true in the environment\nconst USE_SSL = process.env.USE_SSL === \"true\" ? true : false;\n\n// tell the app whether to serve SSL\nlet SSL_INFO = {\n    useSsl: USE_SSL\n};\n\n// if so, set up the certificate info\nif( USE_SSL === true ) {\n\n    // make sure there's a specified certificate name\n    if( !process.env.SSL_CERT_HOST_NAME ) {\n        logError( `USE_SSL=true requires a specified SSL_CERT_HOST_NAME` );\n        process.exit( serverConstants.ERROR_TYPES.BAD_OS_EXIT_ERROR_CODE );\n    }\n\n    // prepare for export, set up GCS bucket/file info\n    SSL_INFO.sslCertHostName        = process.env.SSL_CERT_HOST_NAME;\n    SSL_INFO.sslStorageBucketName   = `${PROJECT_ID}-ssl`;\n    SSL_INFO.privKeyFileName        = `${process.env.SSL_CERT_HOST_NAME}/privkey.pem`;\n    SSL_INFO.fullChainFileName      = `${process.env.SSL_CERT_HOST_NAME}/fullchain.pem`;\n\n}\n\n/*******************************************************************************\n* ROOM CHOICE THRESHOLD CONFIGS\n*******************************************************************************/\n\n// provide shortcuts for headset type constants\nconst HT_3DOF   = messageConstants.HEADSET_TYPES.HEADSET_TYPE_3DOF;\nconst HT_6DOF   = messageConstants.HEADSET_TYPES.HEADSET_TYPE_6DOF;\nconst HT_VIEWER = messageConstants.HEADSET_TYPES.HEADSET_TYPE_VIEWER;\n\n\n// set up headset threshold rules\nconst HEADSET_RULES = {\n    [ HT_3DOF ]: {\n        threshold:  3\n    },\n    [ HT_6DOF ]: {\n        threshold:  3\n    },\n    [ HT_VIEWER ]: {\n        threshold:  10\n    }\n};\n\n\n/*******************************************************************************\n* CHOICE OF WHETHER TO TIMEOUT SPHERE HOLDS AUTOMATICALLY\n*******************************************************************************/\n\nconst TIMEOUT_SPHERE_HOLDS = process.env.TIMEOUT_SPHERE_HOLDS === 'false' ? false : true;;\n\n/*******************************************************************************\n* CHOICE OF WHETHER TO DROP VIEWER MESSAGES\n*******************************************************************************/\n\nconst DROP_VIEWER_MESSAGES_IN_PRODUCTION = process.env.DROP_VIEWER_MESSAGES_IN_PRODUCTION === 'true' ? true : false;\n\n/*******************************************************************************\n* CHOICE OF WHETHER TO RATE LIMIT CLIENT/SPHERE POSITION UPDATES\n*******************************************************************************/\n\nconst PER_CLIENT_RATE_LIMIT         = process.env.PER_CLIENT_RATE_LIMIT === 'false' ? false : true;\nconst PER_MESSAGE_TYPE_RATE_LIMIT   = process.env.PER_MESSAGE_TYPE_RATE_LIMIT === 'false' ? false : true;\n\nconst RATE_LIMIT_INFO   = {\n    perClientRateLimit:         PER_CLIENT_RATE_LIMIT,          // switch per-clientrate limiting on or off\n    perMessageTypeRateLimit:    PER_MESSAGE_TYPE_RATE_LIMIT,    // switch rate limiting on or off\n\n    perTypeMsgThreshold:    1,      // 1 message of each type per client\n    perTypeMsgTtl:          0.2,    // per 200ms\n\n    perClientMsgThreshold:  20,     // 20 message of any type per client\n    perClientMsgTtl:        1       // per 1s\n};\n\n/*******************************************************************************\n* NAME OF PRODUCTION ENVIRONMENT GCP PROJECT\n*******************************************************************************/\n\nconst PRODUCTION_ENVIRONMENT_PROJECT_ID         = '<your-production-project-id>';\nconst PRODUCTION_ENVIRONMENT_REQUIRED_ORIGIN    = '<your-production-frontend-hostname>';\n\n/*******************************************************************************\n* EXPORT CONFIG\n*******************************************************************************/\n\nexport default {\n    environmentName:                        ENVIRONMENT_NAME,\n    syncTopicName:                          SYNC_TOPIC_NAME,\n    logDateFormat:                          LOG_DATE_FORMAT,\n    logToCloud:                             LOG_TO_CLOUD,\n    localIpAddress:                         LOCAL_IP_ADDRESS,\n    localIpPortString:                      LOCAL_IP_PORT_STRING,\n    serverPort:                             WS_SERVER_PORT,\n    balancerPort:                           WS_BALANCER_PORT,\n    projectId:                              PROJECT_ID,\n    dropViewerMessagesInProduction:         DROP_VIEWER_MESSAGES_IN_PRODUCTION,\n    productionEnvironmentProjectId:         PRODUCTION_ENVIRONMENT_PROJECT_ID,\n    productionEnvironmentRequiredOrigin:    PRODUCTION_ENVIRONMENT_REQUIRED_ORIGIN,\n    maxClientsPerRoom:                      MAX_CLIENTS_PER_ROOM,\n    clientRoomJoinDeadlineInMs:             5000,\n    roomHeartbeatDelayInMs:                 5000,\n    serverId:                               SERVER_ID,\n    shortServerId:                          SHORT_SERVER_ID,\n    sslInfo:                                SSL_INFO,\n    headsetRules:                           HEADSET_RULES,\n    rateLimitInfo:                          RATE_LIMIT_INFO,\n    timeoutSphereHolds:                     TIMEOUT_SPHERE_HOLDS\n};\n"
  },
  {
    "path": "backend/src/datastore/datastore-constants.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst DATASTORE = {\n    // entity types\n    DS_ENTITY_KEY_SERVER_SUBSCRIPTION_INFO: 'ServerSubscriptionInfo',\n    DS_ENTITY_KEY_PER_ENVIRONMENT_RATE_LIMIT_INFO: 'PerEnvironmentRateLimitInfo'\n};\n\nexport default DATASTORE;\n"
  },
  {
    "path": "backend/src/datastore/index.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport datastore                from '@google-cloud/datastore';\nimport config                   from '../config';\n\nimport { logger }               from '../logger';\n\nimport datastoreConstants       from './datastore-constants';\n\n// create a module-wrapped datastore client object at startup\nlet dsClient = ( ( conf ) => {\n\n    if( !dsClient ) {\n        if( process.env.DATASTORE_EMULATOR_HOST ) {\n            logger.info( `using Datastore emulator running at ${process.env.DATASTORE_EMULATOR_HOST}` );\n        }\n        else {\n            logger.info( `using live Datastore on GCP` );\n        }\n\n        dsClient = datastore( conf );\n    }\n\n    return dsClient;\n\n})( config );\n\nconst runQuery = function* ( query ) {\n    return yield dsClient.runQuery( query );\n};\n\nconst save = function* ( entities ) {\n    return yield dsClient.save( entities );\n};\n\nconst key = function ( keyComponents ) {\n    return dsClient.key( keyComponents );\n};\n\n// export functions to the default namespace, to make a 'datastore' import for other modules\nexport default {\n    runQuery,\n    save,\n    key\n};\n\n// export constants individually, to make a { datastoreConstants } import for other modules\nexport {\n    datastoreConstants,\n    dsClient\n}\n"
  },
  {
    "path": "backend/src/index.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport \"babel-polyfill\";\n\nimport config                   from './config';\n\nimport { dispatchStoreAction }  from './store';\nimport { logger }               from './logger';\nimport { serverActions }        from './server';\n\n// create a server setup request action\nlet setupAction = serverActions.startServerSetupRequestAction();\n\n/*\n * dispatch the setup request to the store.\n * all the action now happens via setupServer() in\n * server/server-sagas.js\n */\ndispatchStoreAction( setupAction );\n"
  },
  {
    "path": "backend/src/logger/cloud-logger.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Logging from '@google-cloud/logging';\n\nimport config from '../config';\n\nimport { formatStringAsGcpResourceName } from '../utils/string-utils';\n\n// create a client\nconst loggingClient = Logging( config );\n\n// the name of the log to write to\nconst logName = formatStringAsGcpResourceName(\n    `${config.environmentName}-cloud-app-log`\n);\n\n// choose a log to write to\nconst selectedLog = loggingClient.log( logName );\n\n// set up resource metadata\nconst metadata = {\n    resource: {\n        type: 'project',\n        labels: {\n            project_id: config.projectId\n        }\n    }\n};\n\n// log function wrapper\nconst writeCloudLogEntry = ( tracerData ) => {\n\n    const entry = selectedLog.entry( metadata, tracerData.output );\n\n    selectedLog.write(entry).catch(\n        ( error ) => {\n            console.error( `error logging message: ${error.message}` );\n        }\n    );\n\n};\n\nexport {\n    writeCloudLogEntry\n};\n"
  },
  {
    "path": "backend/src/logger/index.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport tracer from 'tracer';\n\nimport config from '../config';\n\nimport { writeCloudLogEntry } from './cloud-logger';\n\nlet selectedConsole = config.logToCloud ? tracer.console : tracer.colorConsole;\n\nlet consoleOptions = {\n    format:     \"[{{shortServerId}}|{{timestamp}}] {{sigil}} {{message}} [{{file}}:{{line}}]\",\n    dateformat: config.logDateFormat,\n    preprocess: (data) => {\n        data.shortServerId = config.shortServerId;\n        data.localIpAddress = config.localIpAddress;\n        data.sigil = {\n            error:  `=>`,\n            warn:   `**`,\n            info:   `->`,\n            debug:  `||`,\n            trace:  `##`,\n            log:    ``\n        }[ data.title ];\n    }\n};\n\n// only do info() and above in production\nif( config.projectId === config.productionEnvironmentProjectId ) {\n    consoleOptions.level    = 'info';\n    consoleOptions.format   = \"[{{shortServerId}}|{{localIpAddress}}|{{timestamp}}] {{sigil}} {{message}} [{{file}}:{{line}}]\";\n}\n\n// write log via Stackdriver API?\nif( config.logToCloud ) {\n    consoleOptions.transport = writeCloudLogEntry;\n}\n\nlet l = selectedConsole( consoleOptions );\n\nlet logger = {\n    error:  l.error,\n    warn:   l.warn,\n    info:   l.info,\n    debug:  l.debug,\n    trace:  l.trace,\n    log:    l.log\n};\n\nexport {\n    logger\n};\n"
  },
  {
    "path": "backend/src/messages/index.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport messageActions   from './message-actions';\nimport messageConstants from './message-constants';\nimport messageHandler   from './message-handler';\nimport messageSagas     from './message-sagas';\nimport messageValidator from './message-validator';\n\nexport {\n    messageActions,\n    messageConstants,\n    messageHandler,\n    messageSagas,\n    messageValidator\n};\n"
  },
  {
    "path": "backend/src/messages/message-actions.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport messageConstants     from './message-constants';\nimport { messageSchema }    from './message-schema';\n\n/*******************************************************************************\n*\n* reference of data structure from JSON schema\n*\n*   {\n*      definitions: {\n*        ...,\n*        room_client_position_update:\n*         { type: 'object',\n*           properties: [Object],\n*           required: [Object] }\n*     },\n*\n*     properties: {\n*        ...,\n*        room_client_position_update:\n*         { '$ref': '#/definitions/room_client_position_update' }\n*     }\n*   }\n*\n*   room_client_position_update:  { type: { type: 'string', minLength: 29, maxLength: 29 },\n*     data:\n*      { type: 'object',\n*        properties:\n*         { head: [Object],\n*           left: [Object],\n*           right: [Object],\n*           userdata: [Object] },\n*        required: [ 'head', 'left', 'right' ] } }\n*\n********************************************************************************/\n\n/*******************************************************************************\n* see https://github.com/acdlite/redux-actions\n*\n* redux-actions generates action request objects for use as triggers in redux.\n*\n* example:\n* {\n*   type: 'INCREMENT',\n*   payload: 42,\n*   otherVar: true\n* }\n*\n*******************************************************************************/\n\n// generates 'redux-actions' action functions for all incoming message types in schema\nconst messageRequestActions = Object.keys( messageSchema.properties ).reduce(\n\n    // for each key in messageSchema.properties\n    ( acc, type ) => {\n\n        // create a function which ...\n        let fn = function() {\n            // combines the provided type with fn args to generate an action request object\n            return Object.assign( { type }, arguments[ 0 ] );\n        };\n\n        // key the function by the incoming message type name, to export them all\n        acc[ type ] = fn;\n        return acc;\n    },\n\n    {} // empty starting accumulator\n);\n\n// export the generated functions for use as \"import messageActions from 'message-actions'\"\nexport default messageRequestActions;\n"
  },
  {
    "path": "backend/src/messages/message-constants.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst sphereIdLabel     = 'spId';\nconst clientIdLabel     = 'cId';\nconst holderIdLabel     = 'hId';\nconst serverIdLabel     = 'srId';\nconst toneLabel         = 't';\nconst positionLabel     = 'p';\nconst rotationLabel     = 'r';\nconst headLabel         = 'h';\nconst leftLabel         = 'l';\nconst rightLabel        = 'r';\nconst sphereLabel       = 's';\nconst headsetTypeLabel  = 'ht';\nconst velocityLabel     = 'v';\nconst connectionsLabel  = 'c';\nconst meristemLabel     = 'm';\n\nconst fromLabel         = 'f';\nconst typeLabel         = 't';\nconst dataLabel         = 'd';\nconst msgLabel          = 'm';\n\nconst roomNameLabel     = 'rn';\nconst soundbankLabel    = 'sb';\nconst clientsLabel      = 'c';\nconst spheresLabel      = 's';\n\nconst INCOMING_MESSAGE_TYPES = {\n\n    EXIT_ROOM:                          'e_r',\n\n    UPDATE_CLIENT_COORDS:               'u_c_c',    // position msg for individual client\n\n    CREATE_SPHERE_OF_TONE_AT_POSITION:  'c_s_o_t_a_p',\n    GRAB_SPHERE:                        'g_s',\n    RELEASE_SPHERE:                     'r_s',\n    DELETE_SPHERE:                      'd_s',\n    STRIKE_SPHERE:                      's_s',\n    SET_SPHERE_TONE:                    's_s_t',\n    SET_SPHERE_CONNECTIONS:             's_s_c'\n};\n\nconst INCOMING_MESSAGE_COMPONENTS = {\n    ALL_MESSAGES: {\n        FROM:   fromLabel,\n        MSG:    msgLabel,\n        TYPE:   typeLabel,\n        DATA:   dataLabel\n    },\n    REFERENCES: {\n        COORDINATE:     'coordinate',\n        COORDINATE_SET: 'coordinateSet'\n    },\n    REF_COORDINATE: {\n        X:  'x',\n        Y:  'y',\n        Z:  'z'\n    },\n    REF_COORDINATE_SET: {\n        POSITION:   positionLabel,\n        ROTATION:   rotationLabel\n    },\n    UPDATE_CLIENT_COORDS: {\n        HEAD:               headLabel,\n        LEFT:               leftLabel,\n        RIGHT:              rightLabel,\n        SPHERES:            spheresLabel,\n        SPHERE_ID:          sphereIdLabel,\n        SPHERE_POSITION:    positionLabel\n    },\n    CREATE_SPHERE_OF_TONE_AT_POSITION: {\n        TONE:   toneLabel,\n        POSITION:   positionLabel\n    },\n    GRAB_SPHERE: {\n        SPHERE_ID:  sphereIdLabel\n    },\n    RELEASE_SPHERE: {\n        SPHERE_ID: sphereIdLabel\n    },\n    DELETE_SPHERE: {\n        SPHERE_ID: sphereIdLabel\n    },\n    STRIKE_SPHERE: {\n        SPHERE_ID:  sphereIdLabel,\n        VELOCITY:   velocityLabel\n    },\n    SET_SPHERE_TONE: {\n        SPHERE_ID:  sphereIdLabel,\n        TONE:       toneLabel\n    },\n    SET_SPHERE_CONNECTIONS: {\n        SPHERE_ID:      sphereIdLabel,\n        CONNECTIONS:    connectionsLabel\n    }\n};\n\nconst OUTGOING_MESSAGE_TYPES = {\n\n    CONNECTION_INFO:                    'c_i',          // sent to new clients on connect\n    ROOM_STATUS_INFO:                   'r_s_i',        // sent to new clients on joining a room\n    ROOM_EXIT_SUCCESS:                  'r_e_s',        // sent to clients leaving a room\n\n    ROOM_HEARTBEAT:                     'r_h',          // server heartbeat message for a room\n\n    ROOM_CLIENT_JOIN:                   'r_c_j',        // sent to existing clients on client join\n    ROOM_CLIENT_EXIT:                   'r_c_e',        // sent to remaining clients on client exit\n    ROOM_CLIENT_COORDS_UPDATED:         'r_c_c_u',\n\n    ROOM_SPHERE_CREATED:                'r_s_c',\n    ROOM_SPHERE_GRABBED:                'r_s_g',\n    ROOM_SPHERE_POSITION_UPDATED:       'r_s_p_u',\n    ROOM_SPHERE_TONE_SET:               'r_s_t_s',\n    ROOM_SPHERE_CONNECTIONS_SET:        'r_s_c_s',\n    ROOM_SPHERE_RELEASED:               'r_s_r',\n    ROOM_SPHERE_STRUCK:                 'r_s_s',\n    ROOM_SPHERE_DELETED:                'r_s_d',\n\n    CREATE_SPHERE_DENIED:               'c_s_d',\n    CREATE_SPHERE_SUCCESS:              'c_s_s',\n\n    GRAB_SPHERE_DENIED:                 'g_s_d',\n    GRAB_SPHERE_SUCCESS:                'g_s_s',\n\n    RELEASE_SPHERE_DENIED:              'r_sp_d',\n    RELEASE_SPHERE_INVALID:             'r_sp_i',\n    RELEASE_SPHERE_SUCCESS:             'r_sp_s',\n\n    DELETE_SPHERE_DENIED:               'd_s_d',\n    DELETE_SPHERE_INVALID:              'd_s_i',\n    DELETE_SPHERE_SUCCESS:              'd_s_s',\n\n    SET_SPHERE_TONE_DENIED:             's_s_t_d',\n    SET_SPHERE_TONE_INVALID:            's_s_t_i',\n    SET_SPHERE_TONE_SUCCESS:            's_s_t_s',\n\n    SET_SPHERE_CONNECTIONS_DENIED:      's_s_c_d',\n    SET_SPHERE_CONNECTIONS_INVALID:     's_s_c_i',\n    SET_SPHERE_CONNECTIONS_SUCCESS:     's_s_c_s',\n\n    CONNECT_SPHERES_IDENTICAL:          'c_s_id',\n    CONNECT_SPHERES_INVALID:            'c_s_in',\n    CONNECT_SPHERES_MISSING:            'c_s_m'\n};\n\nconst OUTGOING_MESSAGE_COMPONENTS = {\n    ALL_MESSAGES: {\n        DATA:   dataLabel,\n        FROM:   fromLabel,\n        MSG:    msgLabel,\n        TYPE:   typeLabel,\n        CLIENT_ID:  clientIdLabel\n    },\n    CONNECTION_INFO: {\n        CLIENT_ID:  clientIdLabel,\n        SERVER_ID:  serverIdLabel\n    },\n    ROOM_STATUS_INFO: {\n        ROOM_NAME:      roomNameLabel,\n        SOUNDBANK:      soundbankLabel,\n        CLIENTS:        clientsLabel,\n        CLIENT_ID:      clientIdLabel,\n        CLIENT_HEADSET_TYPE:    headsetTypeLabel,\n        SPHERES:        spheresLabel,\n        SPHERE_ID:      sphereIdLabel,\n        TONE:           toneLabel,\n        POSITION:       positionLabel,\n        CONNECTIONS:    connectionsLabel,\n        MERISTEM:       meristemLabel\n    },\n    ROOM_EXIT_SUCCESS: {\n        ROOM_NAME:  roomNameLabel\n    },\n    CREATE_SPHERE_SUCCESS: {\n        SPHERE_ID:  sphereIdLabel\n    },\n    SPHERE_ACTION_DENIED: {\n        SPHERE_ID:  sphereIdLabel,\n        HOLDER_ID:  holderIdLabel,\n    },\n    ROOM_SPHERE_CREATED: {\n        SPHERE_ID:  sphereIdLabel,\n        TONE:       toneLabel,\n        POSITION:   positionLabel,\n        CLIENT_ID:  clientIdLabel\n    },\n    GRAB_SPHERE_SUCCESS: {\n        SPHERE_ID:  sphereIdLabel,\n        CLIENT_ID:  clientIdLabel\n    },\n    RELEASE_SPHERE_SUCCESS: {\n        SPHERE_ID:  sphereIdLabel,\n        CLIENT_ID:  clientIdLabel\n    },\n    SPHERE_HOLD_TIMEOUT: {\n        SPHERE_ID:  sphereIdLabel,\n    },\n    ROOM_SPHERE_RELEASED: {\n        SPHERE_ID:  sphereIdLabel,\n        CLIENT_ID:  clientIdLabel\n    },\n    ROOM_CLIENT_COORDS_UPDATED: {\n        HEAD:   headLabel,\n        LEFT:   leftLabel,\n        RIGHT:  rightLabel,\n        SPHERES: sphereLabel\n    },\n    ROOM_SPHERE_POSITION_UPDATED: {\n        SPHERE_ID:  sphereIdLabel,\n        POSITION:   positionLabel,\n        CLIENT_ID:  clientIdLabel\n    },\n    SET_SPHERE_TONE_SUCCESS: {\n        SPHERE_ID:  sphereIdLabel\n    },\n    ROOM_SPHERE_TONE_SET: {\n        SPHERE_ID:  sphereIdLabel,\n        TONE:       toneLabel,\n        CLIENT_ID:  clientIdLabel\n    },\n    SET_SPHERE_CONNECTIONS_SUCCESS: {\n        SPHERE_ID:  sphereIdLabel\n    },\n    ROOM_SPHERE_CONNECTIONS_SET: {\n        SPHERE_ID:      sphereIdLabel,\n        CONNECTIONS:    connectionsLabel,\n        CLIENT_ID:      clientIdLabel\n    },\n    DELETE_SPHERE_SUCCESS: {\n        SPHERE_ID:  sphereIdLabel,\n        CLIENT_ID:  clientIdLabel\n    },\n    ROOM_SPHERE_DELETED: {\n        SPHERE_ID:  sphereIdLabel,\n        CLIENT_ID:  clientIdLabel\n    },\n    ROOM_SPHERE_STRUCK: {\n        SPHERE_ID:  sphereIdLabel,\n        CLIENT_ID:  clientIdLabel,\n        VELOCITY:   velocityLabel\n    },\n    ROOM_CLIENT_JOIN: {\n        CLIENT_ID:  clientIdLabel,\n        CLIENT_HEADSET_TYPE: headsetTypeLabel\n    },\n    ROOM_CLIENT_EXIT: {\n        CLIENT_ID:  clientIdLabel\n    },\n    ROOM_HEARTBEAT: {\n        COUNT:      'c',\n        SECONDS:    's'\n    }\n};\n\nconst HEADSET_TYPES = {\n    HEADSET_TYPE_3DOF:      '3dof',\n    HEADSET_TYPE_6DOF:      '6dof',\n    HEADSET_TYPE_VIEWER:    'viewer'\n};\n\nconst ERROR_TYPES = {\n    INVALID_URL:                    'i_u',\n    NO_ROOMS_AVAILABLE:             'n_r_a',\n\n    NOT_IN_ROOM:                    'n_i_r',\n    NO_SUCH_ROOM:                   'n_s_r',\n    ROOM_QUEUE_FULL:                'r_q_f',\n    ROOM_FULL:                      'r_f',\n    ROOM_UNAVAILABLE:               'r_u',\n    BUSY_TRY_AGAIN:                 'b_t_a',\n    ROOM_NOT_READY:                 'r_n_r',\n    ROOM_JOIN_TIMEOUT:              'r_j_t',\n    ALREADY_IN_ROOM:                'a_i_r',\n    ALREADY_IN_ROOM_QUEUE:          'a_i_r_q',\n\n    CREATE_SPHERE_UNAVAILABLE:      'c_s_u',\n    NON_EXISTENT_SPHERE:            'n_e_s',\n    SPHERE_ALREADY_HELD:            's_a_h',\n    CLIENT_HOLDING_SPHERE:          'c_h_s',\n    CLIENT_HOLDING_MAX_SPHERES:     'c_h_m_s',\n    TOO_MANY_SPHERE_CONNECTIONS:    't_m_s_c',\n\n    GRAB_SPHERE_ERROR:              'g_s_e',\n    RELEASE_SPHERE_ERROR:           'r_s_e',\n    DELETE_SPHERE_ERROR:            'd_s_e',\n    CONNECT_SPHERE_ERROR:           'c_s_e',\n    SPHERE_HOLD_TIMEOUT:            's_h_t',\n\n    CLIENT_INACTIVITY_TIMEOUT:      'c_i_t',\n\n    SYSTEM_ERROR:                   's_'\n};\n\nexport default {\n    INCOMING_MESSAGE_TYPES,\n    INCOMING_MESSAGE_COMPONENTS,\n    OUTGOING_MESSAGE_TYPES,\n    OUTGOING_MESSAGE_COMPONENTS,\n    HEADSET_TYPES,\n    ERROR_TYPES\n};\n\nexport {\n    HEADSET_TYPES\n};\n"
  },
  {
    "path": "backend/src/messages/message-handler.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n    roomDataActions,\n    roomContentConstants,\n    roomDataConstants,\n    roomStateConstants\n} from '../rooms';\n\nimport { logger }                       from '../logger';\nimport { roomStateActions }             from '../rooms'\nimport { serialize }                    from '../s11n';\nimport { sendWsMessageWithLogger }      from '../utils/websocket-utils';\n\nimport {\n    dispatchStoreAction,\n    getStoreState\n} from '../store';\n\nimport config                           from '../config';\n\nimport messageActions                   from './message-actions';\nimport messageConstants                 from './message-constants';\nimport { perMessageTypeRateLimiter }    from './message-rate-limiter';\nimport messageValidator                 from './message-validator';\n\nconst messageComponents = messageConstants.INCOMING_MESSAGE_COMPONENTS;\n\n/*******************************************************************************\n* HANDLER FOR INCOMING ROOM MESSAGES FROM THIS SERVER'S WEBSOCKET CLIENTS\n*******************************************************************************/\n\n/*\n * this function validates an incoming message against the JSON message schema,\n * and dispatches successfully validated messages to the appropriate handler\n * defined in messageActions, generated from the message types in the schema.\n */\nconst handleWebsocketMessage = ( ws, message ) => {\n\n    // drop empty messages\n    if( typeof message === 'undefined' || message === '' || message === null ) {\n        return;\n    }\n\n    // make sure the client's in a room\n    if( typeof ws.currentRoom === 'undefined' ) {\n        return;\n    }\n\n    // make sure message format is valid, drop any invalid messages\n    let validatedMessage;\n\n    try {\n        validatedMessage = messageValidator.validateMessage( message );\n    }\n    catch( error ) {\n        logger.debug( `error validating incoming message from client ${ws.id}: ${error.message}` );\n        return;\n    }\n\n    // messageActions exports action functions generated from schema, hence keyed the same\n    let validatedMessageType = validatedMessage[ messageComponents.ALL_MESSAGES.TYPE ];\n\n    // if rate limiting's switched off\n    if( config.rateLimitInfo.perMessageTypeRateLimit === false ) {\n        // handle the message straightaway\n        handleValidatedMessageOfTypeForWebsocket( validatedMessage, validatedMessageType, ws );\n        return;\n    }\n\n    // otherwise, rate-limit messages per type, per client\n    const rateLimiterNamespace = `${ws.id}/${validatedMessageType}`;\n\n    perMessageTypeRateLimiter.consume( rateLimiterNamespace )\n        // as long as the client is within limits for this message type\n        .then(\n            () => {\n                // handle the message\n                handleValidatedMessageOfTypeForWebsocket( validatedMessage, validatedMessageType, ws );\n            }\n        )\n        // otherwise\n        .catch(\n            ( error ) => {\n                // silently drop the message\n            }\n        );\n\n};\n\nconst handleValidatedMessageOfTypeForWebsocket = ( validatedMessage, validatedMessageType, ws ) => {\n\n    // add id to msg, dispatch generated messageAction required by message type\n    let attributedMessage = {\n        ws,\n        [ messageComponents.ALL_MESSAGES.FROM ]:       ws.id,\n        [ messageComponents.ALL_MESSAGES.MSG ]:        validatedMessage\n    };\n\n    // tell the message saga which room it's in\n    attributedMessage.roomName = ws.currentRoom;\n\n    // check the room is in the right state to receive messages\n    let roomState   = getStoreState().roomStateReducer.rooms[ ws.currentRoom ];\n\n    // rooms need to be in specific states to accept messages\n    let roomStatesAcceptingMessages = [\n        roomStateConstants.STATES.READY,\n        roomStateConstants.STATES.ROOM_FULL\n    ];\n\n    if( roomStatesAcceptingMessages.indexOf( roomState.status ) < 0 ) {\n\n        let roomNotReadyMessage = {\n            from: config.serverId,\n            msg: {\n                type: messageConstants.ERROR_TYPES.ROOM_NOT_READY\n            }\n        };\n\n        sendWsMessageWithLogger( ws, roomNotReadyMessage, logger );\n\n        return;\n    }\n\n    dispatchStoreAction( messageActions[ validatedMessageType ]( attributedMessage ) );\n\n};\n\nexport default {\n    handleWebsocketMessage\n};\n"
  },
  {
    "path": "backend/src/messages/message-rate-limiter.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FastRateLimit }    from 'fast-ratelimit';\n\nimport config               from '../config';\n\nimport { logger }           from '../logger';\n\n// rate-limits overall messages per client, regardless of type\nlet perClientRateLimiter = new FastRateLimit({\n    threshold:  config.rateLimitInfo.perClientMsgThreshold,\n    ttl:        config.rateLimitInfo.perClientMsgTtl\n});\n\nif( config.rateLimitInfo.perClientRateLimit ) {\n    logger.info(\n        `default overall client message rate limiter allowing ${perClientRateLimiter.__options.threshold}` +\n        `msg/${perClientRateLimiter.__options.ttl_millisec}ms`\n    );\n}\n\n// rate-limits individual message types per-client\nlet perMessageTypeRateLimiter = new FastRateLimit({\n    threshold:  config.rateLimitInfo.perTypeMsgThreshold,\n    ttl:        config.rateLimitInfo.perTypeMsgTtl\n});\n\nif( config.rateLimitInfo.perMessageTypeRateLimit ) {\n    logger.info(\n        `default per-type client message rate limiter allowing ${perMessageTypeRateLimiter.__options.threshold}` +\n        `msg/${perMessageTypeRateLimiter.__options.ttl_millisec}ms`\n    );\n}\n\n// allows dynamic setting of per-client rate limiter threshold\nconst setPerClientRateLimiterOptions = ( options ) => {\n    perClientRateLimiter.__options = options;\n    logger.info(\n        `set overall client message rate limiter to allow ${perClientRateLimiter.__options.threshold}` +\n        `msg/${perClientRateLimiter.__options.ttl_millisec}ms`\n    );\n};\n\n// allows dynamic setting of per-message-type rate limiter threshold\nconst setPerMessageTypeRateLimiterOptions = ( options ) => {\n    perMessageTypeRateLimiter.__options = options;\n    logger.info(\n        `set per-type client message rate limiter to allow ${perMessageTypeRateLimiter.__options.threshold}` +\n        `msg/${perMessageTypeRateLimiter.__options.ttl_millisec}ms`\n    );\n};\n\nexport {\n    perClientRateLimiter,\n    perMessageTypeRateLimiter,\n    setPerClientRateLimiterOptions,\n    setPerMessageTypeRateLimiterOptions\n};\n"
  },
  {
    "path": "backend/src/messages/message-sagas.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport uuid from 'uuid';\n\nimport { delay, takeEvery, takeLatest } from 'redux-saga';\nimport { call, put, fork, select }      from 'redux-saga/effects';\n\nimport { logger }                       from '../logger';\nimport { sphereConstants }          from '../spheres';\n\nimport {\n    roomDataActions,\n    roomNames,\n    roomStateActions,\n    roomStateConstants\n}  from '../rooms';\n\nimport {\n    makeWsReplyMessage,\n    makeWsBroadcastMessage,\n    sendWsMessageWithLogger\n} from '../utils/websocket-utils';\n\nimport config                       from '../config';\nimport { sagaUtils }                from '../utils';\n\nimport messageActions               from './message-actions';\nimport messageConstants             from './message-constants';\n\nconst incomingMsgComponents = messageConstants.INCOMING_MESSAGE_COMPONENTS;\nconst outgoingMsgComponents = messageConstants.OUTGOING_MESSAGE_COMPONENTS;\n\n/*******************************************************************************\n* EXIT ROOM\n*******************************************************************************/\n\nconst exitRoom = function* ( message ) {\n\n    logger.trace( `client ${message.ws.id} is leaving room ${message.ws.currentRoom}` );\n\n    // send a goodbye message to the client\n    let roomExitSuccessMessage = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_EXIT_SUCCESS,\n        { [ outgoingMsgComponents.ROOM_EXIT_SUCCESS.ROOM_NAME ]: message.ws.currentRoom }\n    );\n\n    sendWsMessageWithLogger( message.ws, roomExitSuccessMessage, logger );\n\n    // remove it from the state\n    yield put(\n        roomDataActions.removeLocalClientFromRoomRequestAction(\n            message.ws.id,\n            message.ws.currentRoom\n        )\n    );\n\n    // close the connection\n    message.ws.close();\n};\n\n/*******************************************************************************\n* UPDATE ROOM CLIENT POSITION\n*******************************************************************************/\n\nconst updateClientCoords = function* ( action ) {\n\n    let { msgType, msgData } = getTypeAndDataForMessage( action );\n\n    // check whether the coords include any sphere position update\n    let spherePositionUpdates = msgData[ incomingMsgComponents.UPDATE_CLIENT_COORDS.SPHERES ];\n\n    if( typeof spherePositionUpdates !== 'undefined' ) {\n\n        let roomState = yield select( ( state ) => { return state.roomDataReducer.rooms; } );\n        let spheresHeldByClient = roomState[ action.roomName ].content.clients[ action.ws.id ].spheresHeld;\n        let checkedSpherePositionUpdates = [];\n\n        // spherePositionUpdates will be an array if it exists, as it's been validated\n        const spherePositionUpdateCount = spherePositionUpdates.length;\n\n        for( let i = 0; i < spherePositionUpdateCount; i++ ) {\n\n            let updatedSphere   = spherePositionUpdates[ i ];\n            let updatedSphereId = updatedSphere[ incomingMsgComponents.UPDATE_CLIENT_COORDS.SPHERE_ID ];\n\n            // check that this client is holding this sphere\n            if( typeof spheresHeldByClient[ updatedSphereId ] === 'undefined' ) {\n                continue;\n            }\n\n            let updatedSpherePosition = updatedSphere[ incomingMsgComponents.UPDATE_CLIENT_COORDS.SPHERE_POSITION ];\n\n            if( spheresHeldByClient[ updatedSphereId ] === true ) {\n\n                // set the sphere position in state so new clients see it\n                yield put(\n                    roomDataActions.setPositionForSphereInRoomRequestAction(\n                        updatedSpherePosition,\n                        updatedSphereId,\n                        action.roomName\n                    )\n                );\n\n                // add it to the list of checked updates\n                checkedSpherePositionUpdates.push( updatedSphere );\n            }\n        }\n\n        // set a populated checked array back in the message\n        if( checkedSpherePositionUpdates.length > 0 ) {\n            msgData[ incomingMsgComponents.UPDATE_CLIENT_COORDS.SPHERES ] = checkedSpherePositionUpdates;\n        }\n\n        // or just remove an empty one (= no valid sphere updates)\n        else {\n            delete msgData[ incomingMsgComponents.UPDATE_CLIENT_COORDS.SPHERES ];\n        }\n    }\n\n    // don't record any client state change, just broadcast its new position to other clients\n    let updatedCoordsMessage = makeWsBroadcastMessage(\n        action[ incomingMsgComponents.ALL_MESSAGES.FROM ],\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_CLIENT_COORDS_UPDATED,\n        action[ incomingMsgComponents.ALL_MESSAGES.MSG ][ incomingMsgComponents.ALL_MESSAGES.DATA ]\n    );\n\n    yield put( roomDataActions.publishMessageToRoomRequestAction( updatedCoordsMessage, action.roomName ) );\n\n};\n\n/*******************************************************************************\n* CREATE SPHERE AT POSITION IN ROOM\n*******************************************************************************/\n\nconst createSphereOfToneAtPosition = function* ( action ) {\n\n    let { msgType, msgData } = getTypeAndDataForMessage( action );\n\n    let roomData = yield select( ( state ) => { return state.roomDataReducer; } );\n    let sphereState = roomData.rooms[ action.roomName ].content.spheres;\n\n    // make sure there's sphere space in the room\n    if( Object.keys( sphereState ).length >= sphereConstants.SPHERE_INFO.MAX_NUMBER_OF_SPHERES_PER_ROOM ) {\n        denySphereAction(\n            action.ws,\n            null,   // no sphere under discussion, so ...\n            null,   // ... no client holding it\n            messageConstants.ERROR_TYPES.CREATE_SPHERE_UNAVAILABLE\n        );\n        return;\n    }\n\n    // snag relevant data\n    let newSphereId = uuid();\n    let tone        = msgData[ incomingMsgComponents.CREATE_SPHERE_OF_TONE_AT_POSITION.TONE ];\n    let position    = msgData[ incomingMsgComponents.CREATE_SPHERE_OF_TONE_AT_POSITION.POSITION ];\n    let clientId    = action.ws.id;\n\n    // create the sphere, save in sate\n    yield put(\n        roomDataActions.addSphereOfToneAtPositionInRoomRequestAction(\n            newSphereId,\n            tone,\n            position,\n            action.roomName\n        )\n    );\n\n    // tell the client it created the sphere OK\n    let createSuccessMessage = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.CREATE_SPHERE_SUCCESS,\n        { [ outgoingMsgComponents.CREATE_SPHERE_SUCCESS.SPHERE_ID ]: newSphereId }\n    );\n\n    sendWsMessageWithLogger( action.ws, createSuccessMessage, logger );\n\n    // broadcast 'sphere created' action\n    let data = {\n        [ outgoingMsgComponents.ROOM_SPHERE_CREATED.SPHERE_ID ]:    newSphereId,\n        [ outgoingMsgComponents.ROOM_SPHERE_CREATED.TONE ]:         tone,\n        [ outgoingMsgComponents.ROOM_SPHERE_CREATED.POSITION ]:     position,\n        [ outgoingMsgComponents.ROOM_SPHERE_CREATED.CLIENT_ID ]:    clientId\n    };\n\n    let sphereMsg = makeWsBroadcastMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_SPHERE_CREATED,\n        data\n    );\n\n    yield put( roomDataActions.publishMessageToRoomRequestAction( sphereMsg, action.roomName ) );\n};\n\n/*******************************************************************************\n* GRAB SPHERE BY ID\n*******************************************************************************/\n\nconst grabSphere = function* ( action ) {\n\n    let { msgType, msgData } = getTypeAndDataForMessage( action );\n\n    // get necessary state\n    let roomState = yield select(\n        ( state ) => {\n            return state.roomDataReducer.rooms[ action.roomName ];\n        }\n    );\n\n    // check the client has a spare hand\n    let spheresHeldByClient = roomState.content.clients[ action.ws.id ].spheresHeld;\n\n    // if it's already holding more than 1 ...\n    if( Object.keys( spheresHeldByClient ).length > 1 ) {\n\n        // it can't grab any more\n        denySphereAction(\n            action.ws,\n            sphereId,\n            null,    // it would be invalid for this ws to grab it\n            messageConstants.ERROR_TYPES.CLIENT_HOLDING_MAX_SPHERES\n        );\n        return;\n    }\n\n    // snag relevant data\n    let sphereId = msgData[ incomingMsgComponents.GRAB_SPHERE.SPHERE_ID ];\n    let sphereState = roomState.content.spheres;\n\n    // does sphere exist?\n    if( !sphereState[ sphereId ] ) {\n        denySphereAction(\n            action.ws,\n            sphereId,\n            null,    // it would be invalid for this ws to grab it\n            messageConstants.ERROR_TYPES.NON_EXISTENT_SPHERE\n        );\n        return;\n\n    };\n\n    // is it already held ...\n    if( sphereState[ sphereId ].hold ) {\n        let alreadyHeldErrorCode;\n        let holderId = sphereState[ sphereId ].hold.clientId;\n\n        // ... by this client?\n        if( holderId === action.ws.id ) {\n            alreadyHeldErrorCode = messageConstants.ERROR_TYPES.CLIENT_HOLDING_SPHERE;\n        }\n\n        // or another client?\n        else {\n            alreadyHeldErrorCode = messageConstants.ERROR_TYPES.SPHERE_ALREADY_HELD;\n        }\n\n        // either way, no go\n        denySphereAction(\n            action.ws,\n            sphereId,\n            holderId,    // it would be invalid for this ws to grab it\n            alreadyHeldErrorCode\n        );\n\n        return;\n    }\n\n    // create a hold on this sphere for this client\n    yield put(\n        roomDataActions.createHoldOnSphereForClientInRoomRequestAction(\n            sphereId,\n            action.ws.id,\n            action.roomName\n        )\n    );\n\n    // TODO check the client got the hold?\n\n    // tell the client it got a hold\n    let grabSuccessMessage = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.GRAB_SPHERE_SUCCESS,\n        { [ outgoingMsgComponents.GRAB_SPHERE_SUCCESS.SPHERE_ID ]: sphereId }\n    );\n\n    sendWsMessageWithLogger( action.ws, grabSuccessMessage, logger );\n\n    // broadcast 'sphere grabbed' action\n    let sphereGrabbedData = {\n        [ outgoingMsgComponents.GRAB_SPHERE_SUCCESS.SPHERE_ID ]: sphereId,\n        [ outgoingMsgComponents.GRAB_SPHERE_SUCCESS.CLIENT_ID ]: action.ws.id\n    };\n\n    let sphereGrabbedMsg = makeWsBroadcastMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_SPHERE_GRABBED,\n        sphereGrabbedData\n    );\n\n    yield put( roomDataActions.publishMessageToRoomRequestAction( sphereGrabbedMsg, action.roomName ) );\n\n};\n\n/*******************************************************************************\n* RELEASE SPHERE BY ID\n*******************************************************************************/\n\nconst releaseSphere = function* ( action ) {\n\n    let { msgType, msgData } = getTypeAndDataForMessage( action );\n\n    // get necessary state\n    let sphereState = yield getSphereStateForRoom( action.roomName );\n\n    // snag relevant data\n    let sphereId = msgData[ incomingMsgComponents.RELEASE_SPHERE.SPHERE_ID ];\n\n    // does this sphere exist?\n    if( !sphereState[ sphereId ] ) {\n\n        denySphereAction(\n            action.ws,\n            sphereId,\n            null,    // it would be invalid for this ws to grab it\n            messageConstants.ERROR_TYPES.NON_EXISTENT_SPHERE\n        );\n\n        return;\n    };\n\n    // is it held at all?\n    if( !sphereState[ sphereId ].hold ) {\n\n        denySphereAction(\n            action.ws,\n            sphereId,\n            null,    // it would be invalid for this ws to release it\n            messageConstants.OUTGOING_MESSAGE_TYPES.RELEASE_SPHERE_INVALID\n        );\n\n        return;\n    }\n\n    let holderId = sphereState[ sphereId ].hold.clientId;\n\n    if( typeof holderId === 'undefined' ) {\n\n        denySphereAction(\n            action.ws,\n            sphereId,\n            null,\n            messageConstants.ERROR_TYPES.SYSTEM_ERROR\n        );\n\n        return;\n    }\n\n    // is it held by another client?\n    if( holderId !== action.ws.id ) {\n\n        denySphereAction(\n            action.ws,\n            sphereId,\n            holderId,  // denied because this other one's holding it\n            messageConstants.OUTGOING_MESSAGE_TYPES.RELEASE_SPHERE_DENIED\n        );\n\n        return;\n    }\n\n    // remove the hold on this sphere for this client\n    yield put(\n        roomDataActions.removeHoldOnSphereForClientInRoomRequestAction(\n            sphereId,\n            action.ws.id,\n            action.roomName\n        )\n    );\n\n    // tell the client it released the sphere OK\n    let releaseSuccessMessage = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.RELEASE_SPHERE_SUCCESS,\n        { [ outgoingMsgComponents.RELEASE_SPHERE_SUCCESS.SPHERE_ID ]: sphereId }\n    );\n\n    sendWsMessageWithLogger( action.ws, releaseSuccessMessage, logger );\n\n    // broadcast to the room that the sphere's been released\n    let outgoingMsgData = {\n        [ outgoingMsgComponents.ROOM_SPHERE_RELEASED.SPHERE_ID ]: sphereId,\n        [ outgoingMsgComponents.ROOM_SPHERE_RELEASED.CLIENT_ID ]: action.ws.id\n    };\n\n    let sphereReleasedMessage = makeWsBroadcastMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_SPHERE_RELEASED,\n        outgoingMsgData\n    );\n\n    yield put( roomDataActions.publishMessageToRoomRequestAction( sphereReleasedMessage, action.roomName ) );\n};\n\n/*******************************************************************************\n* SET SPHERE TONE BY ID\n*******************************************************************************/\n\nconst setSphereTone = function* ( action ) {\n\n    let { msgType, msgData } = getTypeAndDataForMessage( action );\n\n    // get necessary state\n    let sphereState = yield getSphereStateForRoom( action.roomName );\n\n    // snag relevant data\n    let sphereId    = msgData[ incomingMsgComponents.SET_SPHERE_TONE.SPHERE_ID ];\n    let tone        = msgData[ incomingMsgComponents.SET_SPHERE_TONE.TONE ];\n\n    // does this sphere exist?\n    if( !sphereState[ sphereId ] ) {\n\n        denySphereAction(\n            action.ws,\n            sphereId,\n            null,    // it would be invalid for this ws to grab it\n            messageConstants.ERROR_TYPES.NON_EXISTENT_SPHERE\n        );\n\n        return;\n    };\n\n    // is it held by another client?\n    if( sphereState[ sphereId ].hold ) {\n\n        let holderId = sphereState[ sphereId ].hold.clientId;\n\n        if( typeof holderId === 'undefined' ) {\n            denySphereAction(\n                action.ws,\n                sphereId,\n                null,\n                messageConstants.ERROR_TYPES.SYSTEM_ERROR\n            );\n\n            return;\n        }\n\n        if( holderId !== action.ws.id ) {\n\n            denySphereAction(\n                action.ws,\n                sphereId,\n                holderId,  // denied because this other one's holding it\n                messageConstants.OUTGOING_MESSAGE_TYPES.SET_SPHERE_TONE_DENIED\n            );\n\n            return;\n        }\n    }\n\n    // set the tone for this sphere\n    yield put(\n        roomDataActions.setToneForSphereInRoomRequestAction(\n            tone,\n            sphereId,\n            action.roomName\n        )\n    );\n\n    // tell the client it set the tone OK\n    let toneSuccessMessage = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.SET_SPHERE_TONE_SUCCESS,\n        { [ outgoingMsgComponents.SET_SPHERE_TONE_SUCCESS.SPHERE_ID ]: sphereId }\n    );\n\n    sendWsMessageWithLogger( action.ws, toneSuccessMessage, logger );\n\n    // broadcast the new tone to the room\n    let outgoingMsgData = {\n        [ outgoingMsgComponents.ROOM_SPHERE_TONE_SET.SPHERE_ID ]:   sphereId,\n        [ outgoingMsgComponents.ROOM_SPHERE_TONE_SET.TONE ]:        tone,\n        [ outgoingMsgComponents.ROOM_SPHERE_TONE_SET.CLIENT_ID ]:   action.ws.id\n    };\n\n    let sphereToneMsg = makeWsBroadcastMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_SPHERE_TONE_SET,\n        outgoingMsgData\n    );\n\n    yield put( roomDataActions.publishMessageToRoomRequestAction( sphereToneMsg, action.roomName ) );\n\n};\n\n/*******************************************************************************\n* SET SPHERE CONNECTIONS BY ID\n*******************************************************************************/\n\nconst setSphereConnections = function* ( action ) {\n\n    let { msgType, msgData } = getTypeAndDataForMessage( action );\n\n    // get necessary state\n    let sphereState = yield getSphereStateForRoom( action.roomName );\n\n    // snag relevant data\n    let sphereId    = msgData[ incomingMsgComponents.SET_SPHERE_CONNECTIONS.SPHERE_ID ];\n    let connections = msgData[ incomingMsgComponents.SET_SPHERE_CONNECTIONS.CONNECTIONS ];\n\n    // make sure there aren't too many connections\n    if( connections.length > sphereConstants.SPHERE_INFO.MAX_NUMBER_OF_CONNECTIONS_PER_SPHERE ) {\n        denySphereAction(\n            action.ws,\n            sphereId,\n            null,    // it would be invalid for this ws to grab it\n            messageConstants.ERROR_TYPES.TOO_MANY_SPHERE_CONNECTIONS\n        );\n        return;\n    }\n\n    // does sphere exist?\n    if( !sphereState[ sphereId ] ) {\n        denySphereAction(\n            action.ws,\n            sphereId,\n            null,    // it would be invalid for this ws to grab it\n            messageConstants.ERROR_TYPES.NON_EXISTENT_SPHERE\n        );\n        return;\n\n    };\n\n    // is it already held ...\n    if( sphereState[ sphereId ].hold ) {\n\n        let holderId = sphereState[ sphereId ].hold.clientId;\n\n        // ... by another client?\n        if( holderId !== action.ws.id ) {\n\n            denySphereAction(\n                action.ws,\n                sphereId,\n                holderId,    // it would be invalid for this ws to grab it\n                messageConstants.OUTGOING_MESSAGE_TYPES.SET_SPHERE_CONNECTIONS_DENIED\n            );\n\n            return;\n        }\n    }\n\n    // if it's a non-empty list of spheres to connect to\n    if( connections.length > 0 ) {\n\n        // make sure that ...\n        let uniqueAvailableConnections = connections.filter(\n            // spheres are not the same\n            ( sphereIdIterator ) => {\n                return sphereIdIterator !== sphereId;\n            }\n        ).filter(\n            // spheres exist in room\n            ( sphereIdIterator ) => {\n                return Object.keys( sphereState ).indexOf( sphereIdIterator ) >= 0;\n            }\n        );\n\n        if( uniqueAvailableConnections.length == 0 ) {\n            logger.trace( `setSphereConnections: no unique available connections requested` );\n            denySphereAction(\n                action.ws,\n                sphereId,\n                null,    // it would be invalid for this ws to set it\n                messageConstants.OUTGOING_MESSAGE_TYPES.SET_SPHERE_CONNECTIONS_DENIED\n            );\n            return;\n        }\n    }\n\n    // set the list of connections for this sphere\n    yield put(\n        roomDataActions.setConnectionsForSphereInRoomRequestAction(\n            connections,\n            sphereId,\n            action.roomName\n        )\n    );\n\n    // tell the client it connected the spheres OK\n    let connectSuccessMessage = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.SET_SPHERE_CONNECTIONS_SUCCESS,\n        { [ outgoingMsgComponents.SET_SPHERE_CONNECTIONS_SUCCESS.SPHERE_ID ]: sphereId }\n    );\n\n    sendWsMessageWithLogger( action.ws, connectSuccessMessage, logger );\n\n    // broadcast the new connections to the room\n    let outgoingMsgData = {\n        [ outgoingMsgComponents.ROOM_SPHERE_CONNECTIONS_SET.SPHERE_ID ]:    sphereId,\n        [ outgoingMsgComponents.ROOM_SPHERE_CONNECTIONS_SET.CONNECTIONS ]:  connections,\n        [ outgoingMsgComponents.ROOM_SPHERE_CONNECTIONS_SET.CLIENT_ID ]:    action.ws.id\n    };\n\n    let sphereConnectionsMsg = makeWsBroadcastMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_SPHERE_CONNECTIONS_SET,\n        outgoingMsgData\n    );\n\n    yield put( roomDataActions.publishMessageToRoomRequestAction( sphereConnectionsMsg, action.roomName ) );\n\n};\n\n/*******************************************************************************\n* DELETE SPHERE BY ID\n*******************************************************************************/\n\nconst deleteSphere = function* ( action ) {\n\n    let { msgType, msgData } = getTypeAndDataForMessage( action );\n\n    // get necessary state\n    let sphereState = yield getSphereStateForRoom( action.roomName );\n\n    // snag relevant data\n    let sphereId    = msgData[ incomingMsgComponents.DELETE_SPHERE.SPHERE_ID ];\n\n    // does this sphere exist?\n    if( !sphereState[ sphereId ] ) {\n\n        denySphereAction(\n            action.ws,\n            sphereId,\n            null,    // it would be invalid for this ws to grab it\n            messageConstants.ERROR_TYPES.NON_EXISTENT_SPHERE\n        );\n\n        return;\n    };\n\n    // is it held at all?\n    if( sphereState[ sphereId ].hold ) {\n\n        let holderId = sphereState[ sphereId ].hold.clientId;\n\n        if( typeof holderId === 'undefined' ) {\n\n            denySphereAction(\n                action.ws,\n                sphereId,\n                null,\n                messageConstants.ERROR_TYPES.SYSTEM_ERROR\n            );\n\n            return;\n        }\n\n        // is it held by another client?\n        if( holderId !== action.ws.id ) {\n\n            denySphereAction(\n                action.ws,\n                sphereId,\n                holderId,  // denied because this other one's holding it\n                messageConstants.OUTGOING_MESSAGE_TYPES.DELETE_SPHERE_DENIED\n            );\n\n            return;\n        }\n    }\n\n    // remove the hold on this sphere for this client\n    yield put(\n        roomDataActions.deleteSphereFromRoomRequestAction(\n            sphereId,\n            action.roomName\n        )\n    );\n\n    // tell the client it deleted the sphere OK\n    let deleteSuccessMessage = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.DELETE_SPHERE_SUCCESS,\n        { [ outgoingMsgComponents.DELETE_SPHERE_SUCCESS.SPHERE_ID ]: sphereId }\n    );\n\n    sendWsMessageWithLogger( action.ws, deleteSuccessMessage, logger );\n\n    // broadcast 'sphere deleted' action\n    let outgoingMsgData = {\n        [ outgoingMsgComponents.ROOM_SPHERE_DELETED.SPHERE_ID ]: sphereId,\n        [ outgoingMsgComponents.ROOM_SPHERE_DELETED.CLIENT_ID ]: action.ws.id\n    };\n\n    let sphereDeletedMsg = makeWsBroadcastMessage(\n        action.ws.id,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_SPHERE_DELETED,\n        outgoingMsgData\n    );\n\n    yield put( roomDataActions.publishMessageToRoomRequestAction( sphereDeletedMsg, action.roomName ) );\n\n};\n\n/*******************************************************************************\n* STRIKE SPHERE BY ID\n*******************************************************************************/\n\nconst strikeSphere = function* ( action ) {\n\n    let { msgType, msgData } = getTypeAndDataForMessage( action );\n\n    // get necessary state\n    let sphereState = yield getSphereStateForRoom( action.roomName );\n\n    // snag relevant data\n    let sphereId    = msgData[ incomingMsgComponents.STRIKE_SPHERE.SPHERE_ID ];\n    let velocity    = msgData[ incomingMsgComponents.STRIKE_SPHERE.VELOCITY ];\n    let fromId      = action[ incomingMsgComponents.ALL_MESSAGES.FROM ];\n\n    // does sphere exist?\n    if( !sphereState[ sphereId ] ) {\n        // drop silently if no such sphere\n        return;\n    };\n\n    // no state change here, just broadcast 'sphere strike' action\n    let outgoingMsgData = {\n        [ outgoingMsgComponents.ROOM_SPHERE_STRUCK.SPHERE_ID ]: sphereId,\n        [ outgoingMsgComponents.ROOM_SPHERE_STRUCK.VELOCITY ]:  velocity,\n        [ outgoingMsgComponents.ROOM_SPHERE_STRUCK.CLIENT_ID ]: fromId\n    };\n\n    let sphereStruckMsg = makeWsBroadcastMessage(\n        action.ws.id,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_SPHERE_STRUCK,\n        outgoingMsgData\n    );\n\n    yield put( roomDataActions.publishMessageToRoomRequestAction( sphereStruckMsg, action.roomName ) );\n\n};\n\n/*******************************************************************************\n* HELPER FUNCTIONS\n*******************************************************************************/\n\nconst getTypeAndDataForMessage = ( message ) => {\n    return {\n        msgType: message[ incomingMsgComponents.ALL_MESSAGES.TYPE ],\n        msgData: message[ incomingMsgComponents.ALL_MESSAGES.MSG ][ incomingMsgComponents.ALL_MESSAGES.DATA ]\n    };\n};\n\nconst getSphereStateForRoom = function* ( roomName ) {\n\n    let sphereState = yield select(\n        ( state ) => {\n            return state.roomDataReducer.rooms[ roomName ].content.spheres;\n        }\n    );\n\n    return sphereState;\n};\n\nconst denySphereAction = ( ws, sphereId, holderId, denialType ) => {\n\n\n    let data = {};\n\n    if( sphereId ) {\n        data[ outgoingMsgComponents.SPHERE_ACTION_DENIED.SPHERE_ID ] = sphereId;\n    }\n\n    if( holderId ) {\n        data[ outgoingMsgComponents.SPHERE_ACTION_DENIED.HOLDER_ID ] = holderId;\n    }\n\n    let actionDeniedMessage = makeWsReplyMessage(\n        config.serverId,\n        denialType,\n        data\n    );\n\n    sendWsMessageWithLogger( ws, actionDeniedMessage, logger );\n    return;\n};\n\n/*******************************************************************************\n* SAGA WATCHER FUNCTIONS\n* these takeEvery clauses match for INCOMING_MESSAGE_TYPES auto-dispatched by\n* messageHandler.handleWebsocketMessage() after message validation\n*******************************************************************************/\n\nconst watchExitRoomRequests = function* () {\n    yield takeEvery(\n        messageConstants.INCOMING_MESSAGE_TYPES.EXIT_ROOM,\n        exitRoom\n    );\n};\n\nconst watchUpdateCoordsForClientRequests = function* () {\n    yield takeEvery(\n        messageConstants.INCOMING_MESSAGE_TYPES.UPDATE_CLIENT_COORDS,\n        updateClientCoords\n    );\n};\n\nconst watchCreateSphereAtPositionRequests = function* () {\n    yield takeEvery(\n        messageConstants.INCOMING_MESSAGE_TYPES.CREATE_SPHERE_OF_TONE_AT_POSITION,\n        createSphereOfToneAtPosition\n    );\n};\n\nconst watchGrabSphereRequests = function* () {\n    yield takeEvery(\n        messageConstants.INCOMING_MESSAGE_TYPES.GRAB_SPHERE,\n        grabSphere\n    );\n};\n\nconst watchReleaseSphereRequests = function* () {\n    yield takeEvery(\n        messageConstants.INCOMING_MESSAGE_TYPES.RELEASE_SPHERE,\n        releaseSphere\n    );\n};\n\nconst watchDeleteSphereRequests = function* () {\n    yield takeEvery(\n        messageConstants.INCOMING_MESSAGE_TYPES.DELETE_SPHERE,\n        deleteSphere\n    );\n};\n\nconst watchSetSphereToneRequests = function* () {\n    yield takeEvery(\n        messageConstants.INCOMING_MESSAGE_TYPES.SET_SPHERE_TONE,\n        setSphereTone\n    );\n};\n\nconst watchSetSphereConnectionsRequests = function* () {\n    yield takeEvery(\n        messageConstants.INCOMING_MESSAGE_TYPES.SET_SPHERE_CONNECTIONS,\n        setSphereConnections\n    );\n};\n\nconst watchStrikeSphereRequests = function* () {\n    yield takeEvery(\n        messageConstants.INCOMING_MESSAGE_TYPES.STRIKE_SPHERE,\n        strikeSphere\n    );\n};\n\nexport default {\n    rootSaga: function* () {\n        const sagas = [\n            watchExitRoomRequests,\n            watchUpdateCoordsForClientRequests,\n            watchCreateSphereAtPositionRequests,\n            watchGrabSphereRequests,\n            watchReleaseSphereRequests,\n            watchDeleteSphereRequests,\n            watchSetSphereToneRequests,\n            watchSetSphereConnectionsRequests,\n            watchStrikeSphereRequests\n        ];\n\n        yield sagaUtils.spawnAutoRestartingSagas( sagas );\n    }\n};\n"
  },
  {
    "path": "backend/src/messages/message-schema.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*******************************************************************************\n*\n* this module implements a JSON schema for incoming messages.\n* it's specified directly as JS objects so we can:\n*\n* - have comments in the schema\n* - include other sections without needing separate JSON files\n* - distinguish between JSON schema property names (barewords)\n*   and schema entry types ('quoted')\n* - dynamically construct the schema's \"properties\" list\n*   from INCOMING_MESSAGE_TYPES constants\n*\n*******************************************************************************/\n\nimport { sphereConstants }  from '../spheres';\nimport messageConstants     from './message-constants';\n\n// shorthand for dictionary of INCOMING_MESSAGE_TYPES constants\nlet messageTypes        = messageConstants.INCOMING_MESSAGE_TYPES;\nlet messageComponents   = messageConstants.INCOMING_MESSAGE_COMPONENTS;\n\n\n/*******************************************************************************\n* UTILITY FUNCTIONS\n*******************************************************************************/\n\n// make 'type: \"string\"' schema definition entry using a messageTypes constant\nconst makeStringTypeForMessageTypeName = ( name ) => {\n    return {\n        type: 'string',\n        minLength:  ( () => { return name.length; } )(),\n        maxLength:  ( () => { return name.length; } )()\n    }\n};\n\n/*******************************************************************************\n* INCOMING MESSAGE TYPE SCHEMA DEFINITIONS\n*******************************************************************************/\n\n// exit current room\nconst exitRoomMessageSchema = {\n\n    type: 'object',\n\n    properties: {\n\n        [ messageComponents.ALL_MESSAGES.TYPE ]: ( () => {\n            return makeStringTypeForMessageTypeName( messageTypes.EXIT_ROOM );\n        } )()\n\n    },\n\n    required: [ messageComponents.ALL_MESSAGES.TYPE ]\n};\n\n// \"client position update\" message\nconst updateClientCoordsMessageSchema = {\n\n    type: 'object',\n\n    properties: {\n\n        [ messageComponents.ALL_MESSAGES.TYPE ]: ( () => {\n            return makeStringTypeForMessageTypeName( messageTypes.UPDATE_CLIENT_COORDS );\n        } )(),\n\n        [ messageComponents.ALL_MESSAGES.DATA ]: {\n\n            type: 'object',\n\n            properties: {\n                [ messageComponents.UPDATE_CLIENT_COORDS.HEAD ]:    { '$ref': `#/definitions/${messageComponents.REFERENCES.COORDINATE_SET}` },\n                [ messageComponents.UPDATE_CLIENT_COORDS.LEFT ]:    { '$ref': `#/definitions/${messageComponents.REFERENCES.COORDINATE_SET}` },\n                [ messageComponents.UPDATE_CLIENT_COORDS.RIGHT ]:   { '$ref': `#/definitions/${messageComponents.REFERENCES.COORDINATE_SET}` },\n                // optional spheres, only used if client is holding spheres\n                [ messageComponents.UPDATE_CLIENT_COORDS.SPHERES ]: {\n                    type: 'array',\n                    items: {\n                        type: 'object',\n                        properties: {\n                            [ messageComponents.UPDATE_CLIENT_COORDS.SPHERE_ID ]: {\n                                type:   'string',\n                                format: 'uuid'\n                            },\n                            [ messageComponents.UPDATE_CLIENT_COORDS.SPHERE_POSITION ]: { '$ref': `#/definitions/${messageComponents.REFERENCES.COORDINATE}` }\n                        },\n                        required: [\n                            messageComponents.UPDATE_CLIENT_COORDS.SPHERE_ID,\n                            messageComponents.UPDATE_CLIENT_COORDS.SPHERE_POSITION\n                        ]\n                    }\n                }\n            },\n\n            required: [\n                messageComponents.UPDATE_CLIENT_COORDS.HEAD,\n                messageComponents.UPDATE_CLIENT_COORDS.LEFT,\n                messageComponents.UPDATE_CLIENT_COORDS.RIGHT\n            ]\n        }\n    },\n\n    required: [\n        messageComponents.ALL_MESSAGES.TYPE,\n        messageComponents.ALL_MESSAGES.DATA\n    ]\n};\n\n// 'create sphere at position' message\nconst createSphereOfToneAtPositionMessageSchema = {\n\n    type: 'object',\n\n    properties: {\n\n        [ messageComponents.ALL_MESSAGES.TYPE ]: ( () => makeStringTypeForMessageTypeName( messageTypes.CREATE_SPHERE_OF_TONE_AT_POSITION ) )(),\n\n        [ messageComponents.ALL_MESSAGES.DATA ]: {\n\n            type: 'object',\n\n            properties: {\n                [ messageComponents.CREATE_SPHERE_OF_TONE_AT_POSITION.TONE ]: {\n                    type:  'number',\n                    minimum:    sphereConstants.SPHERE_INFO.LOWEST_TONE,\n                    maximum:    sphereConstants.SPHERE_INFO.HIGHEST_TONE\n                },\n                [ messageComponents.CREATE_SPHERE_OF_TONE_AT_POSITION.POSITION ]: { '$ref': `#/definitions/${messageComponents.REFERENCES.COORDINATE}` }\n            },\n\n            required: [\n                messageComponents.CREATE_SPHERE_OF_TONE_AT_POSITION.TONE,\n                messageComponents.CREATE_SPHERE_OF_TONE_AT_POSITION.POSITION\n            ]\n        }\n    },\n\n    required: [\n        messageComponents.ALL_MESSAGES.TYPE,\n        messageComponents.ALL_MESSAGES.DATA\n    ]\n};\n\n// 'grab sphere'\nconst grabSphereMessageSchema = {\n\n    type:       'object',\n\n    properties: {\n\n        [ messageComponents.ALL_MESSAGES.TYPE ]: ( () => makeStringTypeForMessageTypeName( messageTypes.GRAB_SPHERE ) )(),\n\n        [ messageComponents.ALL_MESSAGES.DATA ]: {\n\n            type: 'object',\n\n            properties: {\n                [ messageComponents.GRAB_SPHERE.SPHERE_ID ]: {\n                    type:   'string',\n                    format: 'uuid'\n                }\n            },\n\n            required: [ messageComponents.GRAB_SPHERE.SPHERE_ID ]\n        }\n    },\n\n    required: [\n        messageComponents.ALL_MESSAGES.TYPE,\n        messageComponents.ALL_MESSAGES.DATA\n    ]\n};\n\n// 'release sphere'\nconst releaseSphereMessageSchema = {\n\n    type:       'object',\n\n    properties: {\n\n        [ messageComponents.ALL_MESSAGES.TYPE ]: ( () => makeStringTypeForMessageTypeName( messageTypes.RELEASE_SPHERE ) )(),\n\n        [ messageComponents.ALL_MESSAGES.DATA ]: {\n\n            type: 'object',\n\n            properties: {\n                [ messageComponents.RELEASE_SPHERE.SPHERE_ID ]: {\n                    type:   'string',\n                    format: 'uuid'\n                }\n            },\n\n            required: [ messageComponents.RELEASE_SPHERE.SPHERE_ID ]\n        }\n    },\n\n    required: [\n        messageComponents.ALL_MESSAGES.TYPE,\n        messageComponents.ALL_MESSAGES.DATA\n    ]\n};\n\n// 'delete sphere'\nconst deleteSphereMessageSchema = {\n\n    type:       'object',\n\n    properties: {\n\n        [ messageComponents.ALL_MESSAGES.TYPE ]: ( () => makeStringTypeForMessageTypeName( messageTypes.DELETE_SPHERE ) )(),\n\n        [ messageComponents.ALL_MESSAGES.DATA ]: {\n\n            type: 'object',\n\n            properties: {\n                [ messageComponents.DELETE_SPHERE.SPHERE_ID ]: {\n                    type:   'string',\n                    format: 'uuid'\n                }\n            },\n\n            required: [ messageComponents.DELETE_SPHERE.SPHERE_ID ]\n        }\n    },\n\n    required: [\n        messageComponents.ALL_MESSAGES.TYPE,\n        messageComponents.ALL_MESSAGES.DATA\n    ]\n};\n\n// 'strike sphere'\nconst strikeSphereMessageSchema = {\n\n    type:       'object',\n\n    properties: {\n\n        [ messageComponents.ALL_MESSAGES.TYPE ]: ( () => makeStringTypeForMessageTypeName( messageTypes.STRIKE_SPHERE ) )(),\n\n        [ messageComponents.ALL_MESSAGES.DATA ]: {\n\n            type: 'object',\n\n            properties: {\n                [ messageComponents.STRIKE_SPHERE.SPHERE_ID ]: {\n                    type:   'string',\n                    format: 'uuid'\n                },\n                [ messageComponents.STRIKE_SPHERE.VELOCITY ]: {\n                    type:       'number',\n                    minimum:    sphereConstants.SPHERE_INFO.MINIMUM_STRIKE_VELOCITY,\n                    maximum:    sphereConstants.SPHERE_INFO.MAXIMUM_STRIKE_VELOCITY\n                }\n            },\n\n            required: [\n                messageComponents.STRIKE_SPHERE.SPHERE_ID,\n                messageComponents.STRIKE_SPHERE.VELOCITY\n            ]\n        }\n    },\n\n    required: [\n        messageComponents.ALL_MESSAGES.TYPE,\n        messageComponents.ALL_MESSAGES.DATA\n    ]\n};\n\n// 'set sphere tone'\nconst setSphereToneMessageSchema = {\n\n    type:       'object',\n\n    properties: {\n\n        [ messageComponents.ALL_MESSAGES.TYPE ]: ( () => makeStringTypeForMessageTypeName( messageTypes.SET_SPHERE_TONE ) )(),\n\n        [ messageComponents.ALL_MESSAGES.DATA ]: {\n\n            type: 'object',\n\n            properties: {\n                [ messageComponents.SET_SPHERE_TONE.SPHERE_ID ]: {\n                    type:   'string',\n                    format: 'uuid'\n                },\n                [ messageComponents.SET_SPHERE_TONE.TONE ]: {\n                    type:       'number',\n                    minimum:    sphereConstants.SPHERE_INFO.LOWEST_TONE,\n                    maximum:    sphereConstants.SPHERE_INFO.HIGHEST_TONE\n                }\n            },\n\n            required: [\n                messageComponents.SET_SPHERE_TONE.SPHERE_ID,\n                messageComponents.SET_SPHERE_TONE.TONE\n            ]\n        }\n    },\n\n    required: [\n        messageComponents.ALL_MESSAGES.TYPE,\n        messageComponents.ALL_MESSAGES.DATA\n    ]\n};\n\n// 'set sphere connections'\nconst setSphereConnectionsMessageSchema = {\n\n    type:       'object',\n\n    properties: {\n\n        [ messageComponents.ALL_MESSAGES.TYPE ]: ( () => makeStringTypeForMessageTypeName( messageTypes.SET_SPHERE_CONNECTIONS ) )(),\n\n        [ messageComponents.ALL_MESSAGES.DATA ]: {\n\n            type: 'object',\n\n            properties: {\n                [ messageComponents.SET_SPHERE_CONNECTIONS.SPHERE_ID ]: {\n                    type:   'string',\n                    format: 'uuid'\n                },\n                [ messageComponents.SET_SPHERE_CONNECTIONS.CONNECTIONS ]: {\n                    type:     'array',\n                    items:    {\n                        type:   'string',\n                        format: 'uuid'\n                    }\n                }\n            },\n\n            required: [\n                messageComponents.SET_SPHERE_CONNECTIONS.SPHERE_ID,\n                messageComponents.SET_SPHERE_CONNECTIONS.CONNECTIONS\n            ]\n        }\n    },\n\n    required: [\n        messageComponents.ALL_MESSAGES.TYPE,\n        messageComponents.ALL_MESSAGES.DATA\n    ]\n};\n\n/*******************************************************************************\n* MAPPING FROM INCOMING MESSAGE TYPES TO APPROPRIATE SCHEMA COMPONENTS\n********************************************************************************/\n\nconst incomingMessageSchemaDefinitions = ( () => {\n\n    let definitions = {};\n    let m = messageTypes;\n\n    definitions[ m.EXIT_ROOM ]                          = exitRoomMessageSchema;\n    definitions[ m.UPDATE_CLIENT_COORDS ]               = updateClientCoordsMessageSchema;\n    definitions[ m.CREATE_SPHERE_OF_TONE_AT_POSITION ]  = createSphereOfToneAtPositionMessageSchema;\n    definitions[ m.GRAB_SPHERE ]                        = grabSphereMessageSchema;\n    definitions[ m.RELEASE_SPHERE ]                     = releaseSphereMessageSchema;\n    definitions[ m.DELETE_SPHERE ]                      = deleteSphereMessageSchema;\n    definitions[ m.STRIKE_SPHERE ]                      = strikeSphereMessageSchema;\n    definitions[ m.SET_SPHERE_TONE ]                    = setSphereToneMessageSchema;\n    definitions[ m.SET_SPHERE_CONNECTIONS ]             = setSphereConnectionsMessageSchema;\n\n    return definitions;\n\n})();\n\n\n/*******************************************************************************\n* COMPONENT DEFINITIONS REFERRED TO IN MESSAGE TYPES, MUST BE INCLUDED IN SCHEMA\n********************************************************************************/\n\nconst componentDefinitions = {\n\n    // an x-y-z coordinate\n    [ messageComponents.REFERENCES.COORDINATE ]: {\n\n        type: 'object',\n\n        properties: {\n            [ messageComponents.REF_COORDINATE.X ]: { type: 'number' },\n            [ messageComponents.REF_COORDINATE.Y ]: { type: 'number' },\n            [ messageComponents.REF_COORDINATE.Z ]: { type: 'number' }\n        },\n\n        required: [\n            messageComponents.REF_COORDINATE.X,\n            messageComponents.REF_COORDINATE.Y,\n            messageComponents.REF_COORDINATE.Z\n        ]\n    },\n\n    // a pair of coordinates for rotation/position\n    [ messageComponents.REFERENCES.COORDINATE_SET ]: {\n\n        type: 'object',\n\n        properties: {\n            [ messageComponents.REF_COORDINATE_SET.POSITION ]: { '$ref': `#/definitions/${messageComponents.REFERENCES.COORDINATE}` },\n            [ messageComponents.REF_COORDINATE_SET.ROTATION ]: { '$ref': `#/definitions/${messageComponents.REFERENCES.COORDINATE}` }\n        },\n\n        required: [\n            messageComponents.REF_COORDINATE_SET.POSITION,\n            messageComponents.REF_COORDINATE_SET.ROTATION\n        ]\n    }\n};\n\n/*******************************************************************************\n* COMBINED LIST OF COMPONENT AND MESSAGE DEFINITIONS INCLUDED IN MAIN SCHEMA\n********************************************************************************/\n\nconst messageSchemaDefinitions = Object.assign(\n    componentDefinitions,\n    incomingMessageSchemaDefinitions\n);\n\n\n/*******************************************************************************\n* PROPERTIES USED IN MAIN SCHEMA\n********************************************************************************/\n\n/*\n * list of message types defined as properties in schema,\n * dynamically defined from the list of incoming message schema definitions\n *\n * comes out looking like this, as per JSON schema reqs:\n *\n * properties = {\n *     room_client_position_update: {\n *         '$ref': '#/definitions/room_client_position_update'\n *     }\n * }\n */\nconst messageSchemaProperties = ( () => {\n\n    let properties = {};\n\n    Object.keys( incomingMessageSchemaDefinitions ).forEach(\n        ( messageTypeName ) => {\n            properties[ messageTypeName ] = {\n                '$ref': `#/definitions/${messageTypeName}`\n            }\n        }\n    );\n\n    return properties;\n})();\n\n/*******************************************************************************\n* MAIN SCHEMA OBJECT INCORPORATING DEFINITIONS AND PROPERTIES FROM ABOVE\n*******************************************************************************/\n\nconst messageSchema = {\n\n    // these are type definitions shared by multiple objects\n    definitions: messageSchemaDefinitions,\n\n    // these are the various types\n    properties: messageSchemaProperties\n};\n\nexport {\n    messageSchema\n}\n"
  },
  {
    "path": "backend/src/messages/message-validator.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport ajv                  from 'ajv';\n\nimport { deserialize }      from '../s11n';\nimport { messageSchema }    from './message-schema';\n\nimport messageConstants    from './message-constants';\n\nconst messageComponents = messageConstants.INCOMING_MESSAGE_COMPONENTS;\n\n/*\n * create a validator object for all incoming messages\n */\nlet ajvOptions = {\n    allErrors:          true,   // log all validation errors, not just first\n    removeAdditional:   \"all\"   // this modifies the original message(!)\n};\nlet validator   = new ajv( ajvOptions );\nlet validate    = validator.compile( messageSchema );\n\nlet messageTypesNotRequiringData = [\n    messageConstants.INCOMING_MESSAGE_TYPES.EXIT_ROOM\n];\n\n/*\n * validates messages received over the wire against the appropriate schema.\n * called from messageHandler:handleWebsocketMessageWithStore().\n *\n * any kind of validation issue throws an exception, and messageHandler\n * drops the message.\n */\nconst validateMessage = ( wireMessage ) => {\n\n    let deserializedMessage;\n\n    try {\n        deserializedMessage = deserialize( wireMessage );\n    }\n    catch( error ) {\n        throw error;\n    }\n\n    // only validate if there's a message type\n    if( !deserializedMessage[ messageComponents.ALL_MESSAGES.TYPE ] ) {\n        throw new Error( `incoming message has no '${messageComponents.ALL_MESSAGES.TYPE}' property` );\n    }\n\n    // only validate if there's message data\n    if( !deserializedMessage[ messageComponents.ALL_MESSAGES.DATA ] ) {\n        // except for message types that don't require message data\n        if( messageTypesNotRequiringData.indexOf( deserializedMessage[ messageComponents.ALL_MESSAGES.TYPE ] ) < 0 ) {\n            throw new Error( `incoming message has no '${messageComponents.ALL_MESSAGES.DATA}' property` );\n        }\n    }\n\n    // only validate if it's an acceptable message type\n    if( !isValidMessageType( deserializedMessage[ messageComponents.ALL_MESSAGES.TYPE ] ) ) {\n        throw new Error( `incoming message has invalid type '${deserializedMessage[ messageComponents.ALL_MESSAGES.TYPE ]}'` );\n    }\n\n    // run the validator\n    return validateMessageOfType( deserializedMessage, deserializedMessage[ messageComponents.ALL_MESSAGES.TYPE ] );\n};\n\n/*\n * check the provided type is in the top level 'properties' of the messageSchema\n */\nconst isValidMessageType = ( type ) => {\n    return Object.keys( messageSchema.properties ).indexOf( type ) > -1;\n};\n\n/*\n * validator function for incoming messages\n */\nconst validateMessageOfType = ( message, type ) => {\n\n    let objectToValidate = {};\n    objectToValidate[ type ] = message;\n\n    let valid = validate( objectToValidate )\n\n    if( valid !== true ) {\n        let errorMsgs = validate.errors.map(\n            ( error ) => {\n                return `${error.dataPath} ${error.message}`;\n            }\n        );\n        let errorMsg = errorMsgs.join( '; ' );\n        throw new Error( errorMsg );\n    }\n\n    return message;\n};\n\nexport default {\n    validateMessage\n};\n"
  },
  {
    "path": "backend/src/pubsub/index.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport pubsubClient     from './pubsub-client';\n\nexport {\n    pubsubClient\n};\n"
  },
  {
    "path": "backend/src/pubsub/pubsub-client.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport pubsub           from '@google-cloud/pubsub';\nimport config           from '../config';\nimport { logger }       from '../logger';\n\nlet client = ( ( conf ) => {\n\n    if( !client ) {\n        if( process.env.PUBSUB_EMULATOR_HOST ) {\n            logger.info( `using PubSub emulator running at ${process.env.PUBSUB_EMULATOR_HOST}` );\n        }\n        else {\n            logger.info( `using live PubSub on GCP` );\n        }\n\n        client = pubsub( conf );\n    }\n\n    return client;\n})( config );\n\nconst createPubsubTopic = function* ( topicName ) {\n    return yield client.createTopic( topicName );\n};\n\nconst getPubsubTopic = function* ( topicName ) {\n    return yield client.topic( topicName );\n};\n\nexport default {\n    createPubsubTopic,\n    getPubsubTopic\n};\n"
  },
  {
    "path": "backend/src/rooms/index.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport roomDataActions      from './room-data-actions';\nimport roomDataConstants    from './room-data-constants';\nimport roomDataReducer      from './room-data-reducer';\nimport roomNames            from './room-names';\nimport roomSagas            from './room-sagas';\nimport roomStateActions     from './room-state-actions';\nimport roomStateConstants   from './room-state-constants';\nimport roomStateReducer     from './room-state-reducer';\nimport roomStateUtils       from './room-state-utils';\n\nexport {\n    roomDataActions,\n    roomDataConstants,\n    roomDataReducer,\n    roomNames,\n    roomSagas,\n    roomStateActions,\n    roomStateConstants,\n    roomStateReducer,\n    roomStateUtils\n};\n"
  },
  {
    "path": "backend/src/rooms/room-data-actions.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport constants from './room-data-constants';\n\n/*******************************************************************************\n* ADD/REMOVE CLIENTS TO/FROM ROOMS\n*******************************************************************************/\n\nconst addWebsocketToQueueForRoomRequestAction = ( wsId, roomName ) => {\n    let type = constants.ACTION_TYPES.ADD_WEBSOCKET_TO_QUEUE_FOR_ROOM;\n    return { type, wsId, roomName };\n};\n\nconst removeWebsocketFromQueueForRoomRequestAction = ( wsId, roomName ) => {\n    let type = constants.ACTION_TYPES.REMOVE_WEBSOCKET_FROM_QUEUE_FOR_ROOM;\n    return { type, wsId, roomName };\n};\n\nconst addLocalClientToRoomRequestAction = ( clientId, clientHeadsetType, roomName ) => {\n    let type = constants.ACTION_TYPES.ADD_LOCAL_CLIENT_TO_ROOM;\n    return { type, clientId, clientHeadsetType, roomName };\n};\n\nconst removeLocalClientFromRoomRequestAction = ( clientId, roomName ) => {\n    let type = constants.ACTION_TYPES.REMOVE_LOCAL_CLIENT_FROM_ROOM;\n    return { type, clientId, roomName };\n};\n\n/*******************************************************************************\n* ROOM SETUP/MAINTAIN/TEARDOWN\n*******************************************************************************/\n\nconst setLastHeartbeatCountForRoomRequestAction = ( count, roomName ) => {\n    let type = constants.ACTION_TYPES.UPDATE_LAST_HEARTBEAT_FOR_ROOM;\n    return { type, count, roomName };\n};\n\nconst setHeartbeatTaskForRoomRequestAction = ( heartbeatTask, roomName ) => {\n    let type = constants.ACTION_TYPES.SET_HEARTBEAT_TASK_FOR_ROOM;\n    return { type, heartbeatTask, roomName };\n};\n\nconst removeHeartbeatTaskForRoomRequestAction = ( roomName ) => {\n    let type = constants.ACTION_TYPES.REMOVE_HEARTBEAT_TASK_FOR_ROOM;\n    return { type, roomName };\n};\n\nconst cancelHeartbeatTaskForRoomRequestAction = ( roomName ) => {\n    let type = constants.ACTION_TYPES.CANCEL_HEARTBEAT_TASK_FOR_ROOM;\n    return { type, roomName };\n};\n\nconst checkForEmptyRoomRequestAction = ( roomName ) => {\n    let type = constants.ACTION_TYPES.CHECK_FOR_EMPTY_ROOM;\n    return { type, roomName };\n};\n\n/*******************************************************************************\n* SPHERE STATE ACTIONS\n*******************************************************************************/\n\nconst addSphereOfToneAtPositionInRoomRequestAction = ( newSphereId, tone, position, roomName ) => {\n    let type = constants.ACTION_TYPES.ADD_SPHERE_OF_TONE_AT_POSITION_IN_ROOM;\n    return { type, newSphereId, tone, position, roomName };\n};\n\nconst createHoldOnSphereForClientInRoomRequestAction = ( sphereId, clientId, roomName ) => {\n    let type = constants.ACTION_TYPES.CREATE_HOLD_ON_SPHERE_FOR_CLIENT_IN_ROOM;\n    return { type, sphereId, clientId, roomName };\n};\n\nconst setHoldTimeoutTaskForSphereInRoomRequestAction = ( timeoutTask, sphereId, roomName ) => {\n    let type = constants.ACTION_TYPES.SET_HOLD_TIMEOUT_TASK_FOR_SPHERE_IN_ROOM;\n    return { type, timeoutTask, sphereId, roomName };\n};\n\nconst deleteHoldTimeoutTaskForSphereInRoomRequestAction = ( sphereId, roomName ) => {\n    let type = constants.ACTION_TYPES.DELETE_HOLD_TIMEOUT_TASK_FOR_SPHERE_IN_ROOM;\n    return { type, sphereId, roomName };\n};\n\nconst setPositionForSphereInRoomRequestAction = ( position, sphereId, roomName ) => {\n    let type = constants.ACTION_TYPES.SET_POSITION_FOR_SPHERE_IN_ROOM;\n    return { type, position, sphereId, roomName };\n};\n\nconst setToneForSphereInRoomRequestAction = ( tone, sphereId, roomName ) => {\n    let type = constants.ACTION_TYPES.SET_TONE_FOR_SPHERE_IN_ROOM;\n    return { type, tone, sphereId, roomName };\n};\n\nconst setConnectionsForSphereInRoomRequestAction = ( connections, sphereId, roomName ) => {\n    let type = constants.ACTION_TYPES.SET_CONNECTIONS_FOR_SPHERE_IN_ROOM;\n    return { type, connections, sphereId, roomName };\n};\n\nconst removeHoldOnSphereForClientInRoomRequestAction = ( sphereId, clientId, roomName ) => {\n    let type = constants.ACTION_TYPES.REMOVE_HOLD_ON_SPHERE_FOR_CLIENT_IN_ROOM;\n    return { type, sphereId, clientId, roomName };\n};\n\nconst deleteSphereFromRoomRequestAction = ( sphereId, roomName ) => {\n    let type = constants.ACTION_TYPES.DELETE_SPHERE_FROM_ROOM;\n    return { type, sphereId, roomName };\n};\n\n/*******************************************************************************\n* PUBLISH MESSAGE\n*******************************************************************************/\n\nconst publishMessageToRoomRequestAction = ( message, roomName ) => {\n    let type = constants.ACTION_TYPES.PUBLISH_MESSAGE_TO_ROOM;\n    return { type, message, roomName };\n};\n\nexport default {\n\n    // add/remove clients to/from rooms\n    addWebsocketToQueueForRoomRequestAction,\n    removeWebsocketFromQueueForRoomRequestAction,\n    addLocalClientToRoomRequestAction,\n    removeLocalClientFromRoomRequestAction,\n\n    // room setup/maintain/teardown\n    setLastHeartbeatCountForRoomRequestAction,\n    setHeartbeatTaskForRoomRequestAction,\n    removeHeartbeatTaskForRoomRequestAction,\n    cancelHeartbeatTaskForRoomRequestAction,\n    checkForEmptyRoomRequestAction,\n\n    // sphere state actions\n    addSphereOfToneAtPositionInRoomRequestAction,\n    createHoldOnSphereForClientInRoomRequestAction,\n    setHoldTimeoutTaskForSphereInRoomRequestAction,\n    deleteHoldTimeoutTaskForSphereInRoomRequestAction,\n    setPositionForSphereInRoomRequestAction,\n    setToneForSphereInRoomRequestAction,\n    setConnectionsForSphereInRoomRequestAction,\n    removeHoldOnSphereForClientInRoomRequestAction,\n    deleteSphereFromRoomRequestAction,\n\n    // publish message saga action\n    publishMessageToRoomRequestAction\n\n};\n"
  },
  {
    "path": "backend/src/rooms/room-data-constants.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HEADSET_TYPES } from '../messages/message-constants';\n\nconst ACTION_TYPES = {\n\n    ADD_WEBSOCKET_TO_QUEUE_FOR_ROOM:                'add_websocket_to_queue_for_room',\n    REMOVE_WEBSOCKET_FROM_QUEUE_FOR_ROOM:           'remove_websocket_from_queue_for_room',\n    ADD_LOCAL_CLIENT_TO_ROOM:                       'add_local_client_to_room',\n    REMOVE_LOCAL_CLIENT_FROM_ROOM:                  'remove_local_client_from_room',\n\n    // room setup/teardown\n    UPDATE_LAST_HEARTBEAT_FOR_ROOM:                 'update_last_heartbeat_for_room',\n    SET_HEARTBEAT_TASK_FOR_ROOM:                    'set_heartbeat_task_for_room',\n    REMOVE_HEARTBEAT_TASK_FOR_ROOM:                 'remove_heartbeat_task_for_room',\n    CANCEL_HEARTBEAT_TASK_FOR_ROOM:                 'cancel_heartbeat_task_for_room',\n    CHECK_FOR_EMPTY_ROOM:                           'check_for_empty_room',\n\n    // client state actions\n    SET_COORDS_FOR_CLIENT_IN_ROOM:                  'set_coords_for_client_in_room',\n    REMOVE_CLIENT_FROM_ROOM:                        'remove_client_from_room',\n\n    // sphere state actions\n    ADD_SPHERE_OF_TONE_AT_POSITION_IN_ROOM:         'add_sphere_of_tone_at_position_in_room',\n    SET_POSITION_FOR_SPHERE_IN_ROOM:                'set_position_for_sphere_in_room',\n    SET_TONE_FOR_SPHERE_IN_ROOM:                    'set_tone_for_sphere_in_room',\n    SET_CONNECTIONS_FOR_SPHERE_IN_ROOM:             'set_connections_for_sphere_in_room',\n    CREATE_HOLD_ON_SPHERE_FOR_CLIENT_IN_ROOM:       'create_hold_on_sphere_for_client_in_room',\n    SET_HOLD_TIMEOUT_TASK_FOR_SPHERE_IN_ROOM:       'set_hold_timeout_task_for_sphere_in_room',\n    DELETE_HOLD_TIMEOUT_TASK_FOR_SPHERE_IN_ROOM:    'delete_hold_timeout_task_for_sphere_in_room',\n    REMOVE_HOLD_ON_SPHERE_FOR_CLIENT_IN_ROOM:       'remove_hold_on_sphere_for_client_in_room',\n    DELETE_SPHERE_FROM_ROOM:                        'delete_sphere_from_room',\n\n    // publish to clients in room\n    PUBLISH_MESSAGE_TO_ROOM:                        'publish_message_to_room'\n};\n\nconst CLIENT_INFO = {\n    CLIENT_TYPE_LOCAL:  'client_type_local',\n    CLIENT_TYPE_REMOTE: 'client_type_remote',\n    CLIENT_INACTIVITY_TIMEOUTS_IN_MS: {\n        [ HEADSET_TYPES.HEADSET_TYPE_3DOF ]:    120000, // 2m\n        [ HEADSET_TYPES.HEADSET_TYPE_6DOF ]:    120000, // 2m\n        [ HEADSET_TYPES.HEADSET_TYPE_VIEWER ]:  120000, // 2m\n    },\n    CLIENT_INACTIVITY_TIMEOUT_CHECKS_IN_MS: {\n        [ HEADSET_TYPES.HEADSET_TYPE_3DOF ]:    30000, // 30s\n        [ HEADSET_TYPES.HEADSET_TYPE_6DOF ]:    30000, // 30s\n        [ HEADSET_TYPES.HEADSET_TYPE_VIEWER ]:  30000, // 30s\n    }\n};\n\nconst ERROR_TYPES = {\n    ROOM_TIMEOUT:       'room_timeout',\n    JOINING_ROOM:       'error_joining_room',\n    ROOM_FULL:          'error_room_full',\n    WAITING_FOR_STATE:  'waiting_for_state'\n};\n\nconst ROOM_INFO = {\n    MINIMUM_SOUNDBANK_NUMBER:   0,\n    MAXIMUM_SOUNDBANK_NUMBER:   2\n};\n\nconst SPHERE_INFO = {\n    SPHERE_HOLD_TIMEOUT_IN_MS:          10000,\n    SPHERE_HOLD_TIMEOUT_CHECK_IN_MS:    2500\n};\n\nexport default {\n    ACTION_TYPES,\n    CLIENT_INFO,\n    ERROR_TYPES,\n    ROOM_INFO,\n    SPHERE_INFO\n};\n"
  },
  {
    "path": "backend/src/rooms/room-data-reducer.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport redux                from 'redux';\nimport uuid                 from 'uuid';\nimport sizeof               from 'object-sizeof';\nimport util                 from 'util';\n\nimport {\n    initStateObject,\n    createFilteredActionHandler\n}  from '../utils/reducer-utils';\n\nimport { logger }           from '../logger';\nimport messageConstants     from '../messages/message-constants';\n\nconst HT_3DOF   = messageConstants.HEADSET_TYPES.HEADSET_TYPE_3DOF;\nconst HT_6DOF   = messageConstants.HEADSET_TYPES.HEADSET_TYPE_6DOF;\nconst HT_VIEW   = messageConstants.HEADSET_TYPES.HEADSET_TYPE_VIEWER;\n\nconst CONNECTIONS_LABEL = messageConstants.OUTGOING_MESSAGE_COMPONENTS.ROOM_STATUS_INFO.CONNECTIONS;\nconst POSITION_LABEL    = messageConstants.OUTGOING_MESSAGE_COMPONENTS.ROOM_STATUS_INFO.POSITION;\nconst TONE_LABEL    = messageConstants.OUTGOING_MESSAGE_COMPONENTS.ROOM_STATUS_INFO.TONE;\n\nimport {\n    makeSphereOfToneAtPosition,\n    sphereConstants,\n    trees\n}  from '../spheres';\n\nimport config               from '../config';\n\nimport roomDataConstants    from './room-data-constants';\nimport roomStateConstants   from './room-state-constants';\nimport roomNames            from './room-names';\n\n/*******************************************************************************\n* HELPER FUNCTIONS\n*******************************************************************************/\n\nconst createEmptyRoom = () => {\n    return {\n\n        heartbeatTask:      null,\n        lastHeartbeat:      {\n            count:      0,\n            timestamp:  0\n        },\n\n        setupComplete:              false,\n        waitingForStateStatus:      false,\n        checkingForEmptyRoomStatus: false,\n\n        soundbank:          null,\n        spheres:            {},\n\n        websockets:         {},\n        clients:            {}\n    };\n};\n\n\nconst createNewRoom = () => {\n    let emptyRoom = createEmptyRoom();\n\n    emptyRoom.soundbank = getRandomInt(\n        roomDataConstants.ROOM_INFO.MINIMUM_SOUNDBANK_NUMBER,\n        roomDataConstants.ROOM_INFO.MAXIMUM_SOUNDBANK_NUMBER\n    );\n\n    emptyRoom.spheres   = generateSpheresForRoom( emptyRoom.soundbank );\n\n    return emptyRoom;\n};\n\nconst generateSpheresForRoom = ( soundbank ) => {\n\n    const roomSpheres = {}\n\n    function dist(a, b){\n        return Math.sqrt(Math.pow(a.x - b.x, 2) + \n                         // Math.pow(a.y - b.y, 2) + \n                         Math.pow(a.z - b.z, 2))\n    }\n\n    function randomPos(){\n        const rad = 2.5\n        const pos = {\n            x : Math.random() * rad - rad/2,\n            y : Math.random() * 0.7 + 0.9,\n            z : Math.random() * rad - rad/2\n        }\n        // check that the position is not too close to any other sphere\n        let closestDist = Infinity\n        for (let id in roomSpheres){\n            const roomPos = roomSpheres[id][POSITION_LABEL]\n            closestDist = Math.min(dist(pos, roomPos), closestDist)\n        }\n        //only return the position if its greater than the thresh\n        if (closestDist > 0.5){\n            return pos\n        } else {\n        //otherwise generate a new one\n            return randomPos()\n        }\n    }\n\n    for (let i = 0; i < 10; i++){\n        roomSpheres[uuid()] = {\n            [TONE_LABEL] : getRandomInt(sphereConstants.SPHERE_INFO.LOWEST_TONE, \n                                        sphereConstants.SPHERE_INFO.HIGHEST_TONE),\n            [POSITION_LABEL] : randomPos()\n        }\n    }\n\n    // return spheres\n    return roomSpheres;\n};\n\nconst getRandomInt = ( min, max ) => {\n    let intMin = Math.ceil( min );\n    let intMax = Math.floor( max );\n    return Math.floor( Math.random() * ( intMax - intMin + 1 ) ) + intMin;\n};\n\nconst getZeroCoordsForNewClient = () => {\n    return {\n        head: {\n            position: { x: 0, y: 0, z: 0 },\n            rotation: { x: 0, y: 0, z: 0 }\n        },\n        left: {\n            position: { x: 0, y: 0, z: 0 },\n            rotation: { x: 0, y: 0, z: 0 }\n        },\n        right: {\n            position: { x: 0, y: 0, z: 0 },\n            rotation: { x: 0, y: 0, z: 0 }\n        }\n    };\n};\n\n/*******************************************************************************\n* GENERATE INITIAL EMPTY STATE FOR ALL ROOMS\n*******************************************************************************/\n\n// dictionary of empty room data objects for all names\nconst initialState = ( () => {\n\n    let createFunction = () => {\n        return {\n            queue:      [],\n            tasks:      {}\n        };\n    };\n\n    return {\n        rooms: initStateObject( roomNames, createFunction, 'room data objects', logger ),\n        avails:     ( () => {\n            let obj = {};\n            Object.values( messageConstants.HEADSET_TYPES ).forEach(\n                ( headsetType ) => {\n                    obj[ headsetType ] = {};\n                }\n            );\n            return obj;\n        } )()\n    };\n\n})();\n\n\n/*******************************************************************************\n* ROOM INFO/STATUS REDUCER FUNCTIONS\n*******************************************************************************/\n\n// create a new room state including spheres\n// action: { roomName }\nconst initRoomContent = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    state.rooms[ action.roomName ].content = createNewRoom();\n    logger.trace( `created default content for new room ${action.roomName}` );\n    return state;\n};\n\n// action: { wsId, roomName }\nconst addWebsocketToQueueForRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    if( state.rooms[ action.roomName ].queue.indexOf( action.wsId ) >= 0 ) {\n        return state;\n    }\n    state.rooms[ action.roomName ].queue.push( action.wsId );\n    logger.trace( `[+] ${state.rooms[ action.roomName ].queue.length} websockets in queue for room ${action.roomName}` );\n    return state;\n};\n\n// action: { wsId, roomName }\nconst removeWebsocketFromQueueForRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    state.rooms[ action.roomName ].queue = state.rooms[ action.roomName ].queue.filter(\n        ( clientId ) => {\n            return clientId != action.wsId;\n        }\n    );\n    logger.trace( `[-] ${state.rooms[ action.roomName ].queue.length} websockets in queue for room ${action.roomName}` );\n    return state;\n};\n\n/*******************************************************************************\n* CLIENT REDUCER FUNCTIONS\n*******************************************************************************/\n\n// action: { clientId, roomName }\nconst addLocalClientToRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    let roomClients         = state.rooms[ action.roomName ].content.clients;\n\n    if( typeof roomClients[ action.clientId ] !== 'undefined' ) {\n        return state;\n    }\n\n    roomClients[ action.clientId ] = {\n        headsetType:    action.clientHeadsetType,\n        coords:         getZeroCoordsForNewClient(),\n        spheresHeld:    {}\n    }\n\n    logger.trace( `[+] there are now ${Object.keys( roomClients ).length} total clients in room '${action.roomName}'` );\n\n    return updateClientCountsForRoom( state, action );\n};\n\n// action: { clientId, roomName }\nconst removeLocalClientFromRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    state.rooms[ action.roomName ].content.clients[ action.clientId ] = null;\n    delete state.rooms[ action.roomName ].content.clients[ action.clientId ];\n\n    let roomClients = state.rooms[ action.roomName ].content.clients;\n    logger.trace( `[-] there are now ${Object.keys( roomClients ).length} total clients in room '${action.roomName}'` );\n\n    return updateClientCountsForRoom( state, action );\n};\n\n/*\n *  for a room to be available to 6dof:\n *\n *      6dof must be less than 6dofThreshold\n *      3dof + 6dof must be less than 3dofThreshold + 6dofThreshold\n *      3dof + 6dof + viewers must be less than maxClientsPerRoom\n *\n *  for a room to be available to 3dof:\n *\n *      3dof must be less than 3dofThreshold\n *      3dof + 6dof must be less than 3dofThreshold + 6dofThreshold\n *      3dof + 6dof + viewers must be less than maxClientsPerRoom\n *\n *  for a room to be available to viewer:\n *\n *      3dof + 6dof + viewers must be less than maxClientsPerRoom\n */\nconst updateClientCountsForRoom = ( state, action ) => {\n\n    // this gives { '3dof': 2, '6dof': 1, viewer: 4 }\n    let clientCounts = Object.values( state.rooms[ action.roomName ].content.clients ).reduce(\n        ( acc, client ) => {\n            acc[ client.headsetType ] = acc[ client.headsetType ] + 1;\n            return acc;\n        },\n        ( () => {\n            let obj = {};\n            Object.values( messageConstants.HEADSET_TYPES ).forEach(\n                ( headsetType ) => {\n                    obj[ headsetType ] = 0;\n                }\n            );\n            return obj;\n        } )()\n    );\n\n    // this gives { '3dof': 2 + '6dof': 1 + viewer: 4 } =  totalClients: 7\n    let totalClients = Object.values( clientCounts ).reduce(\n        ( acc, count ) => {\n            return acc + count;\n        },\n        0\n    );\n\n    // if the room's full, it's not available at all;\n    // if it's empty, it doesn't go in the availability lists,\n    // as it'll be found from the list of INIT-state rooms\n    // in roomStateReducer.roomsByState\n    if( totalClients >= config.maxClientsPerRoom || totalClients === 0) {\n        delete state.avails[ HT_6DOF ][ action.roomName ];\n        delete state.avails[ HT_3DOF ][ action.roomName ];\n        delete state.avails[ HT_VIEW ][ action.roomName ];\n        return state;\n    }\n    else {\n        state.avails[ HT_VIEW ][ action.roomName ] = true;\n    }\n\n    let num6dof     = clientCounts[ HT_6DOF ];\n    let num3dof     = clientCounts[ HT_3DOF ];\n    let numViewers  = clientCounts[ HT_VIEW ];\n\n    let threshold6dof   = config.headsetRules[ HT_6DOF ].threshold;\n    let threshold3dof   = config.headsetRules[ HT_3DOF ].threshold;\n    let thresholdViewer = config.headsetRules[ HT_VIEW ].threshold;\n\n    let totalStrikers       = num6dof + num3dof;\n    let strikerThreshold    = threshold6dof + threshold3dof;\n\n    if( num6dof < threshold6dof && totalStrikers < strikerThreshold ) {\n        state.avails[ HT_6DOF ][ action.roomName ] = true;\n    }\n    else {\n        delete state.avails[ HT_6DOF ][ action.roomName ];\n    }\n\n    if( num3dof < threshold3dof && totalStrikers < strikerThreshold ) {\n        state.avails[ HT_3DOF ][ action.roomName ] = true;\n    }\n    else {\n        delete state.avails[ HT_3DOF ][ action.roomName ];\n    }\n\n    return state;\n};\n\n// action: { count, roomName }\nconst setLastHeartbeatCountForRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    let newHeartbeat = {\n        count:      action.count,\n        timestamp:  Date.now()\n    };\n    state.rooms[ action.roomName ].content.lastHeartbeat = newHeartbeat;\n    return state;\n};\n\n// action: { heartbeatTask, roomName }\nconst setHeartbeatTaskForRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    state.rooms[ action.roomName ].tasks.heartbeat = action.heartbeatTask;\n    return state;\n};\n\n// action: { roomName }\nconst removeHeartbeatTaskForRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    state.rooms[ action.roomName ].tasks.heartbeat = null;\n    delete state.rooms[ action.roomName ].tasks.heartbeat;\n    return state;\n};\n\n/*******************************************************************************\n* SPHERE REDUCER FUNCTIONS\n*******************************************************************************/\n\n// - called via redux-action from message-sagas:createSphereAtPositionInRoom\n// action: { newSphereId, position, tone, roomName }\nconst addSphereOfToneAtPositionInRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    try {\n        state.rooms[ action.roomName ].content.spheres[ action.newSphereId ] = makeSphereOfToneAtPosition(\n            action.tone,\n            action.position\n        );\n        return state;\n    }\n    catch( error ) {\n        logger.warn( `caught error trying to create new sphere: ${error.message}` );\n    }\n};\n\n// - called via redux-action from message-sagas:updateClientCoords\n// - spheres in state are sent direct to clients, so use const labels\n// - action properties are fixed rather than using messageConstants\n// - sphere.position is a coordinates object as per message schema\n// action: { position, sphereId, roomName }\nconst setPositionForSphereInRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.spheres[ action.sphereId ] === 'undefined' ) {\n        return state;\n    }\n\n    const positionLabel = messageConstants.INCOMING_MESSAGE_COMPONENTS.UPDATE_CLIENT_COORDS.SPHERE_POSITION;\n\n    state.rooms[ action.roomName ].content.spheres[ action.sphereId ][ positionLabel ] = action.position;\n\n    if( state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold ) {\n        state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold.ts = Date.now();\n    }\n\n    return state;\n};\n\n// - called via redux-action from message-sagas:setSphereConnections\n// - spheres in state are sent direct to clients, so use const labels\n// - action properties are fixed rather than using messageConstants\n// action: { connections, sphereId, roomName }\nconst setConnectionsForSphereInRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.spheres[ action.sphereId ] === 'undefined' ) {\n        return state;\n    }\n\n    const connectionsLabel = messageConstants.INCOMING_MESSAGE_COMPONENTS.SET_SPHERE_CONNECTIONS.CONNECTIONS;\n\n    state.rooms[ action.roomName ].content.spheres[ action.sphereId ][ connectionsLabel ] = action.connections;\n\n    if( state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold ) {\n        state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold.ts = Date.now();\n    }\n\n    return state;\n};\n\n// - called via redux-action from message-sagas:setSphereTone\n// - spheres in state are sent direct to clients, so use const labels\n// - action properties are fixed rather than using messageConstants\n// action: { tone, sphereId, roomName }\nconst setToneForSphereInRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.spheres[ action.sphereId ] === 'undefined' ) {\n        return state;\n    }\n\n    const toneLabel = messageConstants.INCOMING_MESSAGE_COMPONENTS.SET_SPHERE_TONE.TONE;\n\n    state.rooms[ action.roomName ].content.spheres[ action.sphereId ][ toneLabel ] = action.tone;\n\n    if( state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold ) {\n        state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold.ts = Date.now();\n    }\n\n    return state;\n};\n\n// - called via redux-action from message-sagas:grabSphere\n// action: { sphereId, clientId, roomName }\nconst createHoldOnSphereForClientInRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.spheres[ action.sphereId ] === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.clients[ action.clientId ] === 'undefined' ) {\n        return state;\n    }\n\n    // set a hold property on the sphere\n    state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold = {\n        clientId: action.clientId,\n        ts:         Date.now()\n    };\n\n    // mark the client as holding this sphere\n    state.rooms[ action.roomName ].content.clients[ action.clientId ].spheresHeld[ action.sphereId ] = true;\n\n    return state;\n};\n\n// - called from room-sagas:startSphereHoldTimeout\n// - triggered when a sphere hold is created\n// action: { timeoutTask, sphereId, roomName }\nconst setHoldTimeoutTaskForSphereInRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.spheres[ action.sphereId ] === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold === 'undefined' ) {\n        return state;\n    }\n\n    state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold.timeoutTask = action.timeoutTask;\n\n    return state;\n};\n\n// action: { sphereId, roomName }\nconst deleteHoldTimeoutTaskForSphereInRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.spheres[ action.sphereId ] === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold === 'undefined' ) {\n        return state;\n    }\n\n    state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold.timeoutTask = null;\n    delete state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold.timeoutTask;\n\n    return state;\n};\n\n// - called via redux-action from message-sagas:releaseSphere\n// action: { sphereId, clientId, roomName }\nconst removeHoldOnSphereForClientInRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.spheres[ action.sphereId ] === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.clients[ action.clientId ] === 'undefined' ) {\n        return state;\n    }\n\n    if( state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold.clientId == action.clientId ) {\n        // release the sphere hold so other clients can grab it\n        state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold = null;\n        delete state.rooms[ action.roomName ].content.spheres[ action.sphereId ].hold;\n\n        // disassociate the sphere from the client so it can't update it any more\n        state.rooms[ action.roomName ].content.clients[ action.clientId ].spheresHeld[ action.sphereId ] = null;\n        delete state.rooms[ action.roomName ].content.clients[ action.clientId ].spheresHeld[ action.sphereId ];\n    }\n\n    return state;\n};\n\n// - called via redux-action from message-sagas:deleteSphere\n// action: { sphereId, roomName }\nconst deleteSphereFromRoom = ( state, action ) => {\n\n    if( typeof action.roomName === 'undefined' ) {\n        return state;\n    }\n\n    if( typeof state.rooms[ action.roomName ].content.spheres[ action.sphereId ] === 'undefined' ) {\n        return state;\n    }\n\n    state.rooms[ action.roomName ].content.spheres[ action.sphereId ] = null;\n    delete state.rooms[ action.roomName ].content.spheres[ action.sphereId ];\n\n    return state;\n};\n\nconst actionHandlers = {\n\n    // room setup/teardown\n    [ roomStateConstants.EVENT_TYPES.INIT_ROOM_CONTENT ]:                       initRoomContent,\n    [ roomDataConstants.ACTION_TYPES.ADD_WEBSOCKET_TO_QUEUE_FOR_ROOM ]:         addWebsocketToQueueForRoom,\n    [ roomDataConstants.ACTION_TYPES.REMOVE_WEBSOCKET_FROM_QUEUE_FOR_ROOM ]:    removeWebsocketFromQueueForRoom,\n    [ roomDataConstants.ACTION_TYPES.ADD_LOCAL_CLIENT_TO_ROOM ]:                addLocalClientToRoom,\n    [ roomDataConstants.ACTION_TYPES.REMOVE_LOCAL_CLIENT_FROM_ROOM ]:           removeLocalClientFromRoom,\n\n    [ roomDataConstants.ACTION_TYPES.UPDATE_LAST_HEARTBEAT_FOR_ROOM ]:          setLastHeartbeatCountForRoom,\n    [ roomDataConstants.ACTION_TYPES.SET_HEARTBEAT_TASK_FOR_ROOM ]:             setHeartbeatTaskForRoom,\n    [ roomDataConstants.ACTION_TYPES.REMOVE_HEARTBEAT_TASK_FOR_ROOM ]:          removeHeartbeatTaskForRoom,\n\n    // sphere state actions\n    [ roomDataConstants.ACTION_TYPES.ADD_SPHERE_OF_TONE_AT_POSITION_IN_ROOM ]:      addSphereOfToneAtPositionInRoom,\n    [ roomDataConstants.ACTION_TYPES.SET_POSITION_FOR_SPHERE_IN_ROOM ]:             setPositionForSphereInRoom,\n    [ roomDataConstants.ACTION_TYPES.SET_TONE_FOR_SPHERE_IN_ROOM ]:                 setToneForSphereInRoom,\n    [ roomDataConstants.ACTION_TYPES.SET_CONNECTIONS_FOR_SPHERE_IN_ROOM ]:          setConnectionsForSphereInRoom,\n    [ roomDataConstants.ACTION_TYPES.CREATE_HOLD_ON_SPHERE_FOR_CLIENT_IN_ROOM ]:    createHoldOnSphereForClientInRoom,\n    [ roomDataConstants.ACTION_TYPES.SET_HOLD_TIMEOUT_TASK_FOR_SPHERE_IN_ROOM ]:    setHoldTimeoutTaskForSphereInRoom,\n    [ roomDataConstants.ACTION_TYPES.DELETE_HOLD_TIMEOUT_TASK_FOR_SPHERE_IN_ROOM ]: deleteHoldTimeoutTaskForSphereInRoom,\n    [ roomDataConstants.ACTION_TYPES.REMOVE_HOLD_ON_SPHERE_FOR_CLIENT_IN_ROOM ]:    removeHoldOnSphereForClientInRoom,\n    [ roomDataConstants.ACTION_TYPES.DELETE_SPHERE_FROM_ROOM ]:                     deleteSphereFromRoom\n\n};\n\nexport default createFilteredActionHandler( actionHandlers, initialState );\n"
  },
  {
    "path": "backend/src/rooms/room-names.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst roomNames = [\"aaag\",\"aace\",\"aadx\",\"aafo\",\"aafx\",\"aalu\",\"aamd\",\"aamm\",\"aany\",\"aaoc\",\"aarm\",\"aasm\",\"aavz\",\"aawl\",\"aaxz\",\"aayr\",\"aayz\",\"abbo\",\"abip\",\"abji\",\"ablh\",\"ablk\",\"ablo\",\"abod\",\"abrn\",\"abrw\",\"abux\",\"abvo\",\"abvt\",\"abvv\",\"abyi\",\"accr\",\"acdc\",\"acdr\",\"aceq\",\"acgv\",\"achk\",\"acjo\",\"acnh\",\"acod\",\"acpe\",\"acso\",\"actg\",\"acwb\",\"acwy\",\"acxg\",\"adao\",\"adfx\",\"adga\",\"adgo\",\"adgw\",\"adjg\",\"adqo\",\"adtg\",\"advp\",\"advy\",\"adyp\",\"aebs\",\"aech\",\"aefp\",\"aehz\",\"aejm\",\"aemd\",\"aemk\",\"aenx\",\"aeoh\",\"aeqk\",\"aeqm\",\"aerd\",\"aerf\",\"aerj\",\"aetn\",\"aevs\",\"aevv\",\"aewp\",\"aexv\",\"afaq\",\"afdf\",\"affs\",\"afig\",\"afla\",\"aflg\",\"aflx\",\"afqs\",\"afrx\",\"afux\",\"afva\",\"afvx\",\"afwc\",\"afzr\",\"agfw\",\"aggl\",\"agjc\",\"agkt\",\"agkz\",\"aglr\",\"agsr\",\"agss\",\"agtk\",\"agus\",\"agvu\",\"agxz\",\"ahai\",\"ahbh\",\"ahen\",\"ahjz\",\"ahki\",\"ahlk\",\"ahmb\",\"ahnn\",\"ahyu\",\"ahzn\",\"ahzu\",\"aiao\",\"aibs\",\"aibu\",\"aicj\",\"aidl\",\"aigj\",\"aiia\",\"aikm\",\"aima\",\"aiml\",\"aimr\",\"aino\",\"aiok\",\"aiss\",\"aiuo\",\"aiut\",\"aivn\",\"aivy\",\"aiwa\",\"aixt\",\"aizp\",\"ajaa\",\"ajam\",\"ajbj\",\"ajdn\",\"ajdp\",\"ajhp\",\"ajka\",\"ajxi\",\"ajyp\",\"ajzh\",\"ajzq\",\"akam\",\"akdl\",\"aked\",\"akgr\",\"akgt\",\"akpo\",\"akpz\",\"aksr\",\"akvz\",\"akwp\",\"akwq\",\"akxc\",\"alac\",\"alak\",\"alaw\",\"alca\",\"alcn\",\"alfk\",\"alie\",\"aljw\",\"alkg\",\"allc\",\"almr\",\"aloq\",\"alor\",\"alpa\",\"alqt\",\"altq\",\"alua\",\"alub\",\"alwf\",\"alwj\",\"alyf\",\"amaw\",\"amcj\",\"amfk\",\"amoy\",\"ampu\",\"amqj\",\"amtj\",\"amxj\",\"andt\",\"anet\",\"aneu\",\"anfm\",\"angl\",\"anhj\",\"anjf\",\"anmx\",\"annr\",\"anoq\",\"anov\",\"anqm\",\"ansp\",\"anyd\",\"anzn\",\"aobu\",\"aofe\",\"aogj\",\"aogm\",\"aohx\",\"aoka\",\"aonv\",\"aopv\",\"aoqq\",\"aorg\",\"aote\",\"aoyn\",\"apbv\",\"apdy\",\"aphv\",\"apjq\",\"apjx\",\"apkk\",\"apkp\",\"apne\",\"appd\",\"appl\",\"appr\",\"aptc\",\"apzr\",\"aqbr\",\"aqcc\",\"aqcn\",\"aqdm\",\"aqfw\",\"aqgp\",\"aqgy\",\"aqht\",\"aqhx\",\"aqkv\",\"aqln\",\"aqpf\",\"aqpx\",\"aqra\",\"aqrd\",\"aqsr\",\"aqvu\",\"argg\",\"argh\",\"ariq\",\"arli\",\"armq\",\"arny\",\"arrs\",\"arsy\",\"arum\",\"aryo\",\"arzg\",\"arzq\",\"asgc\",\"asit\",\"asiw\",\"aske\",\"asma\",\"asmh\",\"asnm\",\"aspb\",\"asrs\",\"asth\",\"asul\",\"asvi\",\"asvk\",\"aszk\",\"atbm\",\"atds\",\"atec\",\"atgx\",\"athr\",\"atkt\",\"atno\",\"atph\",\"atql\",\"atws\",\"auao\",\"aufd\",\"auhe\",\"auja\",\"aujp\",\"auof\",\"ausw\",\"auux\",\"auvq\",\"avag\",\"avas\",\"avfs\",\"avgv\",\"avix\",\"avnu\",\"avqe\",\"avug\",\"avvr\",\"avvv\",\"avwl\",\"avzb\",\"awbu\",\"awdi\",\"awex\",\"awfg\",\"awhx\",\"awir\",\"awki\",\"awku\",\"awmn\",\"awog\",\"awpt\",\"awrf\",\"awrw\",\"awsh\",\"awvz\",\"awze\",\"awzx\",\"axdh\",\"axlp\",\"axne\",\"axpp\",\"axsi\",\"axtj\",\"axuj\",\"axvr\",\"axxg\",\"axzc\",\"ayfj\",\"ayfq\",\"ayim\",\"ayio\",\"ayjx\",\"aymf\",\"aymu\",\"ayqs\",\"ayry\",\"ayxm\",\"ayzk\",\"ayzx\",\"azbx\",\"azex\",\"azfm\",\"azlq\",\"azlu\",\"azqt\",\"azsm\",\"aztu\",\"azvz\",\"azxd\",\"azxr\",\"azyn\",\"babg\",\"bafj\",\"bakq\",\"bamh\",\"bana\",\"batr\",\"bawg\",\"bazd\",\"bbag\",\"bbct\",\"bbgw\",\"bbhn\",\"bbkj\",\"bbmt\",\"bbog\",\"bbow\",\"bbqh\",\"bbqi\",\"bbqk\",\"bbsg\",\"bbtb\",\"bbte\",\"bbuz\",\"bbwv\",\"bbxn\",\"bbxr\",\"bbyp\",\"bcab\",\"bcae\",\"bcay\",\"bcde\",\"bcfb\",\"bcgj\",\"bchj\",\"bckq\",\"bcll\",\"bcmg\",\"bcoj\",\"bcoq\",\"bcpa\",\"bcuz\",\"bcvw\",\"bcwk\",\"bcwq\",\"bcxh\",\"bdcd\",\"bddt\",\"bddv\",\"bdgd\",\"bdia\",\"bdix\",\"bdju\",\"bdkp\",\"bdlt\",\"bdlu\",\"bdqf\",\"bdtb\",\"bdtv\",\"bdun\",\"beax\",\"bebk\",\"bebu\",\"bedb\",\"bede\",\"befw\",\"behg\",\"behh\",\"beib\",\"beke\",\"belr\",\"bert\",\"bete\",\"beus\",\"beut\",\"beve\",\"beyb\",\"beyj\",\"bezh\",\"bfar\",\"bfbo\",\"bfbx\",\"bfdh\",\"bfdk\",\"bfdr\",\"bfds\",\"bfed\",\"bfhc\",\"bfjt\",\"bfps\",\"bfri\",\"bfuy\",\"bfxc\",\"bfzu\",\"bgak\",\"bgbr\",\"bgcf\",\"bgdd\",\"bgen\",\"bgge\",\"bghh\",\"bghl\",\"bghq\",\"bgjb\",\"bgrh\",\"bgwe\",\"bgwp\",\"bgwr\",\"bgwv\",\"bgzv\",\"bhhx\",\"bhie\",\"bhlz\",\"bhrj\",\"bhub\",\"bhzr\",\"bhzt\",\"bhzy\",\"biam\",\"bica\",\"bidp\",\"biej\",\"bifd\",\"bigz\",\"bilu\",\"bimx\",\"biqa\",\"bitq\",\"biuw\",\"biwe\",\"biwp\",\"bjaa\",\"bjen\",\"bjey\",\"bjgd\",\"bjgk\",\"bjhc\",\"bjhi\",\"bjhx\",\"bjia\",\"bjid\",\"bjiq\",\"bjkn\",\"bjoa\",\"bjph\",\"bjpm\",\"bjrd\",\"bjri\",\"bjrv\",\"bjuu\",\"bjyh\",\"bjyu\",\"bjzw\",\"bkfy\",\"bkoy\",\"bkpr\",\"bkqq\",\"bksm\",\"bkum\",\"bkvh\",\"bkvs\",\"bkxi\",\"blbz\",\"blct\",\"blej\",\"blfo\",\"blhg\",\"bljl\",\"blkj\",\"blkq\",\"bllk\",\"blnk\",\"blqm\",\"blsj\",\"blsu\",\"bltm\",\"bltt\",\"blwu\",\"blxi\",\"blxx\",\"blyk\",\"blyp\",\"blzd\",\"bmbc\",\"bmbe\",\"bmdl\",\"bmef\",\"bmeh\",\"bmgz\",\"bmjr\",\"bmly\",\"bmmu\",\"bmpv\",\"bmqc\",\"bmqm\",\"bmrn\",\"bmry\",\"bmst\",\"bmtu\",\"bmzj\",\"bnai\",\"bnal\",\"bnat\",\"bncf\",\"bnfd\",\"bnfv\",\"bnhw\",\"bnhz\",\"bnmo\",\"bnnb\",\"bnqi\",\"bnqk\",\"bnrg\",\"bnrp\",\"bnsl\",\"bntl\",\"bnto\",\"bnxe\",\"bnxm\",\"bnzt\",\"bobm\",\"bokn\",\"bolr\",\"bonm\",\"boov\",\"boqb\",\"bosd\",\"botr\",\"boub\",\"bpcz\",\"bpjn\",\"bpkp\",\"bpkz\",\"bpll\",\"bpmw\",\"bpov\",\"bpsl\",\"bpwk\",\"bpxb\",\"bpyp\",\"bqbs\",\"bqbz\",\"bqef\",\"bqgi\",\"bqia\",\"bqid\",\"bqil\",\"bqke\",\"bqlf\",\"bqml\",\"bqql\",\"bqqp\",\"bqqs\",\"bqqv\",\"bqru\",\"bqst\",\"bquz\",\"bqvw\",\"bqwm\",\"bqws\",\"bqxl\",\"bqxn\",\"bqxv\",\"bqxw\",\"brgy\",\"briv\",\"brkk\",\"brld\",\"brlj\",\"brnw\",\"bruv\",\"brvg\",\"brvu\",\"bryb\",\"bsat\",\"bsdl\",\"bshd\",\"bsjy\",\"bsll\",\"bslw\",\"bsmg\",\"bsns\",\"bsog\",\"bsqj\",\"bssd\",\"bssq\",\"bsst\",\"bswe\",\"btcs\",\"btdp\",\"btez\",\"btfn\",\"btgw\",\"btid\",\"btij\",\"btir\",\"btnw\",\"btps\",\"btpz\",\"bttf\",\"btuh\",\"btxj\",\"btzf\",\"buao\",\"bubm\",\"bucf\",\"bucj\",\"budu\",\"buen\",\"buge\",\"bugw\",\"buhx\",\"buiv\",\"bulw\",\"bumw\",\"buoo\",\"buop\",\"buqy\",\"busg\",\"buxg\",\"bvar\",\"bvhx\",\"bvii\",\"bvkg\",\"bvkh\",\"bvki\",\"bvpu\",\"bvqa\",\"bvxd\",\"bvxj\",\"bvxv\",\"bvxx\",\"bvzh\",\"bwda\",\"bwei\",\"bwfg\",\"bwgu\",\"bwhz\",\"bwko\",\"bwkt\",\"bwle\",\"bwof\",\"bwpi\",\"bwqw\",\"bwqx\",\"bwto\",\"bwtx\",\"bwxp\",\"bxad\",\"bxdb\",\"bxdj\",\"bxep\",\"bxfq\",\"bxha\",\"bxjp\",\"bxjq\",\"bxjr\",\"bxkn\",\"bxly\",\"bxnd\",\"bxnp\",\"bxsz\",\"bxxe\",\"bxxn\",\"bxzf\",\"bxzm\",\"bxzq\",\"bybo\",\"bycs\",\"byjb\",\"byju\",\"byly\",\"bynh\",\"bypp\",\"byrr\",\"byrv\",\"bysd\",\"bytt\",\"byuo\",\"byxi\",\"bzam\",\"bzaq\",\"bzbp\",\"bzcw\",\"bzgd\",\"bziq\",\"bzjo\",\"bzkh\",\"bzln\",\"bzms\",\"bznt\",\"bzoq\",\"bzpu\",\"bztq\",\"bztw\",\"bzwl\",\"cacc\",\"cahy\",\"caji\",\"cajp\",\"calo\",\"cani\",\"canp\",\"caof\",\"caot\",\"capd\",\"capx\",\"cban\",\"cbbs\",\"cbbv\",\"cbcm\",\"cbgf\",\"cbip\",\"cbkj\",\"cbmi\",\"cbmr\",\"cbmt\",\"cbok\",\"cbou\",\"cbsq\",\"cbwm\",\"cbxr\",\"cbyi\",\"cbzj\",\"ccav\",\"ccbl\",\"ccbz\",\"cccg\",\"cccj\",\"ccet\",\"ccfo\",\"ccfq\",\"ccgg\",\"cchg\",\"cchl\",\"cchr\",\"ccjp\",\"cclg\",\"ccoj\",\"ccor\",\"ccph\",\"ccpq\",\"ccrx\",\"ccry\",\"ccvq\",\"ccwt\",\"ccxb\",\"ccxq\",\"ccyk\",\"cdfy\",\"cdit\",\"cdjq\",\"cdjs\",\"cdml\",\"cdol\",\"cdpj\",\"cdpo\",\"cdpw\",\"cdra\",\"cdrk\",\"cdrz\",\"cdsr\",\"cdts\",\"cdvf\",\"cdvq\",\"cdxm\",\"cedc\",\"cedm\",\"cedt\",\"cedz\",\"cegt\",\"cein\",\"ceiv\",\"cejx\",\"ceoj\",\"cepq\",\"cerm\",\"ceun\",\"cevl\",\"cevv\",\"cewu\",\"ceyj\",\"cfbe\",\"cfit\",\"cfjc\",\"cflz\",\"cfol\",\"cfrs\",\"cfuh\",\"cfxt\",\"cfyp\",\"cfzr\",\"cgcg\",\"cgch\",\"cggt\",\"cgjn\",\"cgkh\",\"cgoo\",\"cgot\",\"cgpv\",\"cgqu\",\"cgsq\",\"cgsz\",\"cgye\",\"cgzl\",\"chbz\",\"chcy\",\"chdd\",\"chfo\",\"chgm\",\"chio\",\"chji\",\"chjt\",\"chjw\",\"chmh\",\"chnb\",\"chnm\",\"chpz\",\"chvy\",\"chxy\",\"chxz\",\"chyo\",\"cian\",\"ciby\",\"cidv\",\"cihr\",\"cijz\",\"cimy\",\"cinw\",\"cjai\",\"cjdd\",\"cjdl\",\"cjeu\",\"cjfe\",\"cjgb\",\"cjgh\",\"cjja\",\"cjmt\",\"cjsr\",\"cjtj\",\"cjuf\",\"cjva\",\"cjwo\",\"ckbc\",\"ckbu\",\"ckck\",\"ckdh\",\"ckhm\",\"ckjw\",\"ckjy\",\"ckkd\",\"ckks\",\"ckpk\",\"ckpw\",\"ckqk\",\"ckrx\",\"ckst\",\"cktn\",\"ckts\",\"ckvg\",\"ckxf\",\"ckzh\",\"clbx\",\"clcs\",\"clcu\",\"clej\",\"cleo\",\"clgn\",\"clhd\",\"clnx\",\"cloj\",\"clox\",\"clqf\",\"clsu\",\"cluu\",\"clxe\",\"cmbl\",\"cmeb\",\"cmgo\",\"cmkn\",\"cmlz\",\"cmnq\",\"cmpc\",\"cmpo\",\"cmta\",\"cmuv\",\"cmuw\",\"cmxd\",\"cmxe\",\"cmyw\",\"cngp\",\"cnhn\",\"cnhs\",\"cnih\",\"cnjj\",\"cnnh\",\"cnps\",\"cnpv\",\"cnqi\",\"cnse\",\"cnsm\",\"cnul\",\"cnxp\",\"cnxt\",\"codd\",\"codg\",\"coga\",\"cojk\",\"cojw\",\"cosp\",\"cotd\",\"cotz\",\"covb\",\"covl\",\"covm\",\"cowg\",\"cowj\",\"coyx\",\"cpan\",\"cpay\",\"cpbl\",\"cpcp\",\"cpfk\",\"cpgl\",\"cpgm\",\"cpgw\",\"cphw\",\"cpii\",\"cpjc\",\"cpjl\",\"cpls\",\"cpmm\",\"cpra\",\"cpre\",\"cprm\",\"cpst\",\"cqfl\",\"cqod\",\"cqos\",\"cqrh\",\"cqsm\",\"cqve\",\"cqvg\",\"cqwp\",\"cqwv\",\"cqzp\",\"crdr\",\"crhp\",\"crhz\",\"crir\",\"crit\",\"crkh\",\"crks\",\"crqz\",\"crsd\",\"cruw\",\"crxk\",\"cryj\",\"crzt\",\"crzx\",\"cscj\",\"csdb\",\"csfi\",\"csfv\",\"csgy\",\"csis\",\"cskc\",\"cskv\",\"csmi\",\"csnd\",\"csqn\",\"csqw\",\"cssg\",\"cstd\",\"cstl\",\"csug\",\"csvx\",\"cszn\",\"ctcv\",\"ctgb\",\"cthj\",\"ctmw\",\"ctpb\",\"ctpd\",\"ctpo\",\"ctqg\",\"ctto\",\"cttz\",\"ctuq\",\"ctvj\",\"ctvz\",\"ctwj\",\"ctxf\",\"ctxh\",\"ctxn\",\"ctyw\",\"ctzx\",\"cubo\",\"cuhb\",\"cuhj\",\"cuhp\",\"cuhy\",\"cujr\",\"cuqe\",\"cuqk\",\"cutl\",\"cuvq\",\"cuwx\",\"cuxy\",\"cuzk\",\"cuzq\",\"cvci\",\"cvcn\",\"cvdj\",\"cvdr\",\"cvft\",\"cvhr\",\"cvhu\",\"cvrr\",\"cvtv\",\"cvze\",\"cwdj\",\"cwee\",\"cwfv\",\"cwfw\",\"cwma\",\"cwmb\",\"cwph\",\"cwrz\",\"cwwk\",\"cwxw\",\"cxbm\",\"cxbt\",\"cxdv\",\"cxhv\",\"cxkb\",\"cxla\",\"cxlm\",\"cxmp\",\"cxoa\",\"cxpe\",\"cxsx\",\"cxvm\",\"cxvp\",\"cxzl\",\"cyau\",\"cybu\",\"cycg\",\"cydd\",\"cydh\",\"cydl\",\"cydu\",\"cygg\",\"cyhe\",\"cyhw\",\"cykl\",\"cyoi\",\"cytc\",\"cytr\",\"cyxs\",\"cyyg\",\"cyyp\",\"czay\",\"czdv\",\"czfa\",\"czfo\",\"czfp\",\"cziv\",\"czkg\",\"czku\",\"czle\",\"czlx\",\"cznd\",\"cznx\",\"czpw\",\"czse\",\"czwc\",\"czws\",\"czxd\",\"czzk\",\"czzu\",\"czzz\",\"dacv\",\"danx\",\"dapq\",\"daqk\",\"daqt\",\"daso\",\"dasp\",\"dasv\",\"dawl\",\"dawv\",\"daxc\",\"daxd\",\"dazj\",\"dbbs\",\"dbgi\",\"dbox\",\"dbrf\",\"dbsd\",\"dbvq\",\"dbxk\",\"dbzf\",\"dcax\",\"dcct\",\"dcee\",\"dcer\",\"dcic\",\"dcin\",\"dciq\",\"dckg\",\"dclz\",\"dcnd\",\"dcok\",\"dcoq\",\"dcpp\",\"dcrn\",\"dcuh\",\"dcul\",\"dcvd\",\"dcwn\",\"dcxb\",\"dczk\",\"ddav\",\"dddj\",\"ddfc\",\"ddjo\",\"ddmr\",\"ddof\",\"ddqv\",\"ddro\",\"ddtb\",\"dduu\",\"ddyq\",\"ddzd\",\"debp\",\"debs\",\"decw\",\"defg\",\"degt\",\"dehl\",\"dejb\",\"dely\",\"demx\",\"deoe\",\"deov\",\"deph\",\"derc\",\"deup\",\"deur\",\"deuw\",\"dewf\",\"dfba\",\"dfcg\",\"dfdn\",\"dfgt\",\"dfiz\",\"dfjx\",\"dfjy\",\"dfkj\",\"dfko\",\"dfli\",\"dfsy\",\"dftj\",\"dftk\",\"dftl\",\"dgaq\",\"dgcq\",\"dgfl\",\"dgii\",\"dgjl\",\"dgjq\",\"dgkg\",\"dgmc\",\"dgnm\",\"dgpt\",\"dgqt\",\"dgsr\",\"dgzq\",\"dhas\",\"dhbq\",\"dhff\",\"dhfh\",\"dhfx\",\"dhhd\",\"dhkk\",\"dhlq\",\"dhlv\",\"dhrt\",\"dhss\",\"dhus\",\"dhyu\",\"dhyy\",\"dico\",\"dihg\",\"diia\",\"dijk\",\"diki\",\"dikm\",\"dimm\",\"dior\",\"dirg\",\"diua\",\"diue\",\"diuu\",\"dixd\",\"dixg\",\"djal\",\"djbz\",\"djfc\",\"djfr\",\"djfy\",\"djhe\",\"djie\",\"djll\",\"djlp\",\"djmb\",\"djmf\",\"djnm\",\"djop\",\"djqd\",\"djse\",\"djsm\",\"djuv\",\"djvz\",\"djwi\",\"djyn\",\"dkae\",\"dkbq\",\"dkel\",\"dkic\",\"dkjd\",\"dkjv\",\"dklq\",\"dkmc\",\"dknz\",\"dksn\",\"dksp\",\"dkun\",\"dkvr\",\"dkwf\",\"dlci\",\"dlkm\",\"dlky\",\"dlmt\",\"dlof\",\"dlqa\",\"dlrl\",\"dlwt\",\"dlyt\",\"dlzc\",\"dmby\",\"dmcu\",\"dmjw\",\"dmmx\",\"dmrh\",\"dmtj\",\"dmtk\",\"dmui\",\"dmum\",\"dmuy\",\"dmwq\",\"dmxi\",\"dmxp\",\"dmxs\",\"dnad\",\"dnbs\",\"dnhn\",\"dnhw\",\"dnic\",\"dnjt\",\"dnlu\",\"dnoa\",\"dnod\",\"dnon\",\"dnor\",\"dnpb\",\"dnph\",\"dnqu\",\"dnsp\",\"dnsr\",\"dnua\",\"dnuz\",\"doam\",\"dobn\",\"doeb\",\"dohc\",\"domo\",\"donn\",\"doqv\",\"doue\",\"dovc\",\"dowx\",\"doxz\",\"dozc\",\"dpbj\",\"dpez\",\"dpfp\",\"dpgj\",\"dpif\",\"dpkv\",\"dpoi\",\"dprs\",\"dpsm\",\"dptw\",\"dpvi\",\"dpwd\",\"dpwh\",\"dpwm\",\"dpxq\",\"dqay\",\"dqbk\",\"dqbx\",\"dqdh\",\"dqeg\",\"dqgl\",\"dqhz\",\"dqie\",\"dqka\",\"dqlo\",\"dqnp\",\"dqns\",\"dqom\",\"dqqc\",\"dqrf\",\"dqrk\",\"dqsn\",\"dqtd\",\"dqtu\",\"dquh\",\"dqul\",\"dqum\",\"dqwl\",\"dqxb\",\"dqxp\",\"dqzq\",\"dqzv\",\"drbh\",\"drgj\",\"drgm\",\"drgp\",\"drgv\",\"drjz\",\"drnl\",\"drpv\",\"drrn\",\"drtf\",\"dryy\",\"drzc\",\"drzf\",\"drzx\",\"dsad\",\"dsaj\",\"dsak\",\"dscn\",\"dsdy\",\"dsey\",\"dsfy\",\"dsif\",\"dsjm\",\"dsla\",\"dsld\",\"dsna\",\"dspm\",\"dsty\",\"dswe\",\"dsxu\",\"dsyt\",\"dtej\",\"dtfi\",\"dtfj\",\"dtgo\",\"dtio\",\"dtiu\",\"dtji\",\"dtjz\",\"dtlg\",\"dtmh\",\"dtmk\",\"dtml\",\"dtwb\",\"dtwm\",\"dtza\",\"duab\",\"dudr\",\"dueu\",\"dugy\",\"dulx\",\"dunz\",\"duom\",\"duph\",\"dupl\",\"duto\",\"duws\",\"duxd\",\"duyo\",\"duys\",\"dvad\",\"dvck\",\"dvev\",\"dvip\",\"dvlh\",\"dvli\",\"dvmq\",\"dvmt\",\"dvns\",\"dvrv\",\"dvvb\",\"dvwg\",\"dvxn\",\"dvxo\",\"dvyn\",\"dwbb\",\"dweu\",\"dwfe\",\"dwfh\",\"dwfy\",\"dwgz\",\"dwho\",\"dwhp\",\"dwlf\",\"dwol\",\"dwqc\",\"dwqj\",\"dwrs\",\"dwsf\",\"dwsx\",\"dwtx\",\"dwus\",\"dwuw\",\"dwzr\",\"dwzw\",\"dxcc\",\"dxdc\",\"dxdd\",\"dxdn\",\"dxef\",\"dxfe\",\"dxiq\",\"dxla\",\"dxnp\",\"dxoe\",\"dxpj\",\"dxpx\",\"dxta\",\"dxxk\",\"dxzw\",\"dyaq\",\"dyba\",\"dycw\",\"dyeh\",\"dyfb\",\"dyix\",\"dykq\",\"dykv\",\"dymt\",\"dyoi\",\"dyqh\",\"dyqs\",\"dysk\",\"dyyn\",\"dyzu\",\"dzbu\",\"dzfo\",\"dzjy\",\"dzlr\",\"dzms\",\"dznd\",\"dzoq\",\"dzps\",\"dzqg\",\"dzqy\",\"dzrc\",\"dzvi\",\"dzvl\",\"dzwd\",\"eada\",\"eadf\",\"eaeq\",\"eaez\",\"eaff\",\"eagf\",\"eahw\",\"eaio\",\"eajs\",\"ealq\",\"eanh\",\"eank\",\"eaps\",\"eaxv\",\"eazg\",\"eazz\",\"ebaz\",\"ebdi\",\"ebgj\",\"ebkd\",\"ebkl\",\"eblr\",\"ebmj\",\"ebnw\",\"ebob\",\"eboz\",\"ebsh\",\"ebzn\",\"ecbq\",\"echi\",\"ecja\",\"eckz\",\"ecmz\",\"ecsx\",\"ecwj\",\"edal\",\"ediz\",\"edme\",\"edmf\",\"edoh\",\"edtg\",\"edtx\",\"edwy\",\"edxp\",\"eeaw\",\"eeew\",\"eejr\",\"eelb\",\"eelt\",\"eelz\",\"eene\",\"eepv\",\"eepz\",\"eeqy\",\"eesj\",\"eesn\",\"eeso\",\"eetw\",\"eewc\",\"eeyn\",\"eeys\",\"eezm\",\"effl\",\"efhl\",\"efhn\",\"efhp\",\"efhx\",\"efjq\",\"efkn\",\"eflx\",\"efmq\",\"efnl\",\"efnp\",\"efqn\",\"efsn\",\"efun\",\"egas\",\"egeu\",\"eghv\",\"egik\",\"egiv\",\"egkp\",\"eglw\",\"egpb\",\"egrb\",\"egro\",\"egxn\",\"egye\",\"ehdg\",\"ehgg\",\"ehhh\",\"ehhq\",\"ehih\",\"ehiu\",\"ehjq\",\"ehnv\",\"ehoe\",\"ehuw\",\"ehwk\",\"ehyq\",\"eiba\",\"eicd\",\"eict\",\"eief\",\"eifq\",\"eige\",\"eigf\",\"eigh\",\"eiix\",\"eije\",\"eikc\",\"eils\",\"eilz\",\"eiok\",\"eisv\",\"eivn\",\"eivv\",\"eiwk\",\"ejab\",\"ejad\",\"ejag\",\"ejbd\",\"ejbk\",\"ejcn\",\"ejdq\",\"ejds\",\"ejeb\",\"ejex\",\"ejfl\",\"ejgz\",\"ejjx\",\"ejkj\",\"ejkq\",\"ejlc\",\"ejne\",\"ejos\",\"ejtl\",\"ejul\",\"ejvv\",\"ejyl\",\"ekes\",\"ekkh\",\"ekku\",\"ekla\",\"ekml\",\"ekne\",\"ekrq\",\"ekru\",\"eksl\",\"ekvp\",\"ekxe\",\"ekyl\",\"elac\",\"elch\",\"elcn\",\"elhp\",\"elmj\",\"elmp\",\"elrx\",\"elsj\",\"eltr\",\"elub\",\"elxf\",\"elyj\",\"embq\",\"emds\",\"emgs\",\"emvs\",\"emwy\",\"emzi\",\"emzr\",\"enao\",\"endc\",\"endi\",\"enfy\",\"enhz\",\"enix\",\"enkt\",\"enlm\",\"enmc\",\"enmq\",\"enuh\",\"enuo\",\"enup\",\"enyw\",\"eoat\",\"eock\",\"eocp\",\"eocu\",\"eoeg\",\"eogt\",\"eojc\",\"eolw\",\"eomg\",\"eoqw\",\"eowb\",\"eoxy\",\"eoyk\",\"eozn\",\"epao\",\"epcs\",\"epda\",\"epeo\",\"epey\",\"epfw\",\"epke\",\"eplx\",\"eppa\",\"eppy\",\"epre\",\"epth\",\"epuw\",\"epzw\",\"eqan\",\"eqbg\",\"eqcj\",\"eqgd\",\"eqjb\",\"eqln\",\"eqmt\",\"eqmu\",\"eqmz\",\"eqpu\",\"eqpz\",\"eqra\",\"eqxd\",\"eqxt\",\"eqye\",\"eqyw\",\"eqza\",\"eqzx\",\"erfo\",\"erkt\",\"erlj\",\"erqf\",\"erue\",\"erxj\",\"esbn\",\"eseu\",\"esfg\",\"esfh\",\"eshb\",\"esht\",\"esip\",\"eslr\",\"eslz\",\"esnn\",\"esnx\",\"espz\",\"esty\",\"esvh\",\"eswb\",\"esxe\",\"esxo\",\"esya\",\"etcj\",\"etfr\",\"etgd\",\"etgz\",\"ethd\",\"etiq\",\"etpe\",\"etqi\",\"etwr\",\"eudo\",\"euhq\",\"euia\",\"euiq\",\"eumq\",\"euny\",\"euon\",\"euos\",\"eurb\",\"eurh\",\"eutk\",\"euui\",\"euul\",\"euus\",\"euve\",\"euvk\",\"euwc\",\"euyb\",\"euyk\",\"euzb\",\"euzp\",\"euzw\",\"evcb\",\"evfp\",\"evgl\",\"evgy\",\"evif\",\"evlr\",\"evnf\",\"evqe\",\"evrn\",\"evza\",\"ewae\",\"ewat\",\"ewbe\",\"ewiz\",\"ewju\",\"ewki\",\"ewlv\",\"ewmi\",\"ewnh\",\"ewni\",\"ewot\",\"ewpc\",\"ewrg\",\"ewrz\",\"ewxq\",\"ewyo\",\"ewzk\",\"exat\",\"exbk\",\"excb\",\"exeo\",\"exfd\",\"exfw\",\"exgj\",\"exin\",\"exjg\",\"exjt\",\"exmd\",\"exmh\",\"exni\",\"expd\",\"exsc\",\"exuu\",\"exyg\",\"eyba\",\"eycb\",\"eydm\",\"eydr\",\"eyft\",\"eyhw\",\"eyih\",\"eyjf\",\"eyll\",\"eynu\",\"eyoa\",\"eypv\",\"eytm\",\"eyue\",\"eyvj\",\"eyvp\",\"eywh\",\"eyxo\",\"ezee\",\"ezkf\",\"ezli\",\"ezma\",\"ezqy\",\"ezxx\",\"ezyj\",\"ezyv\",\"fadm\",\"fahl\",\"falb\",\"fals\",\"fasy\",\"fato\",\"fatz\",\"faue\",\"fazl\",\"fbey\",\"fbfz\",\"fbga\",\"fbgf\",\"fbjj\",\"fbju\",\"fbol\",\"fbon\",\"fbps\",\"fbqx\",\"fbtk\",\"fbtt\",\"fbue\",\"fbxz\",\"fbzv\",\"fcai\",\"fcfj\",\"fchx\",\"fcin\",\"fcja\",\"fckz\",\"fclb\",\"fclv\",\"fcmq\",\"fcmt\",\"fcos\",\"fcss\",\"fcta\",\"fcux\",\"fcvc\",\"fcyk\",\"fczk\",\"fddj\",\"fdgl\",\"fdgv\",\"fdhi\",\"fdkm\",\"fdkw\",\"fdnd\",\"fdnq\",\"fdpg\",\"fdqi\",\"fdsy\",\"fdwj\",\"fdwr\",\"fdxb\",\"fdxx\",\"fdzl\",\"fecn\",\"fedo\",\"feek\",\"fefw\",\"fegd\",\"fehi\",\"fekg\",\"fekp\",\"felp\",\"feno\",\"feon\",\"fers\",\"fetb\",\"fevj\",\"ffbu\",\"ffcr\",\"ffek\",\"ffhi\",\"ffrb\",\"ffsi\",\"ffsx\",\"ffta\",\"ffyc\",\"ffym\",\"ffyx\",\"ffzt\",\"fgcm\",\"fgdj\",\"fgdt\",\"fgjq\",\"fglj\",\"fglw\",\"fgnh\",\"fgqk\",\"fgqn\",\"fgsw\",\"fgtm\",\"fgvj\",\"fgyo\",\"fhak\",\"fhax\",\"fhca\",\"fhcc\",\"fhdw\",\"fhej\",\"fhgf\",\"fhjf\",\"fhkj\",\"fhkq\",\"fhkr\",\"fhli\",\"fhlz\",\"fhmv\",\"fhor\",\"fhtn\",\"fhug\",\"fhul\",\"fhwb\",\"ficx\",\"fiej\",\"fies\",\"figk\",\"fiid\",\"finf\",\"fipr\",\"fiqh\",\"firt\",\"fitt\",\"fiuo\",\"fivq\",\"fiwe\",\"fixj\",\"fiyq\",\"fizn\",\"fjcb\",\"fjdr\",\"fjfj\",\"fjfl\",\"fjfo\",\"fjgt\",\"fjmw\",\"fjne\",\"fjqi\",\"fjrr\",\"fjrz\",\"fjwa\",\"fjxk\",\"fjxz\",\"fjyz\",\"fkbr\",\"fkez\",\"fkgz\",\"fkib\",\"fkjs\",\"fkkn\",\"fkns\",\"fkor\",\"fkph\",\"fkpk\",\"fkpn\",\"fkqx\",\"fkrc\",\"fkrk\",\"fktw\",\"fkup\",\"fkvh\",\"fkye\",\"fkzl\",\"flfw\",\"flke\",\"flkh\",\"flkm\",\"flll\",\"flma\",\"flmt\",\"flmy\",\"flrp\",\"fltn\",\"flvy\",\"flwh\",\"flyg\",\"flzl\",\"fmaq\",\"fmbi\",\"fmej\",\"fmgs\",\"fmin\",\"fmjv\",\"fmly\",\"fmmb\",\"fmmm\",\"fmmn\",\"fmnt\",\"fmph\",\"fmrb\",\"fmtu\",\"fmvd\",\"fmxj\",\"fnep\",\"fngp\",\"fnhb\",\"fnkr\",\"fnkz\",\"fnla\",\"fnme\",\"fnmu\",\"fnpa\",\"fnse\",\"fnsn\",\"fntv\",\"fnyn\",\"fnyr\",\"foax\",\"fobe\",\"fofx\",\"fogq\",\"fohr\",\"fohy\",\"fokr\",\"foks\",\"fokt\",\"foku\",\"folq\",\"fopy\",\"foqx\",\"forc\",\"fost\",\"fotv\",\"fovc\",\"fpac\",\"fpae\",\"fpat\",\"fpbs\",\"fpdp\",\"fpfx\",\"fpgn\",\"fpiu\",\"fpjb\",\"fpka\",\"fpmm\",\"fpov\",\"fppr\",\"fpqy\",\"fpre\",\"fprh\",\"fprz\",\"fpsh\",\"fpsl\",\"fqaa\",\"fqbv\",\"fqda\",\"fqdo\",\"fqfg\",\"fqie\",\"fqis\",\"fqiv\",\"fqjc\",\"fqje\",\"fqkx\",\"fqme\",\"fqnq\",\"fqoi\",\"fqux\",\"fqwg\",\"fqwy\",\"fqxo\",\"frbh\",\"frbi\",\"frcy\",\"frey\",\"frff\",\"frgy\",\"frhd\",\"frhf\",\"frkf\",\"frkm\",\"frkx\",\"frkz\",\"frmd\",\"frnj\",\"frnq\",\"frns\",\"frob\",\"froh\",\"frop\",\"froq\",\"frqv\",\"frrh\",\"frsd\",\"frsp\",\"frvv\",\"frwb\",\"frww\",\"frxa\",\"fryt\",\"fryu\",\"fsby\",\"fscj\",\"fsef\",\"fshm\",\"fsii\",\"fsjb\",\"fskh\",\"fskj\",\"fskw\",\"fsmu\",\"fspg\",\"fssu\",\"fswa\",\"fsxi\",\"ftbp\",\"ftff\",\"ftfj\",\"ftgm\",\"fthe\",\"fthn\",\"ftii\",\"ftoa\",\"ftrk\",\"ftsp\",\"ftvf\",\"ftwg\",\"fuas\",\"fubo\",\"fucx\",\"fucy\",\"fugl\",\"fugt\",\"fugy\",\"fuhj\",\"fukg\",\"fukq\",\"fulf\",\"fuok\",\"fusy\",\"fuud\",\"fuum\",\"fuwz\",\"fuyv\",\"fvbe\",\"fvcb\",\"fvdq\",\"fvfr\",\"fvfu\",\"fvgt\",\"fvid\",\"fvim\",\"fvkp\",\"fvmq\",\"fvox\",\"fvoz\",\"fvpp\",\"fvra\",\"fvrb\",\"fvrc\",\"fvso\",\"fvzo\",\"fwad\",\"fwap\",\"fwca\",\"fwdc\",\"fwds\",\"fwew\",\"fwud\",\"fwum\",\"fwuy\",\"fwvu\",\"fwxj\",\"fxdm\",\"fxen\",\"fxfh\",\"fxfi\",\"fxgt\",\"fxsc\",\"fxtw\",\"fybe\",\"fycl\",\"fycx\",\"fydr\",\"fyed\",\"fyeo\",\"fyhx\",\"fylt\",\"fyoq\",\"fyqb\",\"fyss\",\"fyuf\",\"fyvz\",\"fyyh\",\"fzbt\",\"fzdi\",\"fzig\",\"fzlo\",\"fzmi\",\"fznc\",\"fzni\",\"fznm\",\"fzpp\",\"fzre\",\"fzvi\",\"fzvm\",\"fzyi\",\"gaac\",\"gaaz\",\"gadn\",\"gaij\",\"gama\",\"gamt\",\"gaos\",\"gaow\",\"garj\",\"gasq\",\"gavk\",\"gavq\",\"gawg\",\"gbac\",\"gbdj\",\"gbez\",\"gbih\",\"gbkq\",\"gbld\",\"gblk\",\"gbnj\",\"gboe\",\"gboh\",\"gbvg\",\"gbwo\",\"gbwx\",\"gbye\",\"gbzf\",\"gcar\",\"gchv\",\"gcke\",\"gcln\",\"gclr\",\"gcmk\",\"gcpb\",\"gcrw\",\"gcvd\",\"gcvt\",\"gcwl\",\"gczh\",\"gdbs\",\"gdco\",\"gdfq\",\"gdlc\",\"gdnv\",\"gdrh\",\"gdsq\",\"gdta\",\"gdty\",\"gdyn\",\"gdyw\",\"gebf\",\"gecx\",\"gedg\",\"geew\",\"gefz\",\"gelr\",\"gelz\",\"genb\",\"geyh\",\"gfbi\",\"gfde\",\"gfdv\",\"gfge\",\"gfgx\",\"gfhv\",\"gfjh\",\"gfjz\",\"gflk\",\"gfsg\",\"gftp\",\"gfuq\",\"gfvt\",\"gfwv\",\"ggfb\",\"gghc\",\"gghk\",\"gghn\",\"gglm\",\"ggnb\",\"ggpp\",\"ggqx\",\"ggrg\",\"ggtw\",\"ggun\",\"ggvi\",\"ggwb\",\"ggzo\",\"ghas\",\"ghbf\",\"ghcc\",\"ghda\",\"ghdd\",\"ghdh\",\"ghko\",\"ghkq\",\"ghku\",\"ghlu\",\"ghmi\",\"ghpi\",\"ghvz\",\"ghxu\",\"ghys\",\"ghzz\",\"giai\",\"giaj\",\"gibx\",\"gicl\",\"gict\",\"gidc\",\"gifk\",\"gigd\",\"gijs\",\"giln\",\"gimi\",\"ginm\",\"gipq\",\"giub\",\"giux\",\"gjfe\",\"gjjn\",\"gjlm\",\"gjoa\",\"gjol\",\"gjoz\",\"gjpy\",\"gjrk\",\"gjrw\",\"gjtv\",\"gjtw\",\"gjul\",\"gjur\",\"gjvw\",\"gjwk\",\"gjxy\",\"gjyh\",\"gjzd\",\"gjzp\",\"gkcl\",\"gkcn\",\"gkeh\",\"gkff\",\"gkfh\",\"gkgx\",\"gkhg\",\"gkks\",\"gkmf\",\"gkmg\",\"gkpb\",\"gkrr\",\"gkvu\",\"glah\",\"glbk\",\"glch\",\"gldb\",\"glep\",\"glfe\",\"gljh\",\"glmj\",\"glni\",\"glnz\",\"glot\",\"gloz\",\"glph\",\"glwt\",\"glyh\",\"glyk\",\"glyu\",\"glzz\",\"gmhc\",\"gmkd\",\"gmlh\",\"gmmg\",\"gmmq\",\"gmpb\",\"gmpc\",\"gmpi\",\"gmpo\",\"gmpt\",\"gmqg\",\"gmtv\",\"gmwu\",\"gmxl\",\"gmxz\",\"gmyk\",\"gmzs\",\"gnds\",\"gnfz\",\"gnlr\",\"gnmc\",\"gnnh\",\"gnni\",\"gnqn\",\"gnul\",\"gnuy\",\"gnvz\",\"gnwj\",\"gnzm\",\"goae\",\"goam\",\"gobg\",\"gobr\",\"godk\",\"gohg\",\"goki\",\"gokp\",\"goot\",\"gooz\",\"gopd\",\"gopg\",\"gord\",\"goth\",\"goys\",\"gozx\",\"gpbb\",\"gpcs\",\"gpdm\",\"gpdw\",\"gpgt\",\"gpkk\",\"gpkt\",\"gplc\",\"gpll\",\"gpoj\",\"gpsc\",\"gpuu\",\"gpwb\",\"gpzi\",\"gqar\",\"gqgw\",\"gqiq\",\"gqit\",\"gqle\",\"gqlf\",\"gqli\",\"gqlm\",\"gqoj\",\"gqpd\",\"gqum\",\"gqvv\",\"gqyx\",\"gqzb\",\"gqzf\",\"grah\",\"grec\",\"grfi\",\"grfm\",\"grhj\",\"grhx\",\"grnc\",\"grnm\",\"grnn\",\"grnq\",\"grov\",\"grtx\",\"gryu\",\"grzd\",\"grzv\",\"gsca\",\"gscd\",\"gsct\",\"gscy\",\"gsew\",\"gsfz\",\"gslr\",\"gsnf\",\"gsqt\",\"gsrz\",\"gssl\",\"gsuv\",\"gszf\",\"gszi\",\"gszy\",\"gtcz\",\"gtdp\",\"gtev\",\"gtfl\",\"gthn\",\"gtkr\",\"gtmt\",\"gtnh\",\"gtom\",\"gtqo\",\"gtqq\",\"gtqv\",\"gtrt\",\"gtsf\",\"gtvr\",\"gtwf\",\"gtxj\",\"gtyu\",\"gueb\",\"gueo\",\"gufb\",\"gugz\",\"guik\",\"gumi\",\"gumr\",\"gupj\",\"gurg\",\"gusx\",\"guti\",\"guyh\",\"guyu\",\"gvao\",\"gvcf\",\"gvci\",\"gvdz\",\"gvej\",\"gvfl\",\"gvgu\",\"gvij\",\"gvjd\",\"gvjg\",\"gvkx\",\"gvln\",\"gvok\",\"gvox\",\"gvue\",\"gvus\",\"gvzp\",\"gwhj\",\"gwlu\",\"gwnz\",\"gwom\",\"gwpw\",\"gwss\",\"gwtd\",\"gwwj\",\"gwxo\",\"gwym\",\"gxam\",\"gxaz\",\"gxbk\",\"gxbz\",\"gxdf\",\"gxdu\",\"gxgq\",\"gxgv\",\"gxhu\",\"gxhw\",\"gxii\",\"gxio\",\"gxjg\",\"gxly\",\"gxmi\",\"gxnt\",\"gxow\",\"gxpn\",\"gxpz\",\"gxrv\",\"gxsg\",\"gxwk\",\"gxww\",\"gxxc\",\"gxxq\",\"gxxs\",\"gxxw\",\"gydn\",\"gydv\",\"gyfj\",\"gyjr\",\"gykl\",\"gyqb\",\"gyrf\",\"gyrw\",\"gytm\",\"gywl\",\"gyzc\",\"gzas\",\"gzbl\",\"gzdj\",\"gzea\",\"gzir\",\"gzkj\",\"gzml\",\"gznq\",\"gzpx\",\"gzqi\",\"gzso\",\"gzsz\",\"gztm\",\"gzxv\",\"haah\",\"haeo\",\"hagw\",\"hahm\",\"haia\",\"haif\",\"hakl\",\"hari\",\"hase\",\"hatk\",\"haus\",\"havh\",\"hazg\",\"hbaj\",\"hbey\",\"hbgb\",\"hbgm\",\"hbiv\",\"hbqq\",\"hbrq\",\"hbtp\",\"hbtw\",\"hbuq\",\"hbuu\",\"hbxf\",\"hcdj\",\"hcdr\",\"hcdx\",\"hcef\",\"hcgo\",\"hchp\",\"hcji\",\"hckm\",\"hcku\",\"hcma\",\"hcnp\",\"hcnz\",\"hcoa\",\"hcom\",\"hcos\",\"hcxh\",\"hcxi\",\"hczk\",\"hdbp\",\"hdca\",\"hdel\",\"hdex\",\"hdft\",\"hdnp\",\"hdrz\",\"hdty\",\"hdum\",\"hdwf\",\"hdww\",\"heay\",\"hecl\",\"hedg\",\"hehw\",\"heih\",\"heni\",\"hepr\",\"herr\",\"hesx\",\"hesy\",\"heuj\",\"hevh\",\"hevw\",\"hews\",\"heyi\",\"hfcu\",\"hfcz\",\"hfdi\",\"hfdp\",\"hfef\",\"hffp\",\"hfix\",\"hfni\",\"hfof\",\"hfpf\",\"hfqe\",\"hfqi\",\"hfrg\",\"hftg\",\"hfth\",\"hfvm\",\"hfxb\",\"hfxf\",\"hfyw\",\"hfzb\",\"hgaw\",\"hgbj\",\"hgea\",\"hged\",\"hgen\",\"hgft\",\"hggm\",\"hghk\",\"hghn\",\"hgip\",\"hgkg\",\"hgkn\",\"hgna\",\"hgnp\",\"hgpc\",\"hgru\",\"hgtu\",\"hguu\",\"hguz\",\"hgwn\",\"hgzj\",\"hgzu\",\"hhbm\",\"hhbp\",\"hhcj\",\"hhdb\",\"hhmb\",\"hhmh\",\"hhmu\",\"hhny\",\"hhpo\",\"hhpp\",\"hhqj\",\"hhqy\",\"hhwf\",\"hhxe\",\"hhyr\",\"hhyw\",\"hiav\",\"hiev\",\"higq\",\"hihk\",\"hikn\",\"himg\",\"himw\",\"hion\",\"hipx\",\"hipy\",\"hisa\",\"hitc\",\"hitp\",\"hiug\",\"hiuw\",\"hivm\",\"hiwr\",\"hiyw\",\"hjdd\",\"hjef\",\"hjer\",\"hjhp\",\"hjiq\",\"hjke\",\"hjkz\",\"hjlv\",\"hjnk\",\"hjns\",\"hjoi\",\"hjql\",\"hjqz\",\"hjrw\",\"hjsr\",\"hjtk\",\"hjwd\",\"hjzn\",\"hjzt\",\"hkcq\",\"hkdq\",\"hkfy\",\"hkgy\",\"hkhh\",\"hkkz\",\"hklm\",\"hknp\",\"hknz\",\"hkoi\",\"hkpw\",\"hkqw\",\"hkra\",\"hkrk\",\"hkty\",\"hkwa\",\"hkwi\",\"hkyp\",\"hlbl\",\"hlce\",\"hlct\",\"hlfu\",\"hlhl\",\"hlho\",\"hllg\",\"hlnb\",\"hlpk\",\"hlqs\",\"hltd\",\"hlxq\",\"hlzn\",\"hlzz\",\"hmap\",\"hmbb\",\"hmgu\",\"hmkh\",\"hmow\",\"hmti\",\"hmto\",\"hmuf\",\"hmwn\",\"hmxl\",\"hmzj\",\"hncv\",\"hnez\",\"hnie\",\"hnjk\",\"hnkz\",\"hnoo\",\"hnpn\",\"hoad\",\"hobh\",\"hocm\",\"hodv\",\"hofb\",\"hojs\",\"hojy\",\"hokz\",\"holg\",\"homu\",\"honb\",\"honp\",\"hoom\",\"hoph\",\"hopj\",\"hosd\",\"houi\",\"hous\",\"hovf\",\"hovi\",\"hoxt\",\"hoyr\",\"hpbg\",\"hpbn\",\"hpcg\",\"hpcl\",\"hpgh\",\"hphe\",\"hpjt\",\"hplh\",\"hpmq\",\"hpoj\",\"hppz\",\"hprv\",\"hpse\",\"hptu\",\"hpwo\",\"hpxs\",\"hqdw\",\"hqfd\",\"hqfk\",\"hqfl\",\"hqfy\",\"hqhy\",\"hqla\",\"hqlm\",\"hqmu\",\"hqnd\",\"hqou\",\"hqpz\",\"hqqf\",\"hqsh\",\"hquc\",\"hquu\",\"hqvo\",\"hqww\",\"hqxk\",\"hqzs\",\"hrav\",\"hrdk\",\"hrgm\",\"hrit\",\"hrjs\",\"hrkd\",\"hrkk\",\"hrkv\",\"hrmb\",\"hrqb\",\"hrwa\",\"hrzp\",\"hrzw\",\"hsfv\",\"hsgv\",\"hsjl\",\"hskl\",\"hsku\",\"hskw\",\"hsnc\",\"hsnk\",\"hspy\",\"hsqm\",\"hsru\",\"hstd\",\"hsvu\",\"hsxx\",\"hsze\",\"htae\",\"htas\",\"htbc\",\"htbj\",\"hteb\",\"hten\",\"htey\",\"htfr\",\"htfs\",\"htgy\",\"hthw\",\"htjf\",\"htnt\",\"htoj\",\"htrc\",\"httf\",\"htug\",\"htuh\",\"htuo\",\"htvx\",\"htxw\",\"huaj\",\"hufr\",\"hugn\",\"hujk\",\"husz\",\"huui\",\"huvm\",\"huyp\",\"huzb\",\"huzt\",\"hvae\",\"hvco\",\"hvec\",\"hvek\",\"hveo\",\"hvho\",\"hvjx\",\"hvlb\",\"hvlq\",\"hvoa\",\"hvoo\",\"hvoq\",\"hvov\",\"hvtv\",\"hvuf\",\"hvus\",\"hvxd\",\"hvzu\",\"hwao\",\"hwdf\",\"hwgd\",\"hwhc\",\"hwie\",\"hwkv\",\"hwmg\",\"hwmv\",\"hwoh\",\"hwqh\",\"hwqu\",\"hwrj\",\"hwsp\",\"hwsu\",\"hwyt\",\"hxbc\",\"hxcw\",\"hxfl\",\"hxnc\",\"hxpm\",\"hxpx\",\"hxsh\",\"hxvc\",\"hxvy\",\"hxwo\",\"hxyg\",\"hxyn\",\"hxza\",\"hxzs\",\"hybz\",\"hych\",\"hygp\",\"hygq\",\"hygz\",\"hyhn\",\"hypm\",\"hyrt\",\"hysj\",\"hyvq\",\"hyzf\",\"hzch\",\"hzfu\",\"hzjf\",\"hzkk\",\"hzmn\",\"hzob\",\"hzqz\",\"hzrg\",\"hzui\",\"hzxx\",\"hzze\",\"hzzx\",\"hzzz\",\"iaap\",\"iadf\",\"iaff\",\"iajg\",\"iakw\",\"iaom\",\"iasl\",\"iasx\",\"iats\",\"iatx\",\"iava\",\"iavk\",\"iavw\",\"iaxs\",\"iayx\",\"ibfl\",\"ibiv\",\"ibiy\",\"ibjk\",\"iblu\",\"ibnd\",\"ibnf\",\"iboe\",\"ibph\",\"ibqb\",\"ibqq\",\"ibri\",\"ibxk\",\"ibxp\",\"icar\",\"icbp\",\"iccw\",\"icgb\",\"icgs\",\"iclp\",\"icme\",\"icsh\",\"ictz\",\"icuf\",\"icuq\",\"icut\",\"iczu\",\"idcp\",\"idds\",\"iddz\",\"ided\",\"ideh\",\"idhg\",\"idhq\",\"idie\",\"idkd\",\"idnt\",\"idnw\",\"idqz\",\"idtn\",\"iduo\",\"iduu\",\"idvy\",\"idyt\",\"iedn\",\"ieei\",\"iefu\",\"iemf\",\"iemr\",\"ieod\",\"iepi\",\"iepx\",\"iequ\",\"iesn\",\"iesp\",\"iesq\",\"ieut\",\"ieuz\",\"ievd\",\"ievj\",\"ifbg\",\"ifcf\",\"ifcq\",\"ifdq\",\"ifej\",\"ifft\",\"ifht\",\"ifiy\",\"ifjc\",\"ifjr\",\"ifkr\",\"ifni\",\"ifob\",\"iftt\",\"ifui\",\"ifxj\",\"ifyv\",\"ifzp\",\"ifzq\",\"ifzu\",\"igaj\",\"igbf\",\"iged\",\"igft\",\"iggj\",\"iggl\",\"iggs\",\"igil\",\"igiz\",\"igmm\",\"igoa\",\"igrs\",\"iguh\",\"igyu\",\"igzu\",\"ihbj\",\"ihcf\",\"ihfv\",\"ihgm\",\"ihie\",\"ihkf\",\"ihkg\",\"ihnr\",\"ihod\",\"ihrf\",\"ihsb\",\"ihsg\",\"ihsv\",\"ihsw\",\"ihuw\",\"ihvm\",\"ihwf\",\"ihzb\",\"iiaw\",\"iici\",\"iicn\",\"iidp\",\"iigc\",\"iign\",\"iijd\",\"iikf\",\"iikt\",\"iiky\",\"iile\",\"iilq\",\"iinf\",\"iiom\",\"iior\",\"iioy\",\"iipg\",\"iiqt\",\"iire\",\"iitu\",\"iizo\",\"ijak\",\"ijeb\",\"ijeg\",\"ijer\",\"ijet\",\"ijge\",\"ijju\",\"ijlh\",\"ijlm\",\"ijns\",\"ijqj\",\"ijrv\",\"ijuk\",\"ijxc\",\"ijzr\",\"ijzy\",\"ikac\",\"ikba\",\"ikdi\",\"ikdu\",\"ikeb\",\"ikgo\",\"ikgs\",\"ikit\",\"ikjy\",\"ikko\",\"iklr\",\"iklv\",\"ikmo\",\"ikuj\",\"ikxy\",\"ikym\",\"ilal\",\"ilay\",\"ilbh\",\"ilcj\",\"ilkr\",\"ilng\",\"ilnn\",\"ilqo\",\"ilrk\",\"ilrv\",\"iltk\",\"ilvg\",\"ilwd\",\"ilwf\",\"ilyt\",\"ilzi\",\"imau\",\"imav\",\"imcm\",\"imdb\",\"imeh\",\"imey\",\"imfh\",\"imgx\",\"imjn\",\"imjz\",\"imla\",\"imlc\",\"imoe\",\"imqd\",\"imrq\",\"imrt\",\"imtn\",\"imwg\",\"imwp\",\"imxq\",\"imyq\",\"imzj\",\"indw\",\"infm\",\"infv\",\"ingd\",\"ingn\",\"ings\",\"inhr\",\"inhv\",\"inhx\",\"iniw\",\"inje\",\"injv\",\"inmh\",\"innj\",\"inpb\",\"inpk\",\"inqo\",\"inzf\",\"ioao\",\"iodl\",\"iody\",\"iokt\",\"iolv\",\"iomw\",\"ionc\",\"iopj\",\"iopo\",\"iosd\",\"ioty\",\"ioyc\",\"ioyz\",\"ipew\",\"ipfu\",\"ipif\",\"ipmk\",\"ipmq\",\"ipnv\",\"ippc\",\"ippd\",\"ipqi\",\"iprn\",\"ipxy\",\"ipyz\",\"iqcy\",\"iqex\",\"iqfc\",\"iqfk\",\"iqfx\",\"iqlr\",\"iqns\",\"iqri\",\"iqrw\",\"iqso\",\"iqvb\",\"iqwk\",\"iqxp\",\"iqxx\",\"iqyl\",\"iqyy\",\"iqzy\",\"iral\",\"irdk\",\"irjk\",\"irlw\",\"irmp\",\"irna\",\"iroz\",\"irpo\",\"irux\",\"iryg\",\"iryy\",\"isaf\",\"isao\",\"isbz\",\"isds\",\"isfc\",\"ishi\",\"isif\",\"isml\",\"isoo\",\"isqe\",\"isqr\",\"isrt\",\"istg\",\"istt\",\"isvh\",\"isxy\",\"isza\",\"iszx\",\"itct\",\"itfn\",\"ithi\",\"itip\",\"itiw\",\"itmp\",\"itpf\",\"itpl\",\"itqa\",\"itud\",\"itwg\",\"ityx\",\"iubd\",\"iubi\",\"iudw\",\"iugh\",\"iuhx\",\"iuie\",\"iulz\",\"iuoc\",\"iupm\",\"iuth\",\"iuto\",\"iuvt\",\"iuwx\",\"ivaf\",\"ivbo\",\"ivcd\",\"ivcf\",\"ivea\",\"ivfy\",\"ivhg\",\"ivjj\",\"ivmo\",\"ivmp\",\"ivok\",\"ivrk\",\"ivxh\",\"ivxx\",\"ivyb\",\"ivys\",\"iwek\",\"iwfo\",\"iwim\",\"iwnh\",\"iwnj\",\"iwpo\",\"iwth\",\"iwum\",\"iwvr\",\"iwvv\",\"iwwh\",\"iwxe\",\"iwxs\",\"ixac\",\"ixdf\",\"ixgz\",\"ixiy\",\"ixlo\",\"ixly\",\"ixml\",\"ixor\",\"ixvc\",\"ixvr\",\"ixwz\",\"ixyo\",\"ixyx\",\"iyaz\",\"iyci\",\"iydq\",\"iyfj\",\"iyfl\",\"iylh\",\"iyma\",\"iymo\",\"iynr\",\"iypx\",\"iyrb\",\"iysh\",\"iyyx\",\"iyyy\",\"iyza\",\"izak\",\"izat\",\"izcy\",\"izep\",\"izfk\",\"izjn\",\"izoe\",\"izoz\",\"izqy\",\"izrm\",\"iztw\",\"izut\",\"izvk\",\"izwg\",\"izyi\",\"jacx\",\"jada\",\"jadu\",\"jafi\",\"jafo\",\"jaig\",\"jaka\",\"jamf\",\"jaso\",\"java\",\"jazc\",\"jazp\",\"jbbd\",\"jbbv\",\"jbeb\",\"jbfv\",\"jbfx\",\"jbgn\",\"jbiu\",\"jbiw\",\"jbmk\",\"jbnf\",\"jboe\",\"jbpw\",\"jbqf\",\"jbqg\",\"jbql\",\"jbru\",\"jbtl\",\"jbwf\",\"jbwl\",\"jbwx\",\"jcah\",\"jccg\",\"jcdd\",\"jcdu\",\"jcew\",\"jcgw\",\"jchh\",\"jckb\",\"jclq\",\"jcmo\",\"jcnp\",\"jcoh\",\"jcpr\",\"jctj\",\"jcux\",\"jcvl\",\"jcwe\",\"jczh\",\"jdaf\",\"jdcd\",\"jdce\",\"jdhf\",\"jdhh\",\"jdhi\",\"jdhk\",\"jdlz\",\"jdmn\",\"jdmp\",\"jdog\",\"jdpb\",\"jdtb\",\"jduj\",\"jdwd\",\"jdxv\",\"jdyj\",\"jeah\",\"jeak\",\"jecf\",\"jefs\",\"jegc\",\"jegg\",\"jehp\",\"jejc\",\"jeje\",\"jend\",\"jewx\",\"jfal\",\"jfdv\",\"jfff\",\"jfgb\",\"jfie\",\"jfjv\",\"jfkr\",\"jfkw\",\"jfox\",\"jfqf\",\"jftz\",\"jfvq\",\"jfwc\",\"jfwg\",\"jgbl\",\"jgef\",\"jgfd\",\"jgfg\",\"jggy\",\"jght\",\"jgir\",\"jgkb\",\"jgmw\",\"jgom\",\"jgqg\",\"jgte\",\"jgug\",\"jgus\",\"jgvg\",\"jgvj\",\"jgvu\",\"jgwk\",\"jgxl\",\"jgzb\",\"jhad\",\"jhbj\",\"jhey\",\"jhfb\",\"jhfr\",\"jhis\",\"jhli\",\"jhnn\",\"jhrw\",\"jhse\",\"jhsl\",\"jhss\",\"jhug\",\"jhuy\",\"jhwy\",\"jhxc\",\"jhxf\",\"jhzb\",\"jibt\",\"jifk\",\"jihj\",\"jiib\",\"jiin\",\"jilh\",\"jilm\",\"jimg\",\"jins\",\"jiob\",\"jioi\",\"jipl\",\"jirr\",\"jiur\",\"jiyj\",\"jjah\",\"jjez\",\"jjgf\",\"jjik\",\"jjkp\",\"jjmi\",\"jjnq\",\"jjph\",\"jjrn\",\"jjtb\",\"jjum\",\"jjxm\",\"jjyf\",\"jjyj\",\"jkaf\",\"jkaq\",\"jkcw\",\"jkem\",\"jkgl\",\"jkia\",\"jkjs\",\"jkkw\",\"jklf\",\"jklm\",\"jklo\",\"jkre\",\"jksa\",\"jkth\",\"jktr\",\"jkun\",\"jkwp\",\"jkww\",\"jkya\",\"jkyy\",\"jlbs\",\"jlea\",\"jlep\",\"jleq\",\"jlhj\",\"jlkr\",\"jlmb\",\"jlni\",\"jlse\",\"jltj\",\"jlwi\",\"jlyn\",\"jlza\",\"jlzt\",\"jman\",\"jmbb\",\"jmbi\",\"jmbo\",\"jmcm\",\"jmem\",\"jmev\",\"jmgy\",\"jmiy\",\"jmke\",\"jmkx\",\"jmnt\",\"jmpv\",\"jmqe\",\"jmuj\",\"jmvo\",\"jmxn\",\"jmxz\",\"jmyv\",\"jnbb\",\"jnby\",\"jncr\",\"jndb\",\"jnfz\",\"jniw\",\"jnjf\",\"jnke\",\"jnlg\",\"jnln\",\"jnok\",\"jnyh\",\"jnzp\",\"joaq\",\"jodo\",\"joen\",\"jofi\",\"jofw\",\"john\",\"jojp\",\"jokl\",\"joks\",\"jonj\",\"jooa\",\"joqj\",\"josl\",\"jotv\",\"jovr\",\"jovs\",\"joyy\",\"jozj\",\"jozr\",\"jpah\",\"jpbn\",\"jpdn\",\"jpln\",\"jpnk\",\"jpnn\",\"jpou\",\"jpqh\",\"jpsp\",\"jptg\",\"jptj\",\"jpwf\",\"jpwn\",\"jpxn\",\"jqal\",\"jqbi\",\"jqcl\",\"jqer\",\"jqfi\",\"jqjw\",\"jqlk\",\"jqnx\",\"jqob\",\"jqqh\",\"jqro\",\"jqtb\",\"jqur\",\"jqvk\",\"jqvp\",\"jqxn\",\"jqyx\",\"jqzz\",\"jraf\",\"jrap\",\"jrbs\",\"jrdn\",\"jrgj\",\"jriq\",\"jrky\",\"jrlu\",\"jrmy\",\"jrnu\",\"jrnx\",\"jrps\",\"jrtn\",\"jrvt\",\"jrzl\",\"jscm\",\"jsdl\",\"jsey\",\"jsfb\",\"jsfs\",\"jsku\",\"jsnj\",\"jsoa\",\"jsoq\",\"jsry\",\"jssi\",\"jsvz\",\"jsxj\",\"jsyn\",\"jszl\",\"jteg\",\"jtgh\",\"jthm\",\"jthq\",\"jthv\",\"jtjb\",\"jtkd\",\"jtlb\",\"jtmw\",\"jtpo\",\"jtql\",\"jtuw\",\"jtwm\",\"jtwq\",\"jtyd\",\"jtym\",\"juaa\",\"judh\",\"juen\",\"jueq\",\"jufw\",\"juij\",\"juis\",\"julo\",\"juqr\",\"jutx\",\"jutz\",\"juua\",\"juxy\",\"juzl\",\"jvbl\",\"jvbm\",\"jvbo\",\"jvcb\",\"jvix\",\"jvka\",\"jvkv\",\"jvlg\",\"jvqc\",\"jvst\",\"jvtd\",\"jvtq\",\"jvwz\",\"jvyf\",\"jvzi\",\"jvzp\",\"jwau\",\"jwcc\",\"jwhh\",\"jwhr\",\"jwjq\",\"jwli\",\"jwoy\",\"jwwg\",\"jwwz\",\"jwxf\",\"jwxg\",\"jwzx\",\"jxbd\",\"jxdq\",\"jxdu\",\"jxfo\",\"jxfr\",\"jxfz\",\"jxiu\",\"jxje\",\"jxnf\",\"jxrs\",\"jxsu\",\"jxsy\",\"jxtm\",\"jxyj\",\"jxyq\",\"jxzo\",\"jybk\",\"jycw\",\"jydg\",\"jyhe\",\"jyhv\",\"jyjd\",\"jyku\",\"jykv\",\"jylo\",\"jyrh\",\"jyvc\",\"jyxb\",\"jyzv\",\"jzav\",\"jzbt\",\"jzbu\",\"jzej\",\"jzfy\",\"jzgj\",\"jzgs\",\"jzib\",\"jzij\",\"jzkq\",\"jznz\",\"jzoh\",\"jzqh\",\"jzsr\",\"jzvd\",\"jzwy\",\"jzxh\",\"jzzc\",\"jzzy\",\"kaba\",\"kaby\",\"kaci\",\"kafd\",\"kafh\",\"kajf\",\"kakc\",\"kamw\",\"kaun\",\"kavy\",\"kaxv\",\"kazv\",\"kbau\",\"kbci\",\"kbfw\",\"kbiv\",\"kbnt\",\"kbop\",\"kbph\",\"kbtn\",\"kbts\",\"kbvg\",\"kbvu\",\"kbxw\",\"kbyv\",\"kcct\",\"kcda\",\"kcfw\",\"kcik\",\"kciu\",\"kcjx\",\"kckk\",\"kcmx\",\"kcnd\",\"kcoa\",\"kcpb\",\"kcpp\",\"kcqm\",\"kcrr\",\"kcru\",\"kcso\",\"kcwv\",\"kcya\",\"kdad\",\"kdav\",\"kddh\",\"kdef\",\"kdgn\",\"kdhc\",\"kdju\",\"kdjy\",\"kdke\",\"kdkn\",\"kdlk\",\"kdne\",\"kdog\",\"kdtw\",\"kdyc\",\"kdyl\",\"keaq\",\"kebx\",\"kebz\",\"kecg\",\"kede\",\"kego\",\"kegq\",\"kekj\",\"kenl\",\"keol\",\"keox\",\"kesf\",\"kevf\",\"kexj\",\"kfbh\",\"kfbi\",\"kfda\",\"kfdq\",\"kfdz\",\"kfgt\",\"kfjq\",\"kfko\",\"kfns\",\"kfqg\",\"kfqs\",\"kfss\",\"kftp\",\"kfuw\",\"kfvg\",\"kfxs\",\"kfyt\",\"kfze\",\"kgbv\",\"kgcd\",\"kgdf\",\"kgdj\",\"kgdo\",\"kggn\",\"kgip\",\"kgix\",\"kgji\",\"kgkv\",\"kgle\",\"kglz\",\"kgmu\",\"kgnu\",\"kgok\",\"kgoq\",\"kgph\",\"kgzf\",\"kgzw\",\"khav\",\"khdr\",\"khej\",\"khep\",\"khez\",\"khfd\",\"khlx\",\"khmq\",\"khmt\",\"khnc\",\"khod\",\"khpm\",\"khrx\",\"khwa\",\"khxc\",\"kibr\",\"kiht\",\"kiig\",\"kijk\",\"kijr\",\"kikp\",\"kilj\",\"kiok\",\"kioq\",\"kipv\",\"kird\",\"kisb\",\"kisq\",\"kitu\",\"kivo\",\"kiwu\",\"kizc\",\"kizs\",\"kjbz\",\"kjch\",\"kjdf\",\"kjdl\",\"kjhx\",\"kjid\",\"kjii\",\"kjir\",\"kjll\",\"kjlm\",\"kjop\",\"kjpm\",\"kjpt\",\"kjrx\",\"kjtg\",\"kjut\",\"kjwy\",\"kjxh\",\"kjyg\",\"kjyi\",\"kjyv\",\"kkav\",\"kkdy\",\"kken\",\"kkhl\",\"kkhn\",\"kkja\",\"kkot\",\"kkxg\",\"kkyd\",\"klbv\",\"klct\",\"klda\",\"klfw\",\"klgg\",\"klix\",\"kljv\",\"klny\",\"klpf\",\"kltj\",\"klvc\",\"klve\",\"klvr\",\"klxg\",\"kmba\",\"kmbl\",\"kmbw\",\"kmdh\",\"kmfo\",\"kmhc\",\"kmjj\",\"kmol\",\"kmor\",\"kmpb\",\"kmru\",\"kmrv\",\"kmux\",\"kmwv\",\"kmzr\",\"knak\",\"kndd\",\"kndf\",\"kndg\",\"knem\",\"knim\",\"knjd\",\"knpj\",\"knuz\",\"knwf\",\"knxb\",\"knxy\",\"kobe\",\"kodo\",\"kogr\",\"kogt\",\"koiw\",\"kolx\",\"kome\",\"komp\",\"konz\",\"kosx\",\"kotg\",\"kott\",\"kout\",\"kowk\",\"kpap\",\"kpbj\",\"kpct\",\"kpeo\",\"kpie\",\"kpig\",\"kpjc\",\"kpjh\",\"kpli\",\"kplx\",\"kpmy\",\"kprl\",\"kpti\",\"kptq\",\"kpuf\",\"kpwm\",\"kpwz\",\"kqan\",\"kqhp\",\"kqjh\",\"kqjz\",\"kqla\",\"kqlt\",\"kqmn\",\"kqnn\",\"kqoj\",\"kqtg\",\"kqva\",\"kqzh\",\"krbd\",\"krcc\",\"krcf\",\"kreq\",\"krff\",\"krga\",\"krhq\",\"krhw\",\"krhx\",\"kriu\",\"krjn\",\"krkt\",\"krlj\",\"krow\",\"krqb\",\"krsc\",\"krts\",\"kruj\",\"krwv\",\"krxb\",\"krxe\",\"ksdm\",\"ksez\",\"ksgs\",\"ksgx\",\"kspj\",\"ksqb\",\"ksse\",\"kssv\",\"ksvb\",\"kswl\",\"ksxe\",\"ksym\",\"kszg\",\"ktbm\",\"ktcc\",\"ktcv\",\"ktin\",\"ktiv\",\"ktiy\",\"ktjo\",\"ktkh\",\"ktll\",\"ktox\",\"kttp\",\"ktun\",\"ktwu\",\"kudz\",\"kuia\",\"kukg\",\"kukk\",\"kulo\",\"kutn\",\"kuuk\",\"kuvx\",\"kuwo\",\"kuwq\",\"kuxi\",\"kuzo\",\"kvbv\",\"kvdg\",\"kvdx\",\"kvfs\",\"kvhh\",\"kvhn\",\"kvla\",\"kvmq\",\"kvog\",\"kvuc\",\"kvvc\",\"kvyt\",\"kvzq\",\"kwbg\",\"kwde\",\"kwdo\",\"kwdp\",\"kwgf\",\"kwhh\",\"kwig\",\"kwjd\",\"kwnf\",\"kwqn\",\"kwws\",\"kwxi\",\"kxba\",\"kxef\",\"kxek\",\"kxfz\",\"kxga\",\"kxgp\",\"kxgx\",\"kxmm\",\"kxnb\",\"kxnp\",\"kxpe\",\"kxqb\",\"kxuc\",\"kxyp\",\"kyad\",\"kycq\",\"kyhe\",\"kyhx\",\"kyic\",\"kykq\",\"kykt\",\"kyll\",\"kyns\",\"kyps\",\"kyso\",\"kywe\",\"kywz\",\"kyyb\",\"kzat\",\"kzav\",\"kzcz\",\"kzdm\",\"kzhq\",\"kziq\",\"kzky\",\"kzln\",\"kzmf\",\"kzrr\",\"kzrv\",\"kztd\",\"kztn\",\"kzul\",\"kzvq\",\"labp\",\"lacf\",\"lacp\",\"lacw\",\"ladc\",\"lafr\",\"lahw\",\"lamw\",\"laqr\",\"larx\",\"lasi\",\"lasn\",\"lasu\",\"laxx\",\"layz\",\"lbgc\",\"lbiv\",\"lbjp\",\"lbko\",\"lbos\",\"lbqq\",\"lbvg\",\"lbxt\",\"lbyn\",\"lbze\",\"lcbi\",\"lcfs\",\"lchn\",\"lcky\",\"lclf\",\"lcmt\",\"lcqw\",\"lcsy\",\"lcuv\",\"lcvj\",\"lcwg\",\"lcws\",\"lcyp\",\"lcza\",\"ldar\",\"ldbf\",\"ldbt\",\"ldca\",\"lddl\",\"lddq\",\"ldee\",\"ldir\",\"ldjp\",\"ldjx\",\"ldlw\",\"ldma\",\"ldnm\",\"ldpj\",\"ldpl\",\"ldrl\",\"ldrq\",\"ldtw\",\"ldus\",\"ldzz\",\"leba\",\"lebj\",\"ledo\",\"ledx\",\"lefi\",\"legu\",\"lehg\",\"lehp\",\"leld\",\"lemw\",\"lerh\",\"lesq\",\"lesv\",\"levn\",\"lexb\",\"lfau\",\"lfbb\",\"lfbu\",\"lfer\",\"lfhb\",\"lfhn\",\"lfjn\",\"lfkj\",\"lflu\",\"lfme\",\"lfqg\",\"lfql\",\"lfxi\",\"lfxs\",\"lfxz\",\"lfza\",\"lgda\",\"lggb\",\"lgit\",\"lgle\",\"lgou\",\"lgsy\",\"lgvh\",\"lgwe\",\"lgzj\",\"lhdg\",\"lhdu\",\"lher\",\"lhfh\",\"lhfi\",\"lhiz\",\"lhna\",\"lhni\",\"lhnl\",\"lhnm\",\"lhoc\",\"lhqq\",\"lhro\",\"libc\",\"licf\",\"lict\",\"liek\",\"ligv\",\"lihv\",\"liif\",\"lije\",\"lijs\",\"likm\",\"liky\",\"lilg\",\"limv\",\"linc\",\"linv\",\"lior\",\"lios\",\"lirh\",\"lisn\",\"litr\",\"ljbh\",\"ljcb\",\"ljfv\",\"ljgg\",\"ljjk\",\"ljlf\",\"ljlh\",\"ljnp\",\"ljqe\",\"ljti\",\"ljtp\",\"ljtr\",\"ljuf\",\"ljxg\",\"ljxs\",\"ljzm\",\"lkdc\",\"lkei\",\"lkle\",\"lklf\",\"lkmz\",\"lkoc\",\"lkrj\",\"lkrk\",\"lksd\",\"lkub\",\"lkwm\",\"llcw\",\"llcx\",\"lldn\",\"llju\",\"llla\",\"llmb\",\"llmi\",\"llse\",\"llsr\",\"llss\",\"llsx\",\"lltf\",\"lluh\",\"llwo\",\"llym\",\"llzb\",\"lmab\",\"lmaj\",\"lmaw\",\"lmdl\",\"lmfv\",\"lmiy\",\"lmle\",\"lmsu\",\"lmvm\",\"lmxn\",\"lmzy\",\"lnaj\",\"lnbs\",\"lnfu\",\"lnfv\",\"lngo\",\"lngu\",\"lnir\",\"lnjk\",\"lnmz\",\"lnoq\",\"lnua\",\"lnux\",\"lodl\",\"loey\",\"loez\",\"lofs\",\"lofy\",\"logd\",\"lohy\",\"loiq\",\"lojd\",\"lojw\",\"lokq\",\"lonj\",\"lopb\",\"lopz\",\"loqx\",\"loti\",\"lowc\",\"lowm\",\"loxq\",\"loyf\",\"loys\",\"lpaj\",\"lpca\",\"lpht\",\"lpiq\",\"lpir\",\"lpjb\",\"lpjk\",\"lpks\",\"lplh\",\"lpmp\",\"lppa\",\"lppz\",\"lprg\",\"lpsz\",\"lpta\",\"lpti\",\"lpwu\",\"lpxf\",\"lpyb\",\"lpyc\",\"lpyq\",\"lqax\",\"lqcx\",\"lqed\",\"lqei\",\"lqfx\",\"lqjj\",\"lqlx\",\"lqmm\",\"lqpt\",\"lqqs\",\"lrbr\",\"lres\",\"lrgf\",\"lrgp\",\"lrjz\",\"lrki\",\"lrlv\",\"lrma\",\"lrmd\",\"lrmh\",\"lrmj\",\"lrmy\",\"lrnv\",\"lrnx\",\"lrpa\",\"lrpx\",\"lrqi\",\"lrrg\",\"lrsc\",\"lrtv\",\"lrvj\",\"lrvm\",\"lrzl\",\"lsar\",\"lscg\",\"lsif\",\"lsjg\",\"lskv\",\"lsna\",\"lssm\",\"lsvj\",\"lswc\",\"lsxz\",\"lszw\",\"ltaz\",\"ltdk\",\"ltdt\",\"ltgp\",\"ltgs\",\"ltjs\",\"ltme\",\"ltom\",\"lton\",\"ltou\",\"ltri\",\"ltto\",\"ltvd\",\"ltyn\",\"ltyq\",\"lubd\",\"lubl\",\"lufd\",\"lufz\",\"luji\",\"lukb\",\"luln\",\"luns\",\"luom\",\"luqx\",\"luub\",\"luvi\",\"luxu\",\"luyi\",\"lvbr\",\"lvgs\",\"lvhv\",\"lvoy\",\"lvpp\",\"lvpt\",\"lvrc\",\"lvrq\",\"lvse\",\"lvvb\",\"lvxg\",\"lvxq\",\"lwad\",\"lwfe\",\"lwok\",\"lwqs\",\"lwss\",\"lwtk\",\"lwyl\",\"lwzt\",\"lxam\",\"lxap\",\"lxeq\",\"lxfl\",\"lxjw\",\"lxjz\",\"lxkp\",\"lxla\",\"lxlv\",\"lxni\",\"lxrb\",\"lxui\",\"lxuk\",\"lyeh\",\"lygn\",\"lygw\",\"lyiz\",\"lyjo\",\"lykc\",\"lykq\",\"lymz\",\"lyod\",\"lyrv\",\"lyvq\",\"lyvr\",\"lyxv\",\"lyyb\",\"lyzn\",\"lzag\",\"lzci\",\"lzeo\",\"lzgs\",\"lzjg\",\"lzmw\",\"lzoe\",\"lzoi\",\"lzom\",\"lzqx\",\"lzsg\",\"lzuf\",\"lzvv\",\"lzwn\",\"maab\",\"madw\",\"mafz\",\"makt\",\"maod\",\"maof\",\"maqv\",\"maui\",\"maxr\",\"mbbe\",\"mbbo\",\"mbbr\",\"mbdm\",\"mbgf\",\"mblm\",\"mbls\",\"mbqh\",\"mbqm\",\"mbqw\",\"mbsl\",\"mbso\",\"mbtg\",\"mbtj\",\"mbtl\",\"mbwg\",\"mbwo\",\"mbym\",\"mbze\",\"mcan\",\"mcds\",\"mcfq\",\"mcgw\",\"mcha\",\"mcki\",\"mcma\",\"mcmg\",\"mcmv\",\"mcnv\",\"mcof\",\"mcpa\",\"mcqn\",\"mcro\",\"mcso\",\"mctk\",\"mctu\",\"mcub\",\"mcuv\",\"mcuw\",\"mcxg\",\"mcxy\",\"mcyo\",\"mdad\",\"mday\",\"mdba\",\"mdek\",\"mdjt\",\"mdkb\",\"mdkt\",\"mdlq\",\"mdne\",\"mdnj\",\"mdod\",\"mdoj\",\"mdsa\",\"mdsc\",\"mdtc\",\"mdwt\",\"mdxx\",\"meaz\",\"meck\",\"medi\",\"mefy\",\"meko\",\"memb\",\"memd\",\"meov\",\"merq\",\"mers\",\"mesj\",\"meuo\",\"mevx\",\"mezd\",\"mezk\",\"mfce\",\"mfho\",\"mfji\",\"mflx\",\"mfnr\",\"mfoc\",\"mfpg\",\"mfrf\",\"mftr\",\"mfug\",\"mfvo\",\"mfxn\",\"mfzj\",\"mfzu\",\"mfzv\",\"mfzx\",\"mgcm\",\"mgdu\",\"mges\",\"mgfc\",\"mggr\",\"mggy\",\"mghv\",\"mgie\",\"mgln\",\"mgve\",\"mgyw\",\"mhbf\",\"mhbu\",\"mhcl\",\"mhem\",\"mhgm\",\"mhgs\",\"mhiq\",\"mhkb\",\"mhkz\",\"mhpb\",\"mhpx\",\"mhrs\",\"mhrw\",\"mhyc\",\"mhyr\",\"mibh\",\"midz\",\"miei\",\"miet\",\"mige\",\"mihy\",\"mijg\",\"miji\",\"mika\",\"mikk\",\"mikx\",\"mioe\",\"mirl\",\"misx\",\"miti\",\"miuc\",\"miun\",\"mivr\",\"miyf\",\"mjcb\",\"mjez\",\"mjff\",\"mjhq\",\"mjjj\",\"mjjr\",\"mjkn\",\"mjlj\",\"mjlr\",\"mjor\",\"mjpa\",\"mjrt\",\"mjuy\",\"mjxr\",\"mjzo\",\"mkbt\",\"mkdv\",\"mkgp\",\"mkiz\",\"mkkh\",\"mkmb\",\"mkmi\",\"mknp\",\"mkpc\",\"mkqq\",\"mkrf\",\"mksa\",\"mksu\",\"mktv\",\"mkud\",\"mkvd\",\"mkwr\",\"mkxh\",\"mlat\",\"mlce\",\"mley\",\"mlfo\",\"mlgc\",\"mlgr\",\"mlhu\",\"mliv\",\"mlkr\",\"mllf\",\"mlli\",\"mlly\",\"mllz\",\"mlqd\",\"mlsw\",\"mlvb\",\"mlvu\",\"mlwv\",\"mlxb\",\"mlye\",\"mmab\",\"mmai\",\"mmbz\",\"mmem\",\"mmfq\",\"mmib\",\"mmlh\",\"mmlo\",\"mmmi\",\"mmqt\",\"mmrx\",\"mmvd\",\"mmzm\",\"mnab\",\"mnaz\",\"mnbl\",\"mnbz\",\"mncc\",\"mngm\",\"mngp\",\"mnli\",\"mnln\",\"mnme\",\"mnnu\",\"mnsd\",\"mnsl\",\"mnut\",\"mnxf\",\"mnyi\",\"mnyv\",\"mnzl\",\"mogu\",\"mois\",\"mooz\",\"mopc\",\"mord\",\"morf\",\"mosk\",\"moyk\",\"mpbr\",\"mpcd\",\"mpfi\",\"mphv\",\"mpjh\",\"mpkh\",\"mply\",\"mpne\",\"mppe\",\"mppv\",\"mprf\",\"mpro\",\"mpvt\",\"mpwe\",\"mpxh\",\"mpyy\",\"mqby\",\"mqco\",\"mqcz\",\"mqep\",\"mqif\",\"mqkx\",\"mqlj\",\"mqmm\",\"mqnn\",\"mqns\",\"mqoy\",\"mqpl\",\"mqqt\",\"mqti\",\"mqvw\",\"mqwb\",\"mqxt\",\"mqxw\",\"mqyi\",\"mrbn\",\"mrem\",\"mrex\",\"mrgq\",\"mrhj\",\"mrio\",\"mrjj\",\"mrmd\",\"mrmh\",\"mrnh\",\"mrot\",\"mrpd\",\"mrpn\",\"mrsl\",\"mrtp\",\"msdf\",\"mses\",\"mske\",\"msko\",\"msmb\",\"msmi\",\"msoj\",\"msrf\",\"msrz\",\"mssc\",\"mtdx\",\"mtea\",\"mtgy\",\"mthj\",\"mtnw\",\"mtog\",\"mtop\",\"mtse\",\"mtwa\",\"mtwu\",\"mtxw\",\"mtzl\",\"mual\",\"muay\",\"mudv\",\"mueh\",\"mufp\",\"mukv\",\"muma\",\"mumk\",\"mumq\",\"muoj\",\"muqr\",\"musn\",\"mutm\",\"muvz\",\"muxg\",\"muyg\",\"muzd\",\"muzg\",\"mvci\",\"mvco\",\"mvdz\",\"mvek\",\"mvgd\",\"mvgl\",\"mviy\",\"mvjp\",\"mvjx\",\"mvms\",\"mvnm\",\"mvoy\",\"mvrk\",\"mvyb\",\"mwap\",\"mwbj\",\"mwdw\",\"mwef\",\"mwei\",\"mwik\",\"mwve\",\"mwwj\",\"mwxo\",\"mwyu\",\"mxai\",\"mxcp\",\"mxeq\",\"mxgg\",\"mxgl\",\"mxhf\",\"mxhi\",\"mxhj\",\"mxiw\",\"mxjk\",\"mxjr\",\"mxkv\",\"mxmf\",\"mxvd\",\"mxvi\",\"mxvs\",\"mxwu\",\"mybs\",\"mycx\",\"myeo\",\"myhi\",\"myni\",\"myri\",\"myrv\",\"mytc\",\"myvq\",\"mywp\",\"myyq\",\"myzq\",\"mzdg\",\"mzfp\",\"mzgy\",\"mzih\",\"mzkj\",\"mzkl\",\"mzth\",\"mztl\",\"mzut\",\"nabp\",\"nadk\",\"nall\",\"nama\",\"namx\",\"naqo\",\"naqw\",\"naqx\",\"narb\",\"natu\",\"nauv\",\"nayg\",\"nbdh\",\"nbdj\",\"nbig\",\"nbim\",\"nbjm\",\"nbmz\",\"nbnc\",\"nbsd\",\"nbvn\",\"nbxi\",\"nbxo\",\"ncbq\",\"ncfd\",\"ncgo\",\"ncgu\",\"ncgx\",\"nchk\",\"ncjp\",\"ncme\",\"ncmh\",\"ncmz\",\"ncng\",\"ncov\",\"ncps\",\"ncsy\",\"nctq\",\"ncuo\",\"ncvc\",\"ncwc\",\"nczz\",\"ndag\",\"ndce\",\"ndej\",\"ndex\",\"ndgc\",\"ndjl\",\"ndkn\",\"ndku\",\"ndlh\",\"ndpm\",\"ndqg\",\"ndte\",\"ndua\",\"nduj\",\"neaw\",\"nede\",\"neeb\",\"nefb\",\"nehe\",\"nejp\",\"nekk\",\"nekq\",\"nekr\",\"nell\",\"nely\",\"nens\",\"neoj\",\"nepo\",\"nerr\",\"neug\",\"nevx\",\"nezm\",\"nfcn\",\"nfcv\",\"nfhn\",\"nfhz\",\"nfkf\",\"nfmo\",\"nfmu\",\"nfoo\",\"nfpb\",\"nfsq\",\"nfws\",\"ngeh\",\"ngey\",\"nghm\",\"ngim\",\"ngit\",\"nglp\",\"ngno\",\"ngpn\",\"ngqi\",\"nguw\",\"ngve\",\"ngvz\",\"ngyz\",\"ngzo\",\"nhaa\",\"nhdv\",\"nhfh\",\"nhfp\",\"nhhf\",\"nhia\",\"nhnk\",\"nhog\",\"nhrk\",\"nhsb\",\"nhvd\",\"nhwf\",\"nhxo\",\"nhxx\",\"nhye\",\"nhyu\",\"nieo\",\"nifj\",\"nifu\",\"nigv\",\"nijq\",\"nild\",\"nipz\",\"nist\",\"niuk\",\"niva\",\"niwn\",\"niwp\",\"nizc\",\"nizw\",\"njbd\",\"njbw\",\"njjb\",\"njoj\",\"njpd\",\"njqn\",\"njtk\",\"njxr\",\"njxx\",\"njzd\",\"njzr\",\"nkbt\",\"nkbz\",\"nkdb\",\"nkew\",\"nkfj\",\"nkgj\",\"nkia\",\"nkle\",\"nknc\",\"nkny\",\"nkou\",\"nksw\",\"nkvk\",\"nkxs\",\"nkyg\",\"nkzu\",\"nlcv\",\"nlfv\",\"nlgg\",\"nljk\",\"nljr\",\"nlny\",\"nluu\",\"nlwb\",\"nlwd\",\"nlwv\",\"nmbu\",\"nmez\",\"nmja\",\"nmki\",\"nmlq\",\"nmnn\",\"nmom\",\"nmpc\",\"nmrl\",\"nmsm\",\"nmtj\",\"nmvn\",\"nmvv\",\"nmwh\",\"nmwx\",\"nmyi\",\"nmym\",\"nnbs\",\"nncz\",\"nndb\",\"nngh\",\"nnqc\",\"nnra\",\"nnwq\",\"nnwr\",\"nnxn\",\"nnya\",\"nnyp\",\"nnzj\",\"nnzp\",\"noax\",\"nocc\",\"nogi\",\"nogm\",\"nohw\",\"noja\",\"nokg\",\"nolg\",\"nomg\",\"noni\",\"nopj\",\"nopn\",\"nost\",\"novk\",\"nowi\",\"noxb\",\"noxu\",\"npbn\",\"npde\",\"npdv\",\"nped\",\"npeo\",\"npeu\",\"nphy\",\"npjs\",\"nppf\",\"nqbh\",\"nqea\",\"nqem\",\"nqey\",\"nqfu\",\"nqgi\",\"nqki\",\"nqko\",\"nqlq\",\"nqma\",\"nqom\",\"nqqz\",\"nqrp\",\"nqsh\",\"nqum\",\"nqwu\",\"nqzj\",\"nral\",\"nram\",\"nrbs\",\"nrce\",\"nrct\",\"nrcz\",\"nrdc\",\"nrfn\",\"nrfv\",\"nrib\",\"nrkf\",\"nrnr\",\"nroo\",\"nror\",\"nrqb\",\"nrry\",\"nrsi\",\"nrst\",\"nrtw\",\"nruc\",\"nrud\",\"nrui\",\"nrve\",\"nrvq\",\"nrzb\",\"nrzh\",\"nsas\",\"nscw\",\"nsdm\",\"nseu\",\"nsfj\",\"nshw\",\"nsis\",\"nsjr\",\"nskn\",\"nsoy\",\"nspy\",\"nsrf\",\"nsvt\",\"nswg\",\"nsys\",\"ntbh\",\"ntea\",\"ntfc\",\"ntii\",\"ntiq\",\"ntjg\",\"ntli\",\"ntlk\",\"ntne\",\"ntop\",\"ntqw\",\"nttn\",\"ntxs\",\"ntxx\",\"nuax\",\"nugl\",\"nugr\",\"nujc\",\"nukm\",\"nuol\",\"nuos\",\"nuqd\",\"nuta\",\"nuuz\",\"nuvx\",\"nuya\",\"nuzf\",\"nvax\",\"nvba\",\"nvbh\",\"nvda\",\"nveh\",\"nvlc\",\"nvld\",\"nvlk\",\"nvnm\",\"nvrj\",\"nvrs\",\"nvto\",\"nvtt\",\"nvtu\",\"nvuz\",\"nvwc\",\"nvxp\",\"nvyy\",\"nvzy\",\"nwcx\",\"nwdd\",\"nwez\",\"nwge\",\"nwgr\",\"nwgw\",\"nwho\",\"nwjm\",\"nwmd\",\"nwne\",\"nwqb\",\"nwri\",\"nwvv\",\"nwyb\",\"nxbb\",\"nxbc\",\"nxbl\",\"nxci\",\"nxdf\",\"nxgs\",\"nxhj\",\"nxib\",\"nxii\",\"nxjq\",\"nxpz\",\"nxut\",\"nxwp\",\"nxyf\",\"nybo\",\"nyec\",\"nyfv\",\"nygx\",\"nyhq\",\"nyje\",\"nymk\",\"nyqb\",\"nyqo\",\"nyqy\",\"nysz\",\"nyvx\",\"nywh\",\"nywx\",\"nyxx\",\"nyyd\",\"nyyu\",\"nzcb\",\"nzdp\",\"nzhn\",\"nzjz\",\"nzlr\",\"nzlw\",\"nzmd\",\"nzpn\",\"nzqr\",\"nzrt\",\"nzsq\",\"nzto\",\"nzwq\",\"nzya\",\"oaba\",\"oabf\",\"oaeb\",\"oagd\",\"oajk\",\"oakx\",\"oamc\",\"oamf\",\"oasf\",\"oavb\",\"oawa\",\"oaxu\",\"oazd\",\"obbb\",\"obcu\",\"obgr\",\"objm\",\"objp\",\"obkf\",\"oblq\",\"obnw\",\"obpb\",\"obrw\",\"obtd\",\"obyw\",\"ocbn\",\"occg\",\"oces\",\"ocgw\",\"ochc\",\"ockd\",\"ockw\",\"oclr\",\"ocme\",\"ocnn\",\"ocor\",\"octm\",\"ocua\",\"ocuu\",\"ocyw\",\"odar\",\"odas\",\"odat\",\"oddg\",\"oddt\",\"odfc\",\"odfr\",\"odix\",\"odod\",\"odsj\",\"odva\",\"odwf\",\"odwl\",\"odxs\",\"odyj\",\"oeab\",\"oecl\",\"oeen\",\"oefs\",\"oehe\",\"oeib\",\"oeiq\",\"oejf\",\"oejj\",\"oeko\",\"oeli\",\"oene\",\"oeod\",\"oerq\",\"oesj\",\"oetv\",\"oetw\",\"oevi\",\"oevq\",\"oevu\",\"oeya\",\"oeyn\",\"oeyo\",\"oeyx\",\"ofhg\",\"ofhl\",\"ofhm\",\"ofjb\",\"ofli\",\"ofmi\",\"ofms\",\"ofpd\",\"ofrh\",\"ofro\",\"ofte\",\"oftr\",\"oftu\",\"ofvc\",\"ofvg\",\"ofyk\",\"ofzy\",\"ogab\",\"ogaj\",\"ogao\",\"ogau\",\"ogdi\",\"ogdx\",\"ogdz\",\"ogeu\",\"oghj\",\"ogkw\",\"ogll\",\"ognj\",\"ogug\",\"ogvc\",\"ogvs\",\"ogws\",\"ogyb\",\"ogzs\",\"ohay\",\"ohdd\",\"ohgo\",\"ohiz\",\"ohkr\",\"ohks\",\"ohky\",\"ohls\",\"ohni\",\"ohph\",\"ohpx\",\"ohxo\",\"oiak\",\"oidi\",\"oifl\",\"oigc\",\"oiik\",\"oiod\",\"oioh\",\"oiol\",\"oiom\",\"oiqh\",\"oiqq\",\"oiqx\",\"oiqy\",\"oiry\",\"oism\",\"oiss\",\"oiti\",\"oivk\",\"oixy\",\"oiyj\",\"ojae\",\"ojbh\",\"ojbj\",\"ojip\",\"ojiw\",\"ojkr\",\"ojku\",\"ojme\",\"ojmx\",\"ojqe\",\"ojrj\",\"ojtf\",\"ojuy\",\"ojvt\",\"ojwu\",\"ojxx\",\"ojyw\",\"okbt\",\"okgh\",\"okhe\",\"okhj\",\"okhz\",\"okic\",\"okkl\",\"okoh\",\"okoo\",\"oksa\",\"okvn\",\"okxs\",\"okzf\",\"okzs\",\"oldd\",\"olez\",\"olgy\",\"olih\",\"olju\",\"olko\",\"olls\",\"olsd\",\"olus\",\"olvf\",\"omae\",\"ombz\",\"omdm\",\"omee\",\"omeo\",\"omfd\",\"omil\",\"omll\",\"omlz\",\"omtx\",\"omwk\",\"omxa\",\"omxj\",\"omyc\",\"omyn\",\"omzd\",\"onaq\",\"onav\",\"onhg\",\"onjs\",\"onjy\",\"onmf\",\"onrk\",\"onzt\",\"onzw\",\"ooad\",\"ooal\",\"ooay\",\"oodf\",\"oonc\",\"oonf\",\"oonq\",\"ooob\",\"oooz\",\"ooqm\",\"ootb\",\"ooth\",\"oowj\",\"ooyf\",\"opbe\",\"opcf\",\"opdm\",\"opdp\",\"opff\",\"opgs\",\"opgx\",\"opjs\",\"opju\",\"opkp\",\"oplr\",\"opon\",\"opqu\",\"optf\",\"opwj\",\"opzw\",\"oqdu\",\"oqec\",\"oqge\",\"oqhe\",\"oqin\",\"oqio\",\"oqjc\",\"oqjg\",\"oqle\",\"oqlt\",\"oqlx\",\"oqml\",\"oqni\",\"oqnx\",\"oqop\",\"oqsc\",\"oqtn\",\"oqwb\",\"oqyf\",\"orbw\",\"orim\",\"orjo\",\"ormg\",\"ormj\",\"orns\",\"orpp\",\"orsr\",\"ortf\",\"orvp\",\"orxz\",\"orzd\",\"oseb\",\"osem\",\"osjk\",\"oskp\",\"oslj\",\"osoy\",\"osqn\",\"ossl\",\"ossp\",\"ostq\",\"osun\",\"osxl\",\"oszi\",\"otae\",\"otbb\",\"otct\",\"otff\",\"otfj\",\"oths\",\"otia\",\"otij\",\"otjq\",\"otkp\",\"otkw\",\"otlb\",\"otlv\",\"otlz\",\"otpf\",\"otqa\",\"otsj\",\"otui\",\"otyc\",\"otyw\",\"ouap\",\"oucp\",\"oucx\",\"oucy\",\"ougv\",\"ounx\",\"oupd\",\"ouqh\",\"ouqk\",\"ouwe\",\"ouxo\",\"ouys\",\"ovbb\",\"ovbd\",\"ovce\",\"ovjd\",\"ovjn\",\"ovly\",\"ovmx\",\"ovnf\",\"ovni\",\"ovnj\",\"ovnk\",\"ovnu\",\"ovol\",\"ovot\",\"ovqc\",\"ovrd\",\"ovrw\",\"ovvn\",\"ovvz\",\"ovxt\",\"ovzp\",\"owae\",\"owdt\",\"owfg\",\"owid\",\"owme\",\"ownu\",\"owpf\",\"owpv\",\"owqu\",\"owqz\",\"owwa\",\"owzc\",\"owzw\",\"oxam\",\"oxbh\",\"oxbz\",\"oxdo\",\"oxhq\",\"oxhs\",\"oxhy\",\"oxib\",\"oxiy\",\"oxky\",\"oxpx\",\"oxrv\",\"oxui\",\"oxuu\",\"oxvi\",\"oxvx\",\"oxzi\",\"oyab\",\"oycl\",\"oyeu\",\"oyfd\",\"oyfg\",\"oyfr\",\"oyhr\",\"oyja\",\"oykx\",\"oyll\",\"oymc\",\"oymf\",\"oynb\",\"oyoq\",\"oypd\",\"oytg\",\"oyth\",\"oytr\",\"oyuy\",\"oyvm\",\"oywo\",\"oyxc\",\"oyxj\",\"ozao\",\"ozcm\",\"ozdb\",\"ozdh\",\"ozfl\",\"ozfx\",\"ozhr\",\"ozky\",\"ozns\",\"ozpj\",\"ozqr\",\"ozsc\",\"ozsh\",\"ozuk\",\"ozup\",\"ozuv\",\"ozvr\",\"ozxo\",\"ozzb\",\"ozzm\",\"paad\",\"paev\",\"pafs\",\"pagy\",\"pahd\",\"paix\",\"pakc\",\"pakk\",\"pamr\",\"panr\",\"paos\",\"papd\",\"pauv\",\"pawo\",\"paxl\",\"payd\",\"pbbu\",\"pbff\",\"pbfp\",\"pbge\",\"pbiu\",\"pblh\",\"pbnh\",\"pbnt\",\"pbrv\",\"pbva\",\"pbxg\",\"pbzc\",\"pcco\",\"pcei\",\"pcfs\",\"pcft\",\"pcgi\",\"pcgj\",\"pchw\",\"pcjq\",\"pcqi\",\"pcsc\",\"pcsy\",\"pcxu\",\"pczb\",\"pdbr\",\"pdeo\",\"pdpt\",\"pdqq\",\"pdso\",\"pdxz\",\"pebz\",\"peci\",\"pect\",\"pedn\",\"peef\",\"pegf\",\"pegk\",\"peie\",\"peix\",\"pejp\",\"peku\",\"pelj\",\"peln\",\"pemc\",\"pemn\",\"penf\",\"penh\",\"peqg\",\"perh\",\"pevj\",\"pexu\",\"pfby\",\"pfdi\",\"pfdv\",\"pfep\",\"pfhg\",\"pfic\",\"pfih\",\"pfmw\",\"pfpb\",\"pfvq\",\"pfxj\",\"pgfg\",\"pggq\",\"pgli\",\"pgpd\",\"pgrh\",\"pgtk\",\"pguv\",\"pgwj\",\"pgxi\",\"pgym\",\"pgyn\",\"pgzi\",\"phad\",\"phdd\",\"phjo\",\"phjz\",\"phkq\",\"phmb\",\"phmi\",\"phqk\",\"phqv\",\"phrq\",\"phrr\",\"phrs\",\"phsf\",\"phss\",\"phsu\",\"phta\",\"phvi\",\"phyx\",\"phzc\",\"phzt\",\"piab\",\"piar\",\"pias\",\"pibj\",\"pift\",\"pifu\",\"pigh\",\"pihp\",\"pija\",\"pijw\",\"pimu\",\"pisj\",\"pitc\",\"pjah\",\"pjdf\",\"pjed\",\"pjfn\",\"pjgb\",\"pjhm\",\"pjhx\",\"pjin\",\"pjiq\",\"pjir\",\"pjmh\",\"pjpc\",\"pjpm\",\"pjqe\",\"pjsa\",\"pjti\",\"pjut\",\"pjvm\",\"pjvt\",\"pjvz\",\"pjwg\",\"pjwv\",\"pkcq\",\"pkcr\",\"pkif\",\"pkiy\",\"pkiz\",\"pkkj\",\"pkkq\",\"pklj\",\"pknx\",\"pkoi\",\"pkpj\",\"pkrn\",\"pkrp\",\"pksr\",\"pktw\",\"pkuf\",\"pkvf\",\"pkxs\",\"plbl\",\"plca\",\"plct\",\"pldq\",\"plgo\",\"pljx\",\"pljz\",\"plob\",\"plok\",\"plpe\",\"plpk\",\"plpn\",\"plrs\",\"plue\",\"plva\",\"plya\",\"plym\",\"pmfa\",\"pmhk\",\"pmif\",\"pmkx\",\"pmmd\",\"pmmp\",\"pmpa\",\"pmst\",\"pmtm\",\"pmvl\",\"pnba\",\"pngn\",\"pnio\",\"pnlr\",\"pnnh\",\"pnrg\",\"pnsd\",\"pnsk\",\"pntl\",\"pntp\",\"pnzl\",\"pnzx\",\"pobq\",\"podk\",\"podv\",\"poeh\",\"poic\",\"pokm\",\"ponk\",\"pooo\",\"porh\",\"potb\",\"ppau\",\"ppbc\",\"ppbk\",\"ppgq\",\"ppjg\",\"ppju\",\"ppms\",\"pppw\",\"ppri\",\"pprp\",\"ppss\",\"ppvc\",\"ppze\",\"pqct\",\"pqea\",\"pqfp\",\"pqgd\",\"pqhx\",\"pqjz\",\"pqkc\",\"pqkq\",\"pqnf\",\"pqos\",\"pqra\",\"pqrs\",\"pquk\",\"pqvd\",\"pqvh\",\"pqvp\",\"prai\",\"prbd\",\"prca\",\"prgf\",\"prgx\",\"prkk\",\"prkx\",\"prla\",\"prna\",\"prok\",\"prpl\",\"prps\",\"prrm\",\"prsn\",\"prst\",\"prtf\",\"prth\",\"prvg\",\"prvi\",\"prwb\",\"psbp\",\"pseg\",\"psgt\",\"psig\",\"psjc\",\"psjz\",\"psps\",\"pspv\",\"psqj\",\"psrr\",\"pssj\",\"pssm\",\"pstf\",\"psuq\",\"psur\",\"psuy\",\"psvc\",\"pswf\",\"psxn\",\"psyo\",\"pszv\",\"ptan\",\"ptbb\",\"ptbc\",\"ptbz\",\"ptcp\",\"ptiz\",\"ptju\",\"ptmy\",\"ptnk\",\"ptpg\",\"pttj\",\"ptvd\",\"ptvg\",\"ptwr\",\"ptzp\",\"pubh\",\"pubq\",\"puco\",\"puiv\",\"pulr\",\"pulv\",\"pumt\",\"pura\",\"purq\",\"purv\",\"pusb\",\"puso\",\"pusw\",\"puyq\",\"pvca\",\"pvdj\",\"pvez\",\"pvhe\",\"pvnb\",\"pvqj\",\"pvte\",\"pvwl\",\"pvyb\",\"pvzb\",\"pwad\",\"pwdh\",\"pwec\",\"pwex\",\"pwio\",\"pwix\",\"pwse\",\"pwtd\",\"pwvy\",\"pwxf\",\"pxei\",\"pxfz\",\"pxjy\",\"pxod\",\"pxpb\",\"pxpt\",\"pxtk\",\"pxtm\",\"pxun\",\"pxvo\",\"pxvp\",\"pyac\",\"pycy\",\"pydq\",\"pyge\",\"pygk\",\"pyhr\",\"pyjv\",\"pylk\",\"pylo\",\"pymv\",\"pyns\",\"pyzc\",\"pzbo\",\"pzdh\",\"pzfq\",\"pzjq\",\"pznl\",\"pzsc\",\"pzsl\",\"pzuo\",\"pzvt\",\"pzwp\",\"pzym\",\"pzyz\",\"qaaf\",\"qagu\",\"qajq\",\"qajx\",\"qalr\",\"qamj\",\"qapf\",\"qaum\",\"qauq\",\"qauy\",\"qavu\",\"qawa\",\"qaxp\",\"qayl\",\"qazk\",\"qbel\",\"qbez\",\"qbim\",\"qbjs\",\"qblc\",\"qbln\",\"qbma\",\"qbmi\",\"qbnx\",\"qbqq\",\"qbrc\",\"qbss\",\"qbts\",\"qbuh\",\"qbvk\",\"qbvu\",\"qcbp\",\"qcbx\",\"qceo\",\"qcff\",\"qchj\",\"qcip\",\"qcir\",\"qcjj\",\"qcmp\",\"qcnh\",\"qcwx\",\"qdei\",\"qdem\",\"qdhw\",\"qdlt\",\"qdqv\",\"qdrm\",\"qdrq\",\"qdtz\",\"qdux\",\"qdvu\",\"qdyy\",\"qdzk\",\"qead\",\"qecl\",\"qegk\",\"qegt\",\"qeii\",\"qeja\",\"qejs\",\"qeku\",\"qend\",\"qeuq\",\"qevk\",\"qeyd\",\"qeyy\",\"qfdp\",\"qffo\",\"qfgg\",\"qfgl\",\"qfie\",\"qfif\",\"qfix\",\"qfjb\",\"qflw\",\"qflx\",\"qfmg\",\"qfnp\",\"qfpc\",\"qfpm\",\"qfqm\",\"qfrr\",\"qfuj\",\"qfvm\",\"qfwy\",\"qfxv\",\"qfzq\",\"qgbo\",\"qgda\",\"qgfl\",\"qgju\",\"qgnf\",\"qgng\",\"qgnr\",\"qgpv\",\"qgul\",\"qgus\",\"qgvl\",\"qgvm\",\"qgxl\",\"qhao\",\"qhba\",\"qhcm\",\"qhfb\",\"qhfi\",\"qhjq\",\"qhmd\",\"qhmx\",\"qhnh\",\"qhnr\",\"qhoz\",\"qhrg\",\"qhvm\",\"qhwk\",\"qhyq\",\"qhzt\",\"qidc\",\"qier\",\"qijf\",\"qijl\",\"qilp\",\"qipw\",\"qipx\",\"qisq\",\"qitj\",\"qiug\",\"qixg\",\"qixs\",\"qiyd\",\"qjbz\",\"qjdp\",\"qjdu\",\"qjkt\",\"qjmd\",\"qjna\",\"qjnv\",\"qjoc\",\"qjuj\",\"qjvx\",\"qjwz\",\"qjyg\",\"qkcl\",\"qkdc\",\"qkeg\",\"qkgj\",\"qkka\",\"qkko\",\"qkob\",\"qkqe\",\"qkqi\",\"qkte\",\"qkth\",\"qktp\",\"qkun\",\"qkus\",\"qkwi\",\"qkyq\",\"qkzd\",\"qlbb\",\"qlcc\",\"qles\",\"qlhj\",\"qlhq\",\"qlib\",\"qlid\",\"qliv\",\"qlma\",\"qlmi\",\"qlpk\",\"qlql\",\"qltt\",\"qmag\",\"qmff\",\"qmmc\",\"qmpv\",\"qmqf\",\"qmqt\",\"qmrg\",\"qmtg\",\"qmts\",\"qmyu\",\"qmzd\",\"qnbt\",\"qndu\",\"qnil\",\"qnjh\",\"qnov\",\"qnta\",\"qnth\",\"qntu\",\"qnyh\",\"qnyu\",\"qnzt\",\"qobq\",\"qocv\",\"qodd\",\"qodi\",\"qofv\",\"qojq\",\"qona\",\"qonj\",\"qooz\",\"qora\",\"qowr\",\"qoye\",\"qoyz\",\"qpdh\",\"qpdo\",\"qpfw\",\"qpgx\",\"qphv\",\"qpig\",\"qpmn\",\"qpni\",\"qpop\",\"qpqs\",\"qpsh\",\"qptl\",\"qpup\",\"qpxk\",\"qpyy\",\"qqay\",\"qqbt\",\"qqea\",\"qqfa\",\"qqgs\",\"qqks\",\"qqmb\",\"qqnq\",\"qqnt\",\"qqog\",\"qqok\",\"qqpe\",\"qqub\",\"qqzr\",\"qqzz\",\"qrav\",\"qrck\",\"qrct\",\"qrfj\",\"qrhv\",\"qrji\",\"qrkp\",\"qrnh\",\"qrnw\",\"qrpg\",\"qrtl\",\"qruf\",\"qruw\",\"qrvq\",\"qrvs\",\"qsbr\",\"qsci\",\"qsdf\",\"qsdh\",\"qsfa\",\"qsfb\",\"qsht\",\"qsij\",\"qslr\",\"qsqf\",\"qsvb\",\"qsvx\",\"qswz\",\"qsxk\",\"qsyv\",\"qsze\",\"qtap\",\"qtcx\",\"qtdm\",\"qtdu\",\"qtel\",\"qtfl\",\"qtgv\",\"qthc\",\"qtiu\",\"qtlh\",\"qtoi\",\"qtte\",\"qttm\",\"qtuk\",\"qtxg\",\"qtxl\",\"quau\",\"qubr\",\"qudc\",\"queh\",\"quer\",\"qufd\",\"quhh\",\"quhi\",\"quje\",\"qujl\",\"qule\",\"qulk\",\"qult\",\"qunm\",\"quoo\",\"quoy\",\"quqm\",\"qurb\",\"qurj\",\"qury\",\"qusz\",\"quwg\",\"quxg\",\"quxj\",\"qvca\",\"qvdm\",\"qveq\",\"qvfc\",\"qvfx\",\"qvgp\",\"qvhh\",\"qvlj\",\"qvlk\",\"qvlq\",\"qvmz\",\"qvsz\",\"qvtg\",\"qvve\",\"qvvq\",\"qvvz\",\"qvyh\",\"qvzf\",\"qwbd\",\"qwbv\",\"qwck\",\"qwda\",\"qwdr\",\"qwfm\",\"qwhw\",\"qwoc\",\"qwoq\",\"qwqd\",\"qwve\",\"qxbh\",\"qxbj\",\"qxcs\",\"qxcw\",\"qxed\",\"qxhg\",\"qxjd\",\"qxoa\",\"qxot\",\"qxpo\",\"qxqr\",\"qxrn\",\"qxuy\",\"qxve\",\"qxxv\",\"qyjr\",\"qykv\",\"qylk\",\"qymn\",\"qyqv\",\"qyvk\",\"qywk\",\"qzaa\",\"qzdk\",\"qzgf\",\"qzgx\",\"qzik\",\"qzmr\",\"qznr\",\"qzou\",\"qzsf\",\"qzsr\",\"qztw\",\"qzue\",\"qzwa\",\"qzwy\",\"qzyr\",\"qzzv\",\"raat\",\"rabi\",\"racm\",\"rago\",\"rahr\",\"rait\",\"ramg\",\"ranb\",\"raom\",\"rapz\",\"rats\",\"raue\",\"raxm\",\"rayp\",\"rayq\",\"rbal\",\"rbaw\",\"rbbk\",\"rbcz\",\"rbdr\",\"rbeg\",\"rbfx\",\"rbgs\",\"rbjh\",\"rbjn\",\"rbjw\",\"rbqe\",\"rbqx\",\"rbtc\",\"rbvv\",\"rbxm\",\"rbxp\",\"rbxv\",\"rbya\",\"rbzy\",\"rcae\",\"rcay\",\"rccf\",\"rcfa\",\"rcfd\",\"rcin\",\"rcjx\",\"rcmv\",\"rcoq\",\"rcqx\",\"rcts\",\"rcvb\",\"rcvn\",\"rcwm\",\"rcxs\",\"rcyn\",\"rczw\",\"rczy\",\"rdae\",\"rdar\",\"rdcf\",\"rdcn\",\"rdec\",\"rdgd\",\"rdhz\",\"rdjm\",\"rdjr\",\"rdjt\",\"rdjz\",\"rdme\",\"rdnz\",\"rdoc\",\"rdow\",\"rdqg\",\"rdrc\",\"rdtz\",\"rdue\",\"rduf\",\"rduh\",\"rdva\",\"rdzw\",\"rdzy\",\"reaz\",\"rebb\",\"rebl\",\"redn\",\"refp\",\"regw\",\"rems\",\"reoc\",\"repk\",\"rete\",\"retr\",\"reyb\",\"reyd\",\"reyi\",\"reyx\",\"rfac\",\"rffp\",\"rfgo\",\"rfkk\",\"rfqk\",\"rfql\",\"rfsk\",\"rftc\",\"rftj\",\"rftn\",\"rfud\",\"rfvv\",\"rfxp\",\"rfyc\",\"rfzx\",\"rgbb\",\"rgdh\",\"rgdo\",\"rgfn\",\"rgnc\",\"rgof\",\"rgpj\",\"rgrn\",\"rgsb\",\"rgsn\",\"rgso\",\"rgtr\",\"rgtv\",\"rgui\",\"rgva\",\"rhac\",\"rhbw\",\"rhca\",\"rhcf\",\"rheh\",\"rhel\",\"rhjf\",\"rhkb\",\"rhkm\",\"rhmc\",\"rhmp\",\"rhqv\",\"rhqz\",\"rhvu\",\"rhxy\",\"rhya\",\"rhyk\",\"ribw\",\"ricp\",\"rifn\",\"rihq\",\"rihz\",\"rijr\",\"rikf\",\"rimj\",\"rioz\",\"ripl\",\"rirx\",\"riuj\",\"rixp\",\"riyq\",\"rizr\",\"rjao\",\"rjap\",\"rjgi\",\"rjio\",\"rjmg\",\"rjrg\",\"rjsp\",\"rjva\",\"rjwq\",\"rjyi\",\"rkcb\",\"rkcc\",\"rkcd\",\"rkcv\",\"rkhr\",\"rkib\",\"rklw\",\"rkmo\",\"rkmy\",\"rkoz\",\"rkph\",\"rkqp\",\"rktc\",\"rkuh\",\"rkve\",\"rkxx\",\"rlae\",\"rlaq\",\"rlfg\",\"rlit\",\"rllb\",\"rlny\",\"rlrl\",\"rlsv\",\"rltf\",\"rlun\",\"rluu\",\"rlvn\",\"rlyr\",\"rmav\",\"rmgf\",\"rmiy\",\"rmla\",\"rmmj\",\"rmpb\",\"rmps\",\"rmqi\",\"rmqq\",\"rmrn\",\"rnbn\",\"rncx\",\"rndw\",\"rnmv\",\"rnof\",\"rnpk\",\"rnqe\",\"rnra\",\"rnri\",\"rnrp\",\"rnst\",\"rntr\",\"rntv\",\"rnuf\",\"rnuo\",\"rnvb\",\"rnzo\",\"rofn\",\"rohk\",\"rohx\",\"roix\",\"ropc\",\"rosk\",\"rouo\",\"rovo\",\"roxx\",\"rpbo\",\"rpdq\",\"rpmf\",\"rpnq\",\"rpoj\",\"rpor\",\"rpsc\",\"rpsw\",\"rpsx\",\"rpta\",\"rpux\",\"rpva\",\"rpxt\",\"rpzq\",\"rqav\",\"rqcg\",\"rqlx\",\"rqlz\",\"rqok\",\"rqqb\",\"rqvb\",\"rqwv\",\"rqyp\",\"rqze\",\"rrbq\",\"rrex\",\"rrib\",\"rrih\",\"rrkr\",\"rrnr\",\"rrok\",\"rrpr\",\"rrst\",\"rrvh\",\"rrzw\",\"rsai\",\"rsef\",\"rsfj\",\"rsgc\",\"rsgn\",\"rshl\",\"rsjs\",\"rskr\",\"rspa\",\"rssr\",\"rsuq\",\"rsuv\",\"rsvj\",\"rtcs\",\"rtdj\",\"rtee\",\"rteh\",\"rtfg\",\"rtgg\",\"rthu\",\"rtii\",\"rtil\",\"rtkj\",\"rttg\",\"rtwo\",\"rtws\",\"rtwv\",\"rtyx\",\"rubd\",\"rudu\",\"rufx\",\"ruhe\",\"ruht\",\"rulg\",\"rumg\",\"rumq\",\"ruqw\",\"rurj\",\"rute\",\"ruty\",\"ruue\",\"ruvt\",\"ruwg\",\"rvac\",\"rvcq\",\"rvem\",\"rveu\",\"rvif\",\"rvle\",\"rvln\",\"rvlu\",\"rvod\",\"rvpo\",\"rvpy\",\"rvrp\",\"rvsj\",\"rvsn\",\"rvti\",\"rvtk\",\"rvxo\",\"rwdz\",\"rwez\",\"rwft\",\"rwik\",\"rwkk\",\"rwlk\",\"rwoo\",\"rwpd\",\"rwtc\",\"rwuy\",\"rwvx\",\"rwwk\",\"rwwn\",\"rwxu\",\"rwym\",\"rxbl\",\"rxbq\",\"rxbr\",\"rxct\",\"rxez\",\"rxfh\",\"rxga\",\"rxgm\",\"rxpg\",\"rxqm\",\"rxqq\",\"rxta\",\"rxte\",\"rxul\",\"rxvu\",\"rxxp\",\"rxzr\",\"ryad\",\"rybw\",\"ryci\",\"rycl\",\"rydw\",\"ryes\",\"ryhv\",\"ryio\",\"ryiv\",\"ryjv\",\"rykk\",\"rykw\",\"rylr\",\"rymh\",\"rypn\",\"ryqh\",\"ryqx\",\"ryth\",\"rytu\",\"ryvv\",\"rywl\",\"rywo\",\"rywx\",\"ryyl\",\"ryyq\",\"ryzz\",\"rzap\",\"rzaq\",\"rzba\",\"rzbp\",\"rzct\",\"rzdi\",\"rzif\",\"rzii\",\"rzij\",\"rzip\",\"rzit\",\"rzjd\",\"rzkl\",\"rzoy\",\"rzqo\",\"rzub\",\"rzwf\",\"rzwi\",\"rzxo\",\"rzzg\",\"saev\",\"sakw\",\"sanl\",\"sapk\",\"saqw\",\"saqx\",\"saxl\",\"sayl\",\"sbah\",\"sbck\",\"sbco\",\"sbfs\",\"sbiw\",\"sbiz\",\"sbjm\",\"sbor\",\"sbpy\",\"sbul\",\"sbux\",\"sbvs\",\"sbzj\",\"sbzl\",\"scah\",\"scai\",\"scas\",\"scem\",\"scfx\",\"scgl\",\"schr\",\"schy\",\"scjr\",\"scjz\",\"scle\",\"scvf\",\"scvn\",\"scwd\",\"scxb\",\"sdas\",\"sddw\",\"sdec\",\"sdkd\",\"sdng\",\"sdno\",\"sdnr\",\"sdnt\",\"sdoh\",\"sdpu\",\"sdrq\",\"sdze\",\"seda\",\"sedh\",\"sedv\",\"segn\",\"sehg\",\"sehi\",\"seib\",\"seix\",\"sekk\",\"semr\",\"seno\",\"sesh\",\"sfax\",\"sfdw\",\"sffc\",\"sffn\",\"sfhz\",\"sfkh\",\"sfkp\",\"sfmf\",\"sfqj\",\"sfql\",\"sfsl\",\"sfsm\",\"sfue\",\"sfur\",\"sfuy\",\"sfvx\",\"sfwl\",\"sfxr\",\"sgab\",\"sgbe\",\"sgjd\",\"sgmj\",\"sgml\",\"sgmz\",\"sgnv\",\"sgpc\",\"sgpo\",\"sgrz\",\"sgtb\",\"sguk\",\"sgvg\",\"sgvp\",\"sgwr\",\"sgws\",\"sgwt\",\"sgyz\",\"sgzv\",\"shdu\",\"sheh\",\"shia\",\"shjb\",\"shjc\",\"shln\",\"shmq\",\"shmz\",\"shon\",\"shqn\",\"shrx\",\"shsv\",\"shta\",\"shum\",\"shwu\",\"shxi\",\"shzv\",\"sidk\",\"sidl\",\"sidx\",\"sifl\",\"sihf\",\"siiq\",\"sinq\",\"siqj\",\"siql\",\"siru\",\"sivk\",\"siyk\",\"siyt\",\"sizp\",\"sjcy\",\"sjds\",\"sjea\",\"sjhg\",\"sjjy\",\"sjlt\",\"sjmd\",\"sjns\",\"sjql\",\"sjtj\",\"sjui\",\"sjuo\",\"sjuv\",\"sjwt\",\"sjxu\",\"sjye\",\"skbh\",\"skcd\",\"skdo\",\"skdx\",\"sket\",\"skfg\",\"skjj\",\"skpf\",\"sktd\",\"skus\",\"skvw\",\"skwm\",\"skzp\",\"slal\",\"slan\",\"slcd\",\"sldd\",\"slgf\",\"slio\",\"slox\",\"slqn\",\"slqo\",\"slqs\",\"slrr\",\"slsr\",\"slxf\",\"slym\",\"slyr\",\"smbp\",\"smdv\",\"smel\",\"smfu\",\"smhm\",\"smid\",\"smin\",\"smlh\",\"smlz\",\"smob\",\"smoq\",\"smpn\",\"smpv\",\"smqa\",\"smtg\",\"smwu\",\"smzl\",\"sncm\",\"snfi\",\"snhl\",\"snkc\",\"snlx\",\"snnl\",\"snnm\",\"snny\",\"snqq\",\"snqs\",\"snsm\",\"snsv\",\"snue\",\"snwi\",\"snwm\",\"snyo\",\"snyu\",\"snyz\",\"soag\",\"soas\",\"sobb\",\"soet\",\"sohq\",\"soij\",\"sojh\",\"sonw\",\"sopi\",\"soqs\",\"sork\",\"sowj\",\"soxc\",\"sozy\",\"spap\",\"spbm\",\"spdc\",\"spdw\",\"spev\",\"spfr\",\"splj\",\"spmz\",\"spra\",\"spsn\",\"spsz\",\"sptp\",\"spuu\",\"spvn\",\"sqax\",\"sqay\",\"sqdg\",\"sqeh\",\"sqjx\",\"sqou\",\"sqqq\",\"sqrd\",\"sqsm\",\"sqzf\",\"srak\",\"sram\",\"srbh\",\"srcw\",\"srda\",\"srhv\",\"srjb\",\"srlo\",\"srls\",\"sros\",\"srpe\",\"srxc\",\"sryt\",\"srzu\",\"sscg\",\"sscu\",\"ssfn\",\"ssfr\",\"ssgj\",\"ssmh\",\"ssmt\",\"ssol\",\"ssse\",\"sstj\",\"ssuj\",\"sszm\",\"stau\",\"stbv\",\"stfh\",\"sthq\",\"sthx\",\"stjz\",\"stwb\",\"stxn\",\"subi\",\"sudx\",\"sudz\",\"sufu\",\"sugq\",\"suiw\",\"sujf\",\"sumi\",\"summ\",\"sumo\",\"sunx\",\"supq\",\"suvq\",\"suxo\",\"svas\",\"svbk\",\"svcd\",\"svgr\",\"svie\",\"sviu\",\"svjm\",\"svjx\",\"svna\",\"svpq\",\"svqc\",\"svrt\",\"svtf\",\"svwb\",\"swbd\",\"swqv\",\"swrx\",\"swsw\",\"swte\",\"swvf\",\"swwf\",\"swxg\",\"sxak\",\"sxam\",\"sxbg\",\"sxdr\",\"sxiw\",\"sxkp\",\"sxmq\",\"sxqb\",\"sxun\",\"sxuo\",\"sxxz\",\"sxzf\",\"sxzr\",\"syat\",\"sybh\",\"syct\",\"syfq\",\"syfv\",\"sygl\",\"syid\",\"syir\",\"synq\",\"syoj\",\"syou\",\"sypc\",\"syqf\",\"syqp\",\"syra\",\"syrf\",\"syue\",\"sywg\",\"syxy\",\"syzu\",\"szam\",\"szdc\",\"szee\",\"szgb\",\"szlv\",\"szmc\",\"szod\",\"szoj\",\"szqj\",\"tage\",\"tagy\",\"taie\",\"taiq\",\"tajg\",\"tako\",\"tamk\",\"tapj\",\"tapv\",\"tast\",\"tasw\",\"tatt\",\"tayw\",\"tayx\",\"tazt\",\"tbcv\",\"tbhg\",\"tbjc\",\"tbjm\",\"tbkf\",\"tblh\",\"tbnn\",\"tbqq\",\"tbsa\",\"tbtj\",\"tbtr\",\"tbwj\",\"tbwy\",\"tbxa\",\"tbxp\",\"tbyc\",\"tcaf\",\"tcao\",\"tcbj\",\"tcbn\",\"tcbo\",\"tcen\",\"tcgg\",\"tchc\",\"tchv\",\"tcji\",\"tckc\",\"tcms\",\"tcrl\",\"tcsc\",\"tcvi\",\"tcwp\",\"tcyn\",\"tcyp\",\"tdat\",\"tdbp\",\"tdde\",\"tdfj\",\"tdfw\",\"tdiv\",\"tdji\",\"tdkk\",\"tdlc\",\"tdld\",\"tdlr\",\"tdmt\",\"tdod\",\"tdoy\",\"tdsf\",\"tdvp\",\"tdvt\",\"tdyf\",\"teab\",\"tebc\",\"tecc\",\"tefw\",\"teid\",\"tejo\",\"tejs\",\"temf\",\"tepd\",\"teqq\",\"teqy\",\"teue\",\"teuu\",\"tewc\",\"tewu\",\"tewz\",\"texb\",\"tfbn\",\"tfcy\",\"tfew\",\"tffu\",\"tfmo\",\"tfpb\",\"tfpm\",\"tfqy\",\"tfrw\",\"tfry\",\"tfzi\",\"tfzo\",\"tgfo\",\"tgjo\",\"tgkv\",\"tgqm\",\"tgss\",\"tguz\",\"tgws\",\"tgxb\",\"tgxv\",\"tgyg\",\"tgyv\",\"tgzy\",\"thch\",\"thdb\",\"thdu\",\"thet\",\"thfn\",\"thgy\",\"thim\",\"thkh\",\"thmg\",\"thpx\",\"thsv\",\"thtk\",\"thtw\",\"thua\",\"thvt\",\"thwy\",\"tiau\",\"ticw\",\"tiez\",\"tifa\",\"tigg\",\"tiiz\",\"tije\",\"tikz\",\"tipc\",\"tirm\",\"tirs\",\"tirw\",\"tisu\",\"tith\",\"tity\",\"tiuk\",\"tivv\",\"tiwy\",\"tixm\",\"tjag\",\"tjbs\",\"tjbt\",\"tjcp\",\"tjey\",\"tjhb\",\"tjiu\",\"tjjh\",\"tjjl\",\"tjll\",\"tjly\",\"tjqx\",\"tjtp\",\"tjuh\",\"tkbc\",\"tkic\",\"tkid\",\"tkkx\",\"tkli\",\"tklp\",\"tkpe\",\"tkqp\",\"tkrf\",\"tkrl\",\"tkrw\",\"tkyx\",\"tkzg\",\"tlcz\",\"tldf\",\"tldm\",\"tlgh\",\"tlke\",\"tlmi\",\"tlnr\",\"tlqn\",\"tlry\",\"tluc\",\"tlvn\",\"tmco\",\"tmee\",\"tmfh\",\"tmjt\",\"tmkm\",\"tmkn\",\"tmlk\",\"tmmi\",\"tmmr\",\"tmno\",\"tmqh\",\"tmqm\",\"tmro\",\"tmry\",\"tmsh\",\"tmvn\",\"tmxi\",\"tmxj\",\"tmxx\",\"tmyj\",\"tnad\",\"tncj\",\"tndi\",\"tnmb\",\"tnpn\",\"tnrw\",\"tnxl\",\"tnyq\",\"toes\",\"tolr\",\"toma\",\"topg\",\"torr\",\"toul\",\"toyh\",\"toys\",\"tpan\",\"tpau\",\"tpbe\",\"tpdx\",\"tpes\",\"tpfr\",\"tpgp\",\"tphc\",\"tphg\",\"tphv\",\"tpip\",\"tpkp\",\"tpmp\",\"tpow\",\"tpoy\",\"tppm\",\"tppo\",\"tpps\",\"tpqp\",\"tpsy\",\"tptf\",\"tpto\",\"tpuc\",\"tpza\",\"tpzk\",\"tqbf\",\"tqbq\",\"tqbz\",\"tqcf\",\"tqdi\",\"tqhw\",\"tqlo\",\"tqlv\",\"tqrg\",\"tqrk\",\"tqrz\",\"tqtz\",\"tquw\",\"tqyd\",\"tqza\",\"trai\",\"trdp\",\"trge\",\"trgk\",\"trjo\",\"trjs\",\"trma\",\"trmh\",\"trmn\",\"troo\",\"trry\",\"trsw\",\"trtp\",\"truj\",\"truq\",\"trux\",\"truz\",\"trvz\",\"trwo\",\"trxg\",\"trxw\",\"tscy\",\"tseg\",\"tsfa\",\"tsfh\",\"tsfq\",\"tsif\",\"tsjj\",\"tskb\",\"tsmx\",\"tsno\",\"tsom\",\"tsor\",\"tsql\",\"tsuj\",\"tsum\",\"tsux\",\"tsvz\",\"tswu\",\"tswz\",\"ttbv\",\"ttef\",\"tthw\",\"ttji\",\"ttkn\",\"ttlj\",\"ttnv\",\"ttoe\",\"ttor\",\"ttsi\",\"ttsk\",\"ttsp\",\"ttya\",\"ttyc\",\"ttyr\",\"tubo\",\"tubp\",\"tufc\",\"tufk\",\"tufp\",\"tugp\",\"tugu\",\"tuhx\",\"tuic\",\"tujl\",\"tukz\",\"tunb\",\"tunz\",\"tuph\",\"tuvf\",\"tuvm\",\"tuvn\",\"tvap\",\"tvcr\",\"tver\",\"tvez\",\"tvhb\",\"tvko\",\"tvng\",\"tvqd\",\"tvqp\",\"tvub\",\"tvud\",\"tvuu\",\"tvwq\",\"tvxc\",\"tvxn\",\"tvxt\",\"tvyf\",\"twbj\",\"twbq\",\"twcr\",\"twou\",\"twpc\",\"twqs\",\"twsj\",\"twso\",\"twwv\",\"twwy\",\"twxi\",\"txbe\",\"txfk\",\"txhc\",\"txir\",\"txjv\",\"txkz\",\"txlg\",\"txnl\",\"txnn\",\"txpd\",\"txqn\",\"txrj\",\"txuv\",\"txvw\",\"tyau\",\"tyct\",\"tydl\",\"tyeh\",\"tyfu\",\"tygb\",\"tyjy\",\"tykd\",\"tync\",\"tyqq\",\"tyrv\",\"tyui\",\"tyur\",\"tyvm\",\"tyvs\",\"tyxh\",\"tyyh\",\"tzae\",\"tzax\",\"tzby\",\"tzcn\",\"tzcs\",\"tzef\",\"tzgi\",\"tzgv\",\"tzhb\",\"tzhd\",\"tzii\",\"tzjj\",\"tzjl\",\"tzkc\",\"tzoa\",\"tzou\",\"tzqp\",\"tzrh\",\"tztu\",\"tztw\",\"tzvg\",\"tzvo\",\"uabi\",\"uabn\",\"uafd\",\"uagi\",\"uaha\",\"uahd\",\"uahk\",\"uajr\",\"uals\",\"uaml\",\"uamv\",\"uanj\",\"uaog\",\"uaqb\",\"uask\",\"uawv\",\"uaxo\",\"uazb\",\"ubab\",\"ubaz\",\"ubcu\",\"ubhe\",\"ubhr\",\"ubib\",\"ubis\",\"ubkb\",\"ubkk\",\"ubkw\",\"ubno\",\"ubsq\",\"ubue\",\"ubxi\",\"ubxu\",\"ucaz\",\"ucbg\",\"uchf\",\"ucjc\",\"ucjn\",\"uclg\",\"ucnx\",\"ucok\",\"ucov\",\"ucqs\",\"ucrw\",\"ucwv\",\"ucxx\",\"ucym\",\"ucyu\",\"udad\",\"udbo\",\"udbs\",\"udcm\",\"uddz\",\"uden\",\"udev\",\"udff\",\"udfo\",\"udfx\",\"udgp\",\"udit\",\"udos\",\"udqd\",\"udqh\",\"udtb\",\"udtz\",\"udud\",\"udvq\",\"udwn\",\"udwt\",\"uebp\",\"uedz\",\"uefp\",\"ueia\",\"uejf\",\"uejj\",\"uepy\",\"uetf\",\"ueuj\",\"uexr\",\"ufaf\",\"ufay\",\"ufdr\",\"uffq\",\"uffv\",\"ufhg\",\"ufkf\",\"ufls\",\"uflz\",\"ufnr\",\"ufpg\",\"ufpw\",\"ufsr\",\"ufsz\",\"ufur\",\"ufvq\",\"ufww\",\"ufyn\",\"ufza\",\"ugar\",\"ugav\",\"ugco\",\"uggs\",\"ughh\",\"ugmq\",\"ugnp\",\"ugnz\",\"ugot\",\"ugtf\",\"uguc\",\"ugxd\",\"ugyu\",\"ugzo\",\"uhbs\",\"uhbw\",\"uhcs\",\"uhcv\",\"uhkj\",\"uhkq\",\"uhpg\",\"uhpi\",\"uhql\",\"uhqn\",\"uhsf\",\"uhvj\",\"uiaq\",\"uics\",\"uidc\",\"uigf\",\"uili\",\"uilz\",\"uind\",\"uitr\",\"uiux\",\"uivs\",\"uizj\",\"ujar\",\"ujcb\",\"ujcv\",\"ujcz\",\"ujez\",\"ujfm\",\"ujkq\",\"ujky\",\"ujod\",\"ujur\",\"ujut\",\"ujwa\",\"ujwh\",\"ujxd\",\"ujyn\",\"ukaa\",\"ukah\",\"ukas\",\"ukfr\",\"ukfv\",\"ukgm\",\"ukhy\",\"ukjc\",\"uknw\",\"ukpg\",\"ukre\",\"ukwk\",\"ukxm\",\"ukxv\",\"ukzu\",\"ulal\",\"ulbb\",\"ulbc\",\"ulbr\",\"uldj\",\"ulha\",\"ulhq\",\"ulmm\",\"ulok\",\"ulom\",\"ulpg\",\"ulvx\",\"ulwh\",\"ulws\",\"umbf\",\"umbz\",\"umem\",\"umfi\",\"umga\",\"umgh\",\"umjv\",\"umlj\",\"umlk\",\"umlr\",\"ummu\",\"umom\",\"umxm\",\"unab\",\"unac\",\"unar\",\"uncu\",\"unev\",\"unfk\",\"unfn\",\"unmz\",\"unne\",\"unox\",\"unph\",\"unrl\",\"unsd\",\"unss\",\"unvg\",\"unvt\",\"unxr\",\"uoam\",\"uodf\",\"uoho\",\"uokq\",\"uomf\",\"uomp\",\"uood\",\"uopm\",\"uoql\",\"uotg\",\"upar\",\"upep\",\"upfi\",\"upke\",\"upkt\",\"upln\",\"uppl\",\"upqi\",\"upry\",\"upst\",\"uptt\",\"upxf\",\"upxs\",\"upzp\",\"uqej\",\"uqel\",\"uqgr\",\"uqgz\",\"uqib\",\"uqik\",\"uqjf\",\"uqkh\",\"uqmc\",\"uqok\",\"uqot\",\"uqvf\",\"uqvj\",\"uqxn\",\"uqyn\",\"uqyu\",\"uqyw\",\"urak\",\"urce\",\"urhb\",\"urnh\",\"uroy\",\"uroz\",\"urpx\",\"ursd\",\"ursn\",\"urtl\",\"uruf\",\"urui\",\"uruj\",\"urwj\",\"usbr\",\"usey\",\"usgb\",\"usgp\",\"usgz\",\"usjo\",\"uslm\",\"uslu\",\"usmb\",\"usmp\",\"usog\",\"usvm\",\"usyd\",\"uszz\",\"utdu\",\"utef\",\"utie\",\"utjg\",\"utjp\",\"utlx\",\"utow\",\"utpc\",\"utsb\",\"utse\",\"utsh\",\"utui\",\"utvj\",\"utwh\",\"utzc\",\"utzv\",\"uuak\",\"uucz\",\"uudl\",\"uuhj\",\"uuia\",\"uujs\",\"uumr\",\"uupi\",\"uupm\",\"uuti\",\"uutl\",\"uuur\",\"uuzi\",\"uuzp\",\"uvaj\",\"uvbf\",\"uvcx\",\"uvdj\",\"uveq\",\"uvet\",\"uvfg\",\"uvfx\",\"uvhu\",\"uvjp\",\"uvok\",\"uvpn\",\"uvsl\",\"uvsr\",\"uvuv\",\"uvvg\",\"uvwf\",\"uvww\",\"uwbx\",\"uwcn\",\"uwdg\",\"uwdv\",\"uwff\",\"uwhu\",\"uwjq\",\"uwkl\",\"uwmr\",\"uwny\",\"uwsj\",\"uwte\",\"uwul\",\"uwuz\",\"uwvh\",\"uwvv\",\"uwxn\",\"uxce\",\"uxdo\",\"uxel\",\"uxez\",\"uxft\",\"uxio\",\"uxir\",\"uxjs\",\"uxjz\",\"uxmo\",\"uxoj\",\"uxrc\",\"uxsv\",\"uxyo\",\"uxyt\",\"uyah\",\"uyav\",\"uyaz\",\"uycg\",\"uydt\",\"uyge\",\"uygj\",\"uyhf\",\"uyia\",\"uyoj\",\"uyrm\",\"uyuh\",\"uyxf\",\"uyyi\",\"uzac\",\"uzap\",\"uzbn\",\"uzcp\",\"uzcr\",\"uzdz\",\"uzhg\",\"uzik\",\"uzlh\",\"uzrb\",\"uzvr\",\"uzxm\",\"uzxn\",\"uzyv\",\"vabs\",\"vagk\",\"vagn\",\"vajl\",\"varo\",\"vars\",\"vauo\",\"vawa\",\"vbam\",\"vban\",\"vbay\",\"vbdl\",\"vbdu\",\"vbfq\",\"vbki\",\"vbps\",\"vbtz\",\"vbvv\",\"vbwd\",\"vbwn\",\"vbxs\",\"vbxy\",\"vbzn\",\"vcbg\",\"vcbj\",\"vcfs\",\"vcfw\",\"vcha\",\"vcju\",\"vclh\",\"vcmx\",\"vcnd\",\"vcob\",\"vcps\",\"vcsl\",\"vctd\",\"vcvf\",\"vcwm\",\"vcyj\",\"vcyv\",\"vdag\",\"vdbl\",\"vdcc\",\"vddp\",\"vdkg\",\"vdlq\",\"vdnc\",\"vdsg\",\"vdsv\",\"vdul\",\"vdvl\",\"vdvz\",\"vdxe\",\"vdze\",\"vecd\",\"vedb\",\"vego\",\"vegr\",\"vegv\",\"vekk\",\"velh\",\"veoa\",\"veqq\",\"verf\",\"veuv\",\"vewq\",\"vexj\",\"veye\",\"vfag\",\"vfdu\",\"vffb\",\"vffl\",\"vfgt\",\"vfiz\",\"vfnd\",\"vfob\",\"vfpu\",\"vfqp\",\"vfrg\",\"vfrh\",\"vfts\",\"vfvj\",\"vfyc\",\"vgbq\",\"vgkq\",\"vgkr\",\"vgnl\",\"vgqp\",\"vgsv\",\"vgsw\",\"vgvx\",\"vgzb\",\"vgzc\",\"vhbt\",\"vhbv\",\"vhdr\",\"vhfi\",\"vhfs\",\"vhgn\",\"vhkp\",\"vhtc\",\"vhwe\",\"vhyn\",\"vibo\",\"vidm\",\"vije\",\"vijq\",\"vind\",\"viok\",\"vioo\",\"vitf\",\"viwu\",\"vixh\",\"vixt\",\"viya\",\"vjbg\",\"vjdc\",\"vjdn\",\"vjdw\",\"vjdz\",\"vjhg\",\"vjhm\",\"vjiu\",\"vjjo\",\"vjkc\",\"vjmk\",\"vjng\",\"vjou\",\"vjoy\",\"vjpe\",\"vjqn\",\"vjrc\",\"vjrr\",\"vjsi\",\"vjsx\",\"vjxt\",\"vkaa\",\"vkcc\",\"vkdn\",\"vkft\",\"vkid\",\"vkmm\",\"vknq\",\"vkqx\",\"vksd\",\"vkxv\",\"vkyj\",\"vlcy\",\"vlda\",\"vleo\",\"vlid\",\"vlku\",\"vllu\",\"vlmi\",\"vlmj\",\"vltc\",\"vltn\",\"vltq\",\"vlwd\",\"vlyj\",\"vlzo\",\"vlzp\",\"vmas\",\"vmfb\",\"vmfr\",\"vmhr\",\"vmhu\",\"vmlh\",\"vmly\",\"vmnb\",\"vmnq\",\"vmra\",\"vmtz\",\"vmui\",\"vmyv\",\"vmyw\",\"vnai\",\"vnas\",\"vnfn\",\"vnhc\",\"vnhk\",\"vnhp\",\"vnke\",\"vnmu\",\"vnqb\",\"vnqo\",\"vntk\",\"vnwx\",\"vnxa\",\"vnyk\",\"vobo\",\"voby\",\"vocc\",\"voco\",\"vocu\",\"vofi\",\"vofo\",\"voga\",\"voif\",\"vojp\",\"vokw\",\"vomt\",\"vopn\",\"voqt\",\"vory\",\"votf\",\"voua\",\"vouc\",\"vowg\",\"vozx\",\"vpan\",\"vpcw\",\"vpev\",\"vpkg\",\"vple\",\"vpnz\",\"vpoo\",\"vpqw\",\"vprh\",\"vptm\",\"vpvq\",\"vpwg\",\"vpzp\",\"vqcj\",\"vqeb\",\"vqfc\",\"vqgt\",\"vqkk\",\"vqmq\",\"vqnv\",\"vqoy\",\"vqpm\",\"vqpp\",\"vqug\",\"vqva\",\"vqvw\",\"vqww\",\"vrba\",\"vrbw\",\"vrev\",\"vrfu\",\"vrfx\",\"vrhy\",\"vrna\",\"vror\",\"vrpw\",\"vrrq\",\"vrrs\",\"vrvb\",\"vrxa\",\"vsca\",\"vsel\",\"vsfn\",\"vsfr\",\"vshi\",\"vsjf\",\"vsji\",\"vsli\",\"vslr\",\"vspa\",\"vsql\",\"vsrr\",\"vsrz\",\"vssm\",\"vsvl\",\"vsvw\",\"vtcl\",\"vtcv\",\"vtcy\",\"vtet\",\"vtja\",\"vtjh\",\"vtxi\",\"vuab\",\"vuat\",\"vubq\",\"vuco\",\"vuew\",\"vufr\",\"vuik\",\"vujq\",\"vula\",\"vumv\",\"vuqs\",\"vurr\",\"vurt\",\"vuwf\",\"vuwr\",\"vuwu\",\"vuyh\",\"vuyl\",\"vvgu\",\"vvhc\",\"vvhq\",\"vvik\",\"vvkz\",\"vvme\",\"vvoz\",\"vvqh\",\"vvul\",\"vvur\",\"vvzt\",\"vwah\",\"vwbt\",\"vwcm\",\"vwdl\",\"vwgr\",\"vwgt\",\"vwnj\",\"vwoe\",\"vwpy\",\"vwqx\",\"vwrs\",\"vwuo\",\"vxar\",\"vxbg\",\"vxee\",\"vxeu\",\"vxfr\",\"vxfv\",\"vxgx\",\"vxhk\",\"vxhm\",\"vxkm\",\"vxmn\",\"vxpx\",\"vxra\",\"vxtb\",\"vxwp\",\"vxxi\",\"vxyb\",\"vxza\",\"vybs\",\"vycm\",\"vyda\",\"vygl\",\"vyhl\",\"vyij\",\"vyjh\",\"vyjr\",\"vyof\",\"vyon\",\"vysg\",\"vytm\",\"vytu\",\"vyun\",\"vyzn\",\"vzag\",\"vzbi\",\"vzbv\",\"vzcf\",\"vzfe\",\"vzgv\",\"vztz\",\"vzuq\",\"vzyg\",\"vzza\",\"wadc\",\"wadw\",\"wafs\",\"wagr\",\"waht\",\"waju\",\"waqk\",\"wara\",\"wati\",\"watz\",\"waum\",\"wawe\",\"waxe\",\"waxq\",\"waxs\",\"wazo\",\"wbaa\",\"wbaj\",\"wbav\",\"wbay\",\"wbba\",\"wbca\",\"wbcm\",\"wbcr\",\"wbcs\",\"wbeq\",\"wbfw\",\"wbjb\",\"wbjq\",\"wbkj\",\"wbmc\",\"wbmj\",\"wbow\",\"wbvr\",\"wcak\",\"wcau\",\"wcbg\",\"wccl\",\"wcea\",\"wchf\",\"wcja\",\"wckj\",\"wckm\",\"wclv\",\"wcnj\",\"wcqr\",\"wcsc\",\"wcsp\",\"wcxn\",\"wcym\",\"wcyp\",\"wdan\",\"wdbj\",\"wdct\",\"wdcu\",\"wdda\",\"wddt\",\"wddy\",\"wdgv\",\"wdjx\",\"wdor\",\"wdph\",\"wdpr\",\"wdqz\",\"wdux\",\"wdzp\",\"weco\",\"wefz\",\"weiu\",\"wekp\",\"welg\",\"welj\",\"wemm\",\"wemy\",\"weph\",\"weqg\",\"wesd\",\"wexn\",\"weyy\",\"wffj\",\"wfhf\",\"wfhl\",\"wfjz\",\"wfna\",\"wfne\",\"wfnm\",\"wfqp\",\"wfqw\",\"wfrg\",\"wfrn\",\"wfro\",\"wfrr\",\"wfsn\",\"wfvg\",\"wgci\",\"wgdk\",\"wger\",\"wghd\",\"wgja\",\"wgjf\",\"wgkv\",\"wgky\",\"wglw\",\"wgng\",\"wgte\",\"wgvf\",\"wgvr\",\"wgwt\",\"wgxl\",\"wgyk\",\"wgyp\",\"wgzh\",\"whep\",\"whex\",\"whff\",\"whmh\",\"whog\",\"whse\",\"whsf\",\"whtq\",\"whuk\",\"whvf\",\"whvk\",\"whvp\",\"whwi\",\"whxi\",\"whyl\",\"wich\",\"wicj\",\"wicp\",\"widd\",\"widr\",\"wiey\",\"wiga\",\"wihw\",\"wiil\",\"wiiy\",\"wijs\",\"wimg\",\"wiru\",\"wisi\",\"wisk\",\"witr\",\"wiyb\",\"wjbj\",\"wjcp\",\"wjdb\",\"wjga\",\"wjjc\",\"wjml\",\"wjrp\",\"wjsq\",\"wjti\",\"wjtp\",\"wjtu\",\"wjuo\",\"wjvi\",\"wjwi\",\"wjwz\",\"wjyu\",\"wjzm\",\"wkbt\",\"wkdv\",\"wken\",\"wkhp\",\"wkii\",\"wklw\",\"wknd\",\"wkpu\",\"wkss\",\"wkud\",\"wkvf\",\"wkwk\",\"wkwm\",\"wkyt\",\"wkzg\",\"wkzx\",\"wlaw\",\"wlcx\",\"wleb\",\"wlha\",\"wlhb\",\"wlhy\",\"wljj\",\"wlkz\",\"wlnc\",\"wlno\",\"wlnr\",\"wloz\",\"wlqd\",\"wlvq\",\"wlwb\",\"wlxl\",\"wlya\",\"wlyl\",\"wlzz\",\"wmbj\",\"wmdi\",\"wmew\",\"wmfz\",\"wmkj\",\"wmmc\",\"wmrd\",\"wmru\",\"wmsv\",\"wmta\",\"wmvd\",\"wmvo\",\"wmzx\",\"wnfr\",\"wngi\",\"wngx\",\"wnie\",\"wnls\",\"wnnp\",\"wnny\",\"wnqm\",\"wntw\",\"wnvd\",\"wnwz\",\"wnxe\",\"wnyj\",\"woei\",\"wofe\",\"wogk\",\"woie\",\"woio\",\"woma\",\"wooa\",\"woor\",\"woqt\",\"wour\",\"wpci\",\"wpdl\",\"wpgd\",\"wpht\",\"wphv\",\"wpji\",\"wpko\",\"wplw\",\"wpmy\",\"wpnd\",\"wpqk\",\"wpqq\",\"wpug\",\"wpwc\",\"wpxr\",\"wqaj\",\"wqav\",\"wqba\",\"wqcv\",\"wqon\",\"wqsi\",\"wqyu\",\"wqzh\",\"wral\",\"wrbe\",\"wrjr\",\"wrpr\",\"wrqj\",\"wrxq\",\"wryi\",\"wryw\",\"wscs\",\"wset\",\"wsfc\",\"wsfq\",\"wsgx\",\"wshh\",\"wshr\",\"wsks\",\"wsky\",\"wssb\",\"wssk\",\"wsst\",\"wssx\",\"wsuf\",\"wsul\",\"wsuv\",\"wswb\",\"wtdo\",\"wtey\",\"wtgq\",\"wtiy\",\"wtmb\",\"wtmf\",\"wtmt\",\"wtrp\",\"wttg\",\"wuac\",\"wubn\",\"wubx\",\"wueh\",\"wuie\",\"wujk\",\"wukr\",\"wuku\",\"wukz\",\"wulv\",\"wumw\",\"wuny\",\"wusx\",\"wuwh\",\"wuzk\",\"wuzp\",\"wvay\",\"wvek\",\"wvid\",\"wvij\",\"wviu\",\"wvjv\",\"wvlh\",\"wvmj\",\"wvmp\",\"wvny\",\"wvpx\",\"wvqv\",\"wvtz\",\"wvxp\",\"wwao\",\"wwas\",\"wwjs\",\"wwkm\",\"wwku\",\"wwkz\",\"wwlp\",\"wwlx\",\"wwmf\",\"wwnf\",\"wwnl\",\"wwsu\",\"wwxm\",\"wwxu\",\"wwzs\",\"wxbf\",\"wxes\",\"wxfc\",\"wxfw\",\"wxfx\",\"wxgt\",\"wxir\",\"wxky\",\"wxlq\",\"wxnj\",\"wxpg\",\"wxpk\",\"wxpl\",\"wxri\",\"wxtu\",\"wxud\",\"wxvv\",\"wxxc\",\"wxyb\",\"wxyw\",\"wybj\",\"wybm\",\"wydk\",\"wygd\",\"wyhz\",\"wylz\",\"wymr\",\"wypv\",\"wyul\",\"wyvj\",\"wzau\",\"wzbw\",\"wzcb\",\"wzdw\",\"wzep\",\"wzge\",\"wzhk\",\"wzjw\",\"wzof\",\"wzpf\",\"wztm\",\"wztr\",\"wzuv\",\"wzzo\",\"xadu\",\"xafc\",\"xagt\",\"xahw\",\"xakd\",\"xakj\",\"xarh\",\"xaui\",\"xbaj\",\"xbcr\",\"xber\",\"xbkt\",\"xblq\",\"xbmm\",\"xbnp\",\"xboe\",\"xbof\",\"xbpg\",\"xbsm\",\"xbsw\",\"xbtc\",\"xbug\",\"xbum\",\"xbzg\",\"xcba\",\"xccg\",\"xcck\",\"xcgj\",\"xchn\",\"xckg\",\"xctm\",\"xcuo\",\"xcvv\",\"xcxl\",\"xcyc\",\"xczr\",\"xdav\",\"xdaz\",\"xddh\",\"xddr\",\"xdeq\",\"xdjy\",\"xdms\",\"xdnk\",\"xdno\",\"xdpk\",\"xdpr\",\"xdqo\",\"xdum\",\"xdxn\",\"xdzd\",\"xech\",\"xecr\",\"xegr\",\"xegt\",\"xehp\",\"xehy\",\"xejc\",\"xely\",\"xemt\",\"xeoc\",\"xepr\",\"xerh\",\"xerl\",\"xesf\",\"xeub\",\"xewt\",\"xeyn\",\"xezz\",\"xffj\",\"xfgm\",\"xfnh\",\"xfpn\",\"xfvx\",\"xfxo\",\"xgat\",\"xgcf\",\"xgcy\",\"xgez\",\"xgfm\",\"xgia\",\"xgiu\",\"xgjp\",\"xgkg\",\"xgoa\",\"xgqe\",\"xgqp\",\"xgrk\",\"xgse\",\"xgsq\",\"xguy\",\"xgvp\",\"xgye\",\"xgyj\",\"xhaa\",\"xhbh\",\"xhda\",\"xhdm\",\"xheg\",\"xhgk\",\"xhir\",\"xhjd\",\"xhry\",\"xhsa\",\"xhvf\",\"xhza\",\"xibb\",\"xico\",\"xicy\",\"xifd\",\"xifp\",\"xihe\",\"xiip\",\"xiku\",\"xiny\",\"xiok\",\"xipd\",\"xite\",\"xitg\",\"xith\",\"xiuc\",\"xivt\",\"xjbj\",\"xjdu\",\"xjdv\",\"xjdy\",\"xjhg\",\"xjii\",\"xjjn\",\"xjkh\",\"xjko\",\"xjni\",\"xjqa\",\"xjtc\",\"xjvb\",\"xjvn\",\"xjvt\",\"xjvu\",\"xkcm\",\"xkdn\",\"xkff\",\"xkic\",\"xkmq\",\"xkmw\",\"xkmx\",\"xkot\",\"xkqa\",\"xkqe\",\"xkqo\",\"xksy\",\"xkus\",\"xkyg\",\"xkzi\",\"xkzu\",\"xlhp\",\"xljf\",\"xlkq\",\"xlmc\",\"xlmt\",\"xlnl\",\"xlnn\",\"xlps\",\"xlru\",\"xluc\",\"xlvy\",\"xlyu\",\"xmad\",\"xmdw\",\"xmgi\",\"xmgq\",\"xmjo\",\"xmlu\",\"xmmt\",\"xmmz\",\"xmnq\",\"xmor\",\"xmow\",\"xmsb\",\"xmxq\",\"xmze\",\"xnao\",\"xnco\",\"xnfk\",\"xngb\",\"xnhu\",\"xnid\",\"xnik\",\"xnjo\",\"xnow\",\"xnoz\",\"xnpb\",\"xnqn\",\"xnqs\",\"xnqy\",\"xnrq\",\"xnso\",\"xnui\",\"xnvg\",\"xnwl\",\"xnwp\",\"xnwt\",\"xnwz\",\"xnyq\",\"xoac\",\"xoaf\",\"xocd\",\"xocu\",\"xodf\",\"xoel\",\"xoep\",\"xoev\",\"xofo\",\"xoia\",\"xois\",\"xolz\",\"xopx\",\"xopz\",\"xoqp\",\"xoty\",\"xovq\",\"xoyc\",\"xpaz\",\"xpbp\",\"xpbq\",\"xpei\",\"xpfo\",\"xpgk\",\"xppu\",\"xpru\",\"xptr\",\"xpvs\",\"xpxy\",\"xpyr\",\"xpzm\",\"xqfw\",\"xqhe\",\"xqmv\",\"xqnv\",\"xqok\",\"xqol\",\"xqri\",\"xqrm\",\"xqrx\",\"xqrz\",\"xqsg\",\"xqsh\",\"xqud\",\"xquf\",\"xqun\",\"xquv\",\"xqvc\",\"xrcq\",\"xrcy\",\"xrei\",\"xrfk\",\"xrft\",\"xrgg\",\"xriq\",\"xrkx\",\"xrlp\",\"xrma\",\"xrmw\",\"xrnh\",\"xroo\",\"xrsc\",\"xrse\",\"xrut\",\"xrwz\",\"xrxm\",\"xrxn\",\"xryn\",\"xsda\",\"xsdm\",\"xsdo\",\"xsef\",\"xsgd\",\"xsgq\",\"xsjz\",\"xsly\",\"xsox\",\"xspk\",\"xsru\",\"xsrv\",\"xsup\",\"xsxv\",\"xtbm\",\"xtct\",\"xtem\",\"xtfi\",\"xtia\",\"xtli\",\"xtlk\",\"xtoi\",\"xtro\",\"xtrq\",\"xtsw\",\"xttn\",\"xttz\",\"xtvp\",\"xtyg\",\"xtzr\",\"xtzs\",\"xuah\",\"xufd\",\"xuge\",\"xuje\",\"xuju\",\"xuko\",\"xuku\",\"xuly\",\"xumc\",\"xuqz\",\"xurn\",\"xusj\",\"xuxy\",\"xuyb\",\"xuyn\",\"xuzq\",\"xvbj\",\"xvbz\",\"xvez\",\"xvgk\",\"xvhk\",\"xvip\",\"xvjc\",\"xvkf\",\"xvki\",\"xvlm\",\"xvmu\",\"xvou\",\"xvqe\",\"xvso\",\"xvvs\",\"xvyu\",\"xvzw\",\"xwco\",\"xwdg\",\"xwfd\",\"xwic\",\"xwjc\",\"xwjh\",\"xwmw\",\"xwnh\",\"xwnu\",\"xwnz\",\"xwog\",\"xwot\",\"xwox\",\"xwpm\",\"xwrg\",\"xwri\",\"xwsx\",\"xwtk\",\"xwue\",\"xwvx\",\"xwxq\",\"xwyp\",\"xxaj\",\"xxas\",\"xxce\",\"xxhn\",\"xxhx\",\"xxif\",\"xxke\",\"xxla\",\"xxqi\",\"xxuc\",\"xxuv\",\"xxvo\",\"xxzu\",\"xyaa\",\"xyap\",\"xycl\",\"xycr\",\"xyge\",\"xyjc\",\"xylh\",\"xylp\",\"xyoq\",\"xypa\",\"xyrl\",\"xyrv\",\"xysg\",\"xysn\",\"xywf\",\"xyyk\",\"xyzr\",\"xzap\",\"xzhe\",\"xzkd\",\"xzlv\",\"xzlw\",\"xzmj\",\"xzpq\",\"xzse\",\"xztd\",\"xztr\",\"xzud\",\"xzui\",\"xzum\",\"xzup\",\"xzuq\",\"xzzg\",\"yabf\",\"yafl\",\"yahd\",\"yahp\",\"yahx\",\"yaii\",\"yais\",\"yajd\",\"yakq\",\"yakx\",\"yaof\",\"yaqc\",\"yaqr\",\"yaqs\",\"yaua\",\"yauw\",\"yavr\",\"yavz\",\"yawf\",\"yazc\",\"ybbu\",\"ybcs\",\"ybdp\",\"ybhf\",\"ybhq\",\"ybni\",\"ybom\",\"ybox\",\"ybwe\",\"ybwg\",\"ybxe\",\"ybxn\",\"ybyg\",\"ybzj\",\"ycca\",\"yccs\",\"ycdc\",\"ycel\",\"ycgi\",\"ycjx\",\"yckl\",\"yclp\",\"ycnc\",\"ycnk\",\"ycoz\",\"ycqm\",\"ycqq\",\"ycuh\",\"ycya\",\"ycyr\",\"ydec\",\"ydei\",\"ydew\",\"ydhn\",\"ydhp\",\"ydjg\",\"ydnu\",\"ydpf\",\"ydpy\",\"ydqg\",\"ydrh\",\"ydto\",\"yduc\",\"ydui\",\"ydul\",\"yduy\",\"ydvm\",\"yedm\",\"yegb\",\"yeha\",\"yekh\",\"yenm\",\"yesv\",\"yeux\",\"yfaz\",\"yffx\",\"yfgr\",\"yfhy\",\"yfju\",\"yfpy\",\"yfqg\",\"yfql\",\"yfsi\",\"yfst\",\"yfta\",\"yfwb\",\"yfyv\",\"yfzi\",\"yggn\",\"ygim\",\"yglq\",\"ygny\",\"ygoa\",\"ygpk\",\"ygrd\",\"ygtr\",\"ygus\",\"ygwk\",\"ygym\",\"yhbl\",\"yhcs\",\"yhew\",\"yhkr\",\"yhnu\",\"yhpl\",\"yhre\",\"yhry\",\"yhsy\",\"yhue\",\"yhxt\",\"yhxx\",\"yhze\",\"yhzp\",\"yiah\",\"yiat\",\"yiaz\",\"yibg\",\"yibs\",\"yieh\",\"yifi\",\"yigj\",\"yiqq\",\"yirh\",\"yivl\",\"yiwc\",\"yixz\",\"yizn\",\"yizv\",\"yjau\",\"yjdf\",\"yjjq\",\"yjjy\",\"yjol\",\"yjpn\",\"yjpx\",\"yjri\",\"yjud\",\"yjuz\",\"yjwv\",\"yjye\",\"ykan\",\"ykde\",\"ykdu\",\"ykel\",\"ykhc\",\"yknk\",\"ykri\",\"ykrp\",\"yksh\",\"ykui\",\"ykvr\",\"ykxl\",\"ykye\",\"ykzl\",\"ykzu\",\"ylfz\",\"ylgc\",\"ylhb\",\"ylih\",\"yliu\",\"yliw\",\"ylki\",\"ylks\",\"ylpf\",\"ylqk\",\"ylqs\",\"yltp\",\"ylwj\",\"ylzv\",\"ymcg\",\"ymco\",\"ymdr\",\"ymga\",\"ymii\",\"ymjj\",\"ymlp\",\"ymlz\",\"ymma\",\"ymne\",\"ymoo\",\"ymoz\",\"ymqp\",\"ymrg\",\"ymso\",\"ymtn\",\"ymvf\",\"ynba\",\"ynco\",\"yndz\",\"ynei\",\"ynfs\",\"ynhg\",\"ynig\",\"ynis\",\"ynjn\",\"ynkr\",\"ynqh\",\"ynqr\",\"ynxn\",\"ynxs\",\"yoaq\",\"yoct\",\"yoiw\",\"yokt\",\"yone\",\"yosm\",\"youe\",\"yowx\",\"ypak\",\"ypdz\",\"ypgo\",\"yprm\",\"ypro\",\"ypue\",\"yqax\",\"yqcl\",\"yqdf\",\"yqea\",\"yqfu\",\"yqfy\",\"yqjo\",\"yqku\",\"yqlh\",\"yqnn\",\"yqon\",\"yqpx\",\"yqqx\",\"yqsx\",\"yqub\",\"yqup\",\"yqvg\",\"yqvu\",\"yqyn\",\"yqyp\",\"yrdh\",\"yrfa\",\"yrfq\",\"yrfu\",\"yrhl\",\"yrkq\",\"yrqp\",\"yrtd\",\"yrte\",\"yrtu\",\"yrwm\",\"yrwy\",\"yrxo\",\"yscr\",\"ysdq\",\"ysel\",\"ysgb\",\"yshj\",\"ysji\",\"ysjv\",\"yskj\",\"ysph\",\"ysrx\",\"ysut\",\"ysyb\",\"ysyh\",\"yszg\",\"yszr\",\"ytam\",\"ytap\",\"ytbe\",\"ytbk\",\"ytbn\",\"ytda\",\"ytdj\",\"ytdr\",\"ytdy\",\"yteu\",\"ytfx\",\"ytjc\",\"ytjt\",\"ytlm\",\"ytnd\",\"ytpu\",\"ytqb\",\"ytrg\",\"ytzt\",\"yufj\",\"yujs\",\"yuka\",\"yuop\",\"yupc\",\"yuqj\",\"yuud\",\"yuwf\",\"yuxj\",\"yvab\",\"yvbi\",\"yvee\",\"yveo\",\"yvhm\",\"yvhu\",\"yvim\",\"yvpg\",\"yvpu\",\"yvse\",\"yvso\",\"yvus\",\"yvxa\",\"yvxb\",\"yvxg\",\"yvxl\",\"yvyz\",\"ywaj\",\"ywam\",\"ywdr\",\"ywea\",\"ywfc\",\"ywfj\",\"ywkj\",\"ywly\",\"ywnq\",\"ywnx\",\"ywny\",\"ywpd\",\"ywpf\",\"ywpk\",\"ywpq\",\"ywqc\",\"ywqo\",\"ywtu\",\"ywwc\",\"ywxr\",\"ywyd\",\"ywyw\",\"yxaz\",\"yxdo\",\"yxki\",\"yxlw\",\"yxmu\",\"yxob\",\"yxom\",\"yxoq\",\"yxpn\",\"yxvk\",\"yxvn\",\"yxvz\",\"yxws\",\"yxxi\",\"yxzb\",\"yxzd\",\"yyfk\",\"yygr\",\"yyij\",\"yylj\",\"yynf\",\"yyoe\",\"yyqr\",\"yyqs\",\"yyrb\",\"yyss\",\"yyue\",\"yyxb\",\"yyyd\",\"yyzo\",\"yyzu\",\"yzgd\",\"yzja\",\"yzof\",\"yzpa\",\"yzqg\",\"yzrd\",\"yzrt\",\"yzru\",\"yzvs\",\"yzwn\",\"yzwu\",\"yzyj\",\"yzzz\",\"zagv\",\"zaif\",\"zaim\",\"zaki\",\"zale\",\"zalj\",\"zamm\",\"zaoy\",\"zapd\",\"zbaj\",\"zbba\",\"zbbv\",\"zbci\",\"zbdk\",\"zbjs\",\"zbms\",\"zbmy\",\"zbnq\",\"zbrw\",\"zbsk\",\"zbue\",\"zbwu\",\"zbxb\",\"zbzg\",\"zcfd\",\"zchv\",\"zclx\",\"zcmg\",\"zcnh\",\"zcny\",\"zcpf\",\"zcrw\",\"zcsm\",\"zcsz\",\"zcuq\",\"zcvb\",\"zcxk\",\"zdab\",\"zdaq\",\"zdcl\",\"zddi\",\"zdhv\",\"zdkd\",\"zdlg\",\"zdmd\",\"zdph\",\"zdtn\",\"zduq\",\"zdyb\",\"zeeu\",\"zefn\",\"zehr\",\"zehz\",\"zeie\",\"zeju\",\"zeph\",\"zern\",\"zerx\",\"zesr\",\"zeuy\",\"zevc\",\"zezr\",\"zfab\",\"zfci\",\"zfdj\",\"zffy\",\"zfhv\",\"zfiv\",\"zfjf\",\"zfjg\",\"zfkq\",\"zflu\",\"zfmd\",\"zfqd\",\"zfri\",\"zfuq\",\"zfvi\",\"zfzf\",\"zgca\",\"zgjg\",\"zgna\",\"zgnm\",\"zgpk\",\"zgpo\",\"zgqz\",\"zguh\",\"zgur\",\"zgvr\",\"zgzj\",\"zhak\",\"zham\",\"zhbm\",\"zhed\",\"zhfq\",\"zhmi\",\"zhpy\",\"zhso\",\"zhsr\",\"zhxv\",\"zhxx\",\"zhyd\",\"ziar\",\"zibm\",\"zicj\",\"zify\",\"zihf\",\"zija\",\"zikb\",\"zikl\",\"zilh\",\"zimw\",\"zinb\",\"zins\",\"zioi\",\"ziow\",\"zipf\",\"ziqe\",\"ziqn\",\"zise\",\"zism\",\"ziyr\",\"zjeg\",\"zjgk\",\"zjhd\",\"zjin\",\"zjjm\",\"zjka\",\"zjkh\",\"zjmy\",\"zjnj\",\"zjpe\",\"zjqv\",\"zjry\",\"zjts\",\"zjuh\",\"zjya\",\"zkas\",\"zkfj\",\"zkfq\",\"zkhc\",\"zkjl\",\"zkml\",\"zkmr\",\"zkmw\",\"zknq\",\"zknt\",\"zkqi\",\"zkta\",\"zkul\",\"zkvp\",\"zkyw\",\"zkzh\",\"zkzr\",\"zlhc\",\"zlhl\",\"zlko\",\"zlqx\",\"zltv\",\"zlul\",\"zlwd\",\"zlwe\",\"zlxs\",\"zmad\",\"zmaq\",\"zmbd\",\"zmea\",\"zmkf\",\"zmkn\",\"zmko\",\"zmqu\",\"zmrm\",\"zmuf\",\"zmvb\",\"zmvf\",\"zmvs\",\"zmwr\",\"zmxa\",\"zmzr\",\"znbr\",\"znbw\",\"znbz\",\"zncu\",\"zndf\",\"znem\",\"znij\",\"zniv\",\"znjy\",\"znls\",\"znlz\",\"znoz\",\"znqi\",\"znrp\",\"znsp\",\"znvq\",\"znyx\",\"zoel\",\"zoen\",\"zohx\",\"zoim\",\"zokg\",\"zoky\",\"zomv\",\"zonn\",\"zora\",\"zosu\",\"zoth\",\"zous\",\"zovc\",\"zovx\",\"zowv\",\"zpaw\",\"zpbc\",\"zpbj\",\"zpdt\",\"zpge\",\"zplc\",\"zpmh\",\"zpnt\",\"zppm\",\"zprd\",\"zpti\",\"zpwg\",\"zpwz\",\"zqal\",\"zqch\",\"zqco\",\"zqfp\",\"zqjz\",\"zqml\",\"zqof\",\"zqqc\",\"zqsb\",\"zqsz\",\"zqwg\",\"zqxn\",\"zqzx\",\"zqzy\",\"zrbp\",\"zrde\",\"zree\",\"zrfa\",\"zrgb\",\"zrgl\",\"zrgt\",\"zrnh\",\"zrok\",\"zroq\",\"zrqh\",\"zrqo\",\"zryq\",\"zryy\",\"zsdv\",\"zsem\",\"zsen\",\"zshp\",\"zsjm\",\"zskd\",\"zsmk\",\"zsml\",\"zsrv\",\"zsuc\",\"zsuk\",\"zsvk\",\"zsww\",\"zsxd\",\"zszs\",\"zszu\",\"ztay\",\"ztbv\",\"ztcb\",\"ztgl\",\"ztgo\",\"ztnd\",\"ztop\",\"ztot\",\"ztps\",\"zttk\",\"ztuh\",\"ztuw\",\"ztuz\",\"ztvg\",\"ztvz\",\"ztxc\",\"ztxs\",\"ztzy\",\"zucr\",\"zudr\",\"zulj\",\"zumv\",\"zunt\",\"zuqi\",\"zurf\",\"zusi\",\"zutl\",\"zutp\",\"zuvu\",\"zuwp\",\"zuzl\",\"zvao\",\"zvcd\",\"zvcr\",\"zvdm\",\"zvfh\",\"zvgm\",\"zvhg\",\"zvhr\",\"zvil\",\"zvmj\",\"zvrk\",\"zvtf\",\"zvui\",\"zvxa\",\"zvxb\",\"zvxt\",\"zvxx\",\"zvyf\",\"zvzq\",\"zvzx\",\"zwbr\",\"zwbx\",\"zwel\",\"zwfh\",\"zwhx\",\"zwin\",\"zwjh\",\"zwkm\",\"zwmn\",\"zwoc\",\"zwpq\",\"zwtj\",\"zwwk\",\"zwxb\",\"zxhq\",\"zxiw\",\"zxkr\",\"zxpi\",\"zxtj\",\"zxzi\",\"zybb\",\"zybf\",\"zydg\",\"zyem\",\"zyep\",\"zyjp\",\"zykh\",\"zylv\",\"zyoh\",\"zyph\",\"zyqf\",\"zyuy\",\"zzah\",\"zzcv\",\"zzfe\",\"zzft\",\"zzgw\",\"zzkr\",\"zzmc\",\"zzmh\",\"zzoh\",\"zzqz\",\"zzrz\",\"zzxr\",\"zzzw\"];\n\nexport default roomNames;\n"
  },
  {
    "path": "backend/src/rooms/room-sagas.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { delay, takeEvery, takeLatest }     from 'redux-saga';\nimport { call, cancel, cancelled, put, fork, spawn, select }   from 'redux-saga/effects';\n\nimport uuid     from 'uuid';\n\nimport { logger }                           from '../logger';\nimport { messageHandler }                   from '../messages';\nimport { serverActions, serverConstants }   from '../server';\nimport { sphereConstants }                  from '../spheres';\n\nimport {\n    makeWsReplyMessage,\n    makeWsBroadcastMessage,\n    sendWsMessageWithLogger,\n    sendWsErrorWithLogger\n} from '../utils/websocket-utils';\n\nimport config                       from '../config';\nimport { sagaUtils }                from '../utils';\n\nimport messageConstants             from '../messages/message-constants';\n\nimport roomDataActions              from './room-data-actions';\nimport roomDataConstants            from './room-data-constants';\nimport roomNames                    from './room-names';\n\nimport roomStateActions             from './room-state-actions';\nimport roomStateConstants           from './room-state-constants';\nimport roomStateUtils               from './room-state-utils';\n\nconst incomingMsgComponents = messageConstants.INCOMING_MESSAGE_COMPONENTS;\nconst outgoingMsgComponents = messageConstants.OUTGOING_MESSAGE_COMPONENTS;\n\n/*******************************************************************************\n* START SETTING UP THE ROOM\n*******************************************************************************/\n\nconst startRoomSetup = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.STARTING_ROOM_SETUP\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing startRoomSetup, room ${action.roomName} not in state STARTING_ROOM_SETUP` );\n        return;\n    }\n\n    // tell the state machine this worked OK\n    yield put( roomStateActions.notifyStartRoomSetupSuccessRequestAction( action.roomName ) );\n\n};\n\nconst handleStartRoomSetupSuccess = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.ROOM_SETUP_STARTED\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing handleStartRoomSetupSuccess, room ${action.roomName} not in state ROOM_SETUP_STARTED` );\n        return;\n    }\n\n    // initialise the room state\n    yield put( roomStateActions.initRoomContentRequestAction( action.roomName ) );\n\n    // check if it was initialised correctly\n    let roomData = yield select( ( state ) => { return state.roomDataReducer.rooms[ action.roomName ]; } );\n\n    // tell the state machine what happened\n    if( typeof roomData.content === 'object' ) {\n        yield put( roomStateActions.notifyInitRoomContentSuccessRequestAction( action.roomName ) );\n    }\n    else {\n        yield put( roomStateActions.notifyInitRoomContentFailureRequestAction( action.roomName ) );\n    }\n};\n\n/*******************************************************************************\n* HANDLE SUCCESSFUL ROOM STATE INIT\n*******************************************************************************/\n\nconst handleInitRoomContentSuccess = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.ROOM_CONTENT_INITED\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing handleInitRoomContentSuccess, room ${action.roomName} not in state ROOM_CONTENT_INITED` );\n        return;\n    }\n\n    // this is possibly an unnecessary state change(!), but it's explicit\n    yield put( roomStateActions.startRoomHeartbeatRequestAction( action.roomName ) );\n};\n\n/*******************************************************************************\n* START HEARTBEAT FOR ROOM\n*******************************************************************************/\n\nconst startHeartbeatForRoom = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.STARTING_HEARTBEAT\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing startHeartbeatForRoom, room ${action.roomName} not in state ${acceptableStates}` );\n        return;\n    }\n\n    let { roomData, roomState } = yield select(\n        ( state ) => {\n            return {\n                roomData:   state.roomDataReducer.rooms,\n                roomState:  state.roomStateReducer\n            };\n        }\n    );\n\n    let lastHeartbeat, heartbeatCount;\n\n    try {\n\n        lastHeartbeat   = roomData[ action.roomName ].content.lastHeartbeat;\n\n        let roomStatus  = roomState.rooms[ action.roomName ].status;\n\n        logger.trace( `starting heartbeat for new room: ${action.roomName}` );\n    }\n    catch( error ) {\n        logger.error( `error trying to start heartbeat for room '${action.roomName}': ${error.message}` );\n        yield put( roomStateActions.notifyStartRoomHeartbeatFailureRequestAction( action.roomName ) );\n        return;\n    }\n\n    // fork a heartbeat task\n    let heartbeatTask = yield fork( heartbeatTaskFunction, action.roomName );\n\n    // set it in state\n    yield put( roomDataActions.setHeartbeatTaskForRoomRequestAction( heartbeatTask, action.roomName ) );\n\n    // check it got there\n    roomData = yield select( ( state ) => { return state.roomDataReducer.rooms; } );\n\n    // tell the state-machine for the room what's happening\n    if( roomData[ action.roomName ].tasks.heartbeat === heartbeatTask ) {\n        yield put( roomStateActions.notifyStartRoomHeartbeatSuccessRequestAction( action.roomName ) );\n    }\n    else {\n        yield put( roomStateActions.notifyStartRoomHeartbeatFailureRequestAction( action.roomName ) );\n    }\n\n    return;\n};\n\nconst heartbeatTaskFunction = function* ( roomName ) {\n\n    try {\n\n        let lastHeartbeatCount = 0;\n\n        while( true ) {\n            // get the latest status\n            let roomData = yield select( ( state ) => { return state.roomDataReducer.rooms; } );\n\n            // if the state's been cleared, stop sending\n            if( !roomData[ roomName ] ) {\n                logger.trace( `state for room ${roomName} has been cleared, finishing heartbeat` );\n                break;\n            }\n\n            // update the second count\n            let secondCount = ( lastHeartbeatCount * config.roomHeartbeatDelayInMs ) / 1000 ;\n\n            // create a heartbeat message\n            let heartbeatData = {\n                [ outgoingMsgComponents.ROOM_HEARTBEAT.COUNT ]:     lastHeartbeatCount,\n                [ outgoingMsgComponents.ROOM_HEARTBEAT.SECONDS ]:   secondCount\n            };\n\n            let heartbeatMessage = makeWsBroadcastMessage(\n                config.serverId,\n                messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_HEARTBEAT,\n                heartbeatData\n            );\n\n            // set the latest count in the state\n            yield put(\n                roomDataActions.setLastHeartbeatCountForRoomRequestAction(\n                    lastHeartbeatCount,\n                    roomName\n                )\n            );\n\n            // publish to room\n            yield put( roomDataActions.publishMessageToRoomRequestAction( heartbeatMessage, roomName ) );\n\n            // update the heartbeat count\n            lastHeartbeatCount = lastHeartbeatCount + 1;\n\n            // pause until next heartbeat\n            yield delay( config.roomHeartbeatDelayInMs );\n        }\n    }\n    finally {\n        if( yield cancelled() ) {\n            logger.trace( `cancelling heartbeat task for room ${roomName} on request` );\n            yield put( roomDataActions.removeHeartbeatTaskForRoomRequestAction( roomName ) );\n            yield put( roomStateActions.notifyStopHeartbeatSuccessRequestAction( roomName ) );\n        }\n    }\n};\n\n/*******************************************************************************\n* ADD QUEUED WEBSOCKETS INTO ROOM AFTER SETUP\n*******************************************************************************/\n\nconst startProcessingQueue = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.HEARTBEAT_STARTED\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing startProcessingQueue, room ${action.roomName} not in state ${acceptableStates}` );\n        return;\n    }\n\n    // tell the state machine the queue's being processed\n    yield put( roomStateActions.startProcessingQueueRequestAction( action.roomName ) );\n\n    let { serverState, roomData } = yield select(\n        ( state ) => {\n            return {\n                serverState:    state.serverReducer,\n                roomData:       state.roomDataReducer.rooms\n            };\n        }\n    );\n\n    logger.trace( `processing ${roomData[ action.roomName ].queue.length} websockets in queue for room ${action.roomName}` );\n\n    // if all the local clients in the queue left before the setup completed\n    if( roomData[ action.roomName ].queue.length < 1 ) {\n        logger.trace( `no clients left in queue for room ${action.roomName}, closing down room` );\n        // start closing down the room on this server\n        yield put( roomStateActions.checkIfRoomEmptyRequestAction( action.roomName ) );\n        return;\n    }\n\n    for( let clientId of roomData[ action.roomName ].queue ) {\n\n        let clientHeadsetType = serverState.websockets[ clientId ].headsetType;\n        yield put( roomDataActions.addLocalClientToRoomRequestAction( clientId, clientHeadsetType, action.roomName ) );\n        yield put( roomDataActions.removeWebsocketFromQueueForRoomRequestAction( clientId, action.roomName ) );\n    }\n\n    // tell the state machine the queue's been processed\n    yield put( roomStateActions.notifyProcessQueueSuccessRequestAction( action.roomName ) );\n\n};\n\nconst handleProcessQueueSuccess = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.READY\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing handleProcessQueueSuccess, room ${action.roomName} not in state READY` );\n        return;\n    }\n\n    let roomData = yield select( ( state ) => { return state.roomDataReducer.rooms; } );\n\n    // if the room's full\n    if( Object.keys( roomData[ action.roomName ].content.clients ).length >= config.maxClientsPerRoom ) {\n        // tell the state machine about it\n        yield put( roomStateActions.setRoomFullRequestAction( action.roomName ) );\n    }\n};\n\n/*******************************************************************************\n* CONNECT A WEBSOCKET CONNECTION TO A SPECIFIED ROOM\n*******************************************************************************/\n\nconst connectWebsocketToRoom = function* ( action ) {\n\n    // belt & braces\n    if( roomNames.indexOf( action.ws.requestedRoomName ) < 0 ) {\n\n        let noSuchRoomMessage = makeWsReplyMessage(\n            config.serverId,\n            messageConstants.ERROR_TYPES.NO_SUCH_ROOM,\n            { roomName }\n        );\n\n        sendWsMessageWithLogger( action.ws, noSuchRoomMessage, logger );\n\n        return;\n    }\n\n    logger.trace(\n        `joining ${action.ws.headsetType} websocket ${action.ws.id} ` +\n        `to room '${action.ws.requestedRoomName}'`\n    );\n\n    let roomName = action.ws.requestedRoomName;\n\n    // get the current state for chosen room\n    let { roomData, roomState } = yield select(\n        ( state ) => {\n            return {\n                roomData:   state.roomDataReducer.rooms[ roomName ],\n                roomState:  state.roomStateReducer.rooms[ roomName ]\n            };\n        }\n    );\n\n    // work out what FSM state it's in\n    switch( roomState.status ) {\n\n        // idle, waiting for setup\n        case roomStateConstants.STATES.INIT:\n\n            // kick off the room setup\n            yield put( roomStateActions.initRoomContentRequestAction( roomName ) );\n\n            // check if it was initialised correctly\n            let roomData = yield select( ( state ) => { return state.roomDataReducer.rooms[ roomName ]; } );\n\n            // tell the state machine what happened\n            if( typeof roomData.content === 'object' ) {\n                yield put( roomStateActions.notifyInitRoomContentSuccessRequestAction( roomName ) );\n            }\n            else {\n                yield put( roomStateActions.notifyInitRoomContentFailureRequestAction( roomName ) );\n                return;\n            }\n\n            // put the connection in the queue for the room\n            yield put(\n                roomDataActions.addWebsocketToQueueForRoomRequestAction(\n                    action.ws.id,\n                    roomName\n                )\n            );\n\n            break;\n\n        // in process of being set up\n        case roomStateConstants.STATES.INITING_ROOM_CONTENT:\n        case roomStateConstants.STATES.ROOM_CONTENT_INITED:\n        case roomStateConstants.STATES.STARTING_HEARTBEAT:\n        case roomStateConstants.STATES.HEARTBEAT_STARTED:\n\n            // check if there's space in the queue\n            if( roomData.queue.length >= config.maxClientsPerRoom ) {\n\n                let queueFullMessage = makeWsReplyMessage(\n                    config.serverId,\n                    messageConstants.ERROR_TYPES.ROOM_QUEUE_FULL,\n                    { roomName }\n                );\n\n                sendWsMessageWithLogger( action.ws, queueFullMessage, logger );\n                logger.warn( `closing client ${action.ws.id}: queue for ${roomName} is full` );\n                action.ws.close();\n\n                return;\n            }\n\n            // put the connection in the queue for the room\n            yield put(\n                roomDataActions.addWebsocketToQueueForRoomRequestAction(\n                    action.ws.id,\n                    roomName\n                )\n            );\n\n            break;\n\n        // full\n        case roomStateConstants.STATES.ROOM_FULL:\n\n            let roomFullMessage = makeWsReplyMessage(\n                config.serverId,\n                messageConstants.ERROR_TYPES.BUSY_TRY_AGAIN,\n                {}\n            );\n\n            sendWsMessageWithLogger( action.ws, roomFullMessage, logger );\n            logger.warn( `closing client ${action.ws.id}: queue for ${roomName} is full` );\n            action.ws.close();\n\n            return;\n\n        // up and running\n        case roomStateConstants.STATES.READY:\n\n            // add the connection to the room\n            yield put(\n                roomDataActions.addLocalClientToRoomRequestAction(\n                    action.ws.id,\n                    action.ws.headsetType,\n                    roomName\n                )\n            );\n\n            break;\n\n        // anything else\n        default:\n\n            logger.warn( `tried to join ${action.ws.id} to room ${roomName} while in state ${roomState.status}` );\n\n            let roomUnavailableMessage = makeWsReplyMessage(\n                config.serverId,\n                messageConstants.ERROR_TYPES.ROOM_UNAVAILABLE,\n                { roomName }\n            );\n\n            sendWsMessageWithLogger( action.ws, roomUnavailableMessage, logger );\n\n            break;\n    }\n};\n\n/*******************************************************************************\n* ADD CLIENT TO ROOM DATA AND INFORM EXISTING CLIENTS IT'S JOINED\n*******************************************************************************/\n\n// action: { clientId, roomName }\nconst handleAddLocalClientToRoom = function* ( action ) {\n\n    let { serverState, roomState, roomData } = yield select(\n        ( state ) => {\n            return {\n                serverState:    state.serverReducer,\n                roomState:      state.roomStateReducer,\n                roomData:       state.roomDataReducer.rooms\n            };\n        }\n    );\n\n    if( roomState.rooms[ action.roomName ].status === roomStateConstants.STATES.READY ) {\n        // if the room's full\n        if( Object.keys( roomData[ action.roomName ].content.clients ).length >= config.maxClientsPerRoom ) {\n            // tell the state machine about it\n            yield put( roomStateActions.setRoomFullRequestAction( action.roomName ) );\n        }\n    }\n\n    let ws = serverState.websockets[ action.clientId ];\n    if( typeof ws === 'undefined' ) {\n        logger.warn( `error trying to sendRoomInfoToClient: no websocket object for ws id ${action.clientId}` );\n        return;\n    }\n\n    let roomName = action.roomName;\n    if( typeof roomName === 'undefined' ) {\n        logger.warn( `error trying to sendRoomInfoToClient: no currentRoom for ws id ${ws.id}` );\n        sendWsErrorWithLogger( config.serverId, ws, messageConstants.ERROR_TYPES.SYSTEM_ERROR, roomName );\n        return;\n    }\n\n    let roomClients = roomData[ roomName ].content.clients;\n\n    let clients = {};\n\n    // copy of client list, without the connecting client\n    Object.keys( roomClients ).filter(\n        ( clientId ) => {\n            return clientId !== action.clientId;\n        }\n    )\n    .forEach(\n        ( clientId ) => {\n            const headsetType = roomClients[ clientId ].headsetType;\n\n            if( typeof clients[ headsetType ] === 'undefined' ) {\n                clients[ headsetType ] = []\n            }\n            clients[ headsetType ].push( clientId );\n        }\n    );\n\n    // get copy of spheres from room state\n    let spheres = JSON.parse( JSON.stringify( roomData[ roomName ].content.spheres ) );\n\n    // add to client message\n    let roomInfo = {\n        [ outgoingMsgComponents.ROOM_STATUS_INFO.ROOM_NAME ]:   roomName,\n        [ outgoingMsgComponents.ROOM_STATUS_INFO.SOUNDBANK ]:   roomData[ roomName ].content.soundbank,\n        [ outgoingMsgComponents.ROOM_STATUS_INFO.CLIENTS ]:     clients,\n        [ outgoingMsgComponents.ROOM_STATUS_INFO.SPHERES ]:     spheres\n    };\n\n    let roomInfoMessage = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_STATUS_INFO,\n        roomInfo\n    );\n\n    try {\n        sendWsMessageWithLogger( ws, roomInfoMessage, logger );\n    }\n    catch( error ) {\n        logger.trace( `error sending room info message to client ${ws.id}`, error.message );\n    }\n\n    // tell all other clients in the room that this one has joined\n    let clientData = {\n        [ outgoingMsgComponents.ROOM_CLIENT_JOIN.CLIENT_ID ]: ws.id,\n        [ outgoingMsgComponents.ROOM_CLIENT_JOIN.CLIENT_HEADSET_TYPE ]: ws.headsetType\n    };\n\n    let clientJoinMessage = makeWsBroadcastMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_CLIENT_JOIN,\n        clientData\n    );\n\n    yield put( roomDataActions.publishMessageToRoomRequestAction( clientJoinMessage, roomName ) );\n\n    // start off a client inactivty check, to eject the client when it goes idle\n    yield fork( clientInactivityTimeoutTaskFunction, ws.id, ws.headsetType );\n};\n\nconst clientInactivityTimeoutTaskFunction = function* ( clientId, headsetType ) {\n\n    let serverState;\n    let ws;\n    let inactivityTimeoutPeriod = roomDataConstants.CLIENT_INFO.CLIENT_INACTIVITY_TIMEOUTS_IN_MS[ headsetType ];\n\n    try {\n        do {\n            serverState = yield select( ( state ) => { return state.serverReducer; } );\n            ws = serverState.websockets[ clientId ];\n\n            // websocket has disconnected, finish\n            if( typeof ws === 'undefined' ) {\n                break;\n            }\n\n            // websocket has gone too long without activity, eject\n            if( Date.now() - ws.lastAction > inactivityTimeoutPeriod ) {\n\n                // tell the client why it's being closed\n                let clientInactivityTimeoutMsg = makeWsReplyMessage(\n                    config.serverId,\n                    messageConstants.ERROR_TYPES.CLIENT_INACTIVITY_TIMEOUT,\n                    undefined\n                );\n\n                sendWsMessageWithLogger( ws, clientInactivityTimeoutMsg, logger );\n\n                // close it\n                ws.close();\n\n                // stop checking this client\n                break;\n\n            }\n\n            yield delay( roomDataConstants.CLIENT_INFO.CLIENT_INACTIVITY_TIMEOUT_CHECKS_IN_MS[ headsetType ] );\n        }\n        while( true );\n    }\n    catch( error ) {\n        logger.trace( `error checking client activity: ${error.message}` );\n    }\n};\n\n/*******************************************************************************\n* REMOVE CLIENT FROM ROOM DATA AND INFORM EXISTING CLIENTS IT'S LEFT\n*******************************************************************************/\n\n// when a local client is removed from a room, check whether it's got any local clients left\n// action: { clientId, roomName }\nconst handleRemoveLocalClientFromRoom = function* ( action ) {\n\n    let { roomState, roomData } = yield select(\n        ( state ) => {\n            return {\n                roomState:  state.roomStateReducer,\n                roomData:   state.roomDataReducer.rooms\n            };\n        }\n    );\n\n    // find any spheres (THERE CAN BE ONLY ONE) held by this client ...\n    let spheresHeld = Object.keys( roomData[ action.roomName ].content.spheres ).filter(\n        ( sphereId ) => {\n            if( !roomData[ action.roomName ].content.spheres[ sphereId ].hold ) {\n                return false;\n            }\n            return roomData[ action.roomName ].content.spheres[ sphereId ].hold.clientId === action.clientId;\n        }\n    );\n\n    // ... and release each one (THERE CAN BE ONLY ONE)\n    for( let sphereId of spheresHeld ) {\n        yield call( cancelSphereHold, sphereId, action.clientId, action.roomName );\n    }\n\n    if( roomState.rooms[ action.roomName ].status === roomStateConstants.STATES.ROOM_FULL ) {\n        // if the room's full\n        if( Object.keys( roomData[ action.roomName ].content.clients ).length < config.maxClientsPerRoom ) {\n            // tell the state machine about it\n            yield put( roomStateActions.unsetRoomFullRequestAction( action.roomName ) );\n        }\n    }\n\n    // tell remaining clients that this one's left\n    try {\n\n        let clientExitMessage = makeWsBroadcastMessage(\n            config.serverId,\n            messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_CLIENT_EXIT,\n            { [ outgoingMsgComponents.ROOM_CLIENT_EXIT.CLIENT_ID ]: action.clientId }\n        );\n\n        yield put( roomDataActions.publishMessageToRoomRequestAction( clientExitMessage, action.roomName ) );\n    }\n    catch( error ) {\n        logger.trace( `broadcast error trying to notify client exit for ${action.clientId}: ${error.message}` );\n    }\n\n    // tell the state machine to move to CHECKING_IF_EMPTY\n    yield put( roomStateActions.checkIfRoomEmptyRequestAction( action.roomName ) );\n};\n\n/*******************************************************************************\n* REMOVE A SPHERE HOLD WHEN CLIENT LEAVES OR TIMES OUT\n*******************************************************************************/\n\nconst cancelSphereHold = function* ( sphereId, clientId, roomName) {\n\n    // remove the hold on this sphere for this client\n    yield put(\n        roomDataActions.removeHoldOnSphereForClientInRoomRequestAction(\n            sphereId,\n            clientId,\n            roomName\n        )\n    );\n\n    // broadcast to the room that the sphere's been released\n    let outgoingMsgData = {\n        [ outgoingMsgComponents.ROOM_SPHERE_RELEASED.SPHERE_ID ]: sphereId,\n        [ outgoingMsgComponents.ROOM_SPHERE_RELEASED.CLIENT_ID ]: clientId\n\n    };\n\n    let sphereReleasedMessage = makeWsBroadcastMessage(\n        config.serverId,\n        messageConstants.OUTGOING_MESSAGE_TYPES.ROOM_SPHERE_RELEASED,\n        outgoingMsgData\n    );\n\n    yield put( roomDataActions.publishMessageToRoomRequestAction( sphereReleasedMessage, roomName ) );\n};\n\n/*******************************************************************************\n* WORK OUT WHETHER ANY CLIENTS ARE LEFT IN A ROOM, CLOSE ROOM IF NOT\n*******************************************************************************/\n\n// asked explicitly by state-machine event CHECK_IF_EMPTY\nconst checkIfRoomEmpty = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.CHECKING_IF_EMPTY\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing checkIfRoomEmpty, room ${action.roomName} not in state CHECKING_IF_EMPTY` );\n        return;\n    }\n\n    let roomData = yield select( ( state ) => { return state.roomDataReducer.rooms; } );\n    let numberClientsLeft = Object.keys( roomData[ action.roomName ].content.clients ).length;\n\n    if( numberClientsLeft > 0 ) {\n        yield put( roomStateActions.notifyRoomEmptyCheckFailureRequestAction( action.roomName ) );\n    }\n    else {\n        yield put( roomStateActions.notifyRoomEmptyCheckSuccessRequestAction( action.roomName ) );\n    }\n};\n\n// if a room is empty of local clients\nconst handleRoomEmptyCheckSuccess = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.ROOM_EMPTY\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing handleRoomEmptyCheckSuccess, room ${action.roomName} not in state ROOM_EMPTY` );\n        return;\n    }\n\n    let roomData = yield select( ( state ) => { return state.roomDataReducer.rooms; });\n\n    // if there's a heartbeat task running, it should stop\n    if( typeof roomData[ action.roomName ].tasks.heartbeat !== undefined ) {\n        yield put( roomStateActions.stopHeartbeatForRoomRequestAction( action.roomName ) );\n        return;\n    }\n\n};\n\n// asked explicitly by state-machine event STOP_HEARTBEAT\nconst stopHeartbeatForRoom = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.STOPPING_HEARTBEAT\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing stopHeartbeatForRoom, room ${action.roomName} not in state STOPPING_HEARTBEAT` );\n        return;\n    }\n\n    let roomData = yield select( ( state ) => { return state.roomDataReducer.rooms; } );\n\n    // belt & braces double-check to avoid crashing on cancel\n    if( typeof roomData[ action.roomName ].tasks.heartbeat !== undefined ) {\n\n        // ask the heartbeatTaskFunction to cancel\n        yield cancel( roomData[ action.roomName ].tasks.heartbeat );\n    }\n    else {\n        logger.warn( `problem stopping heartbeat for room ${action.roomName}: no task found` );\n        yield put( roomStateActions.notifyStopHeartbeatFailureRequestAction( action.roomName ) );\n    }\n};\n\n// once a server's stopped sending heartbeats for a room, it should re-INIT the room\nconst handleStopHeartbeatSuccessEvent = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.HEARTBEAT_STOPPED\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing handleStopHeartbeatSuccessEvent, room ${action.roomName} not in state HEARTBEAT_STOPPED` );\n        return;\n    }\n\n    yield put( roomStateActions.closeRoomRequestAction( action.roomName ) );\n};\n\n// just log out room close event\nconst handleRoomCloseEvent = function* ( action ) {\n\n    let correctState = yield roomStateUtils.roomIsCurrentlyInState(\n        action.roomName,\n        roomStateConstants.STATES.INIT\n    );\n\n    if( correctState !== true ) {\n        logger.warn( `not doing handleRoomCloseEvent, room ${action.roomName} not in state INIT` );\n        return;\n    }\n\n    logger.trace( `closed room ${action.roomName}` );\n\n};\n\n/*******************************************************************************\n* START A HOLD TIMEOUT WHEN A CLIENT GRABS A SPHERE\n*******************************************************************************/\n\n// triggers on roomDataActions.createHoldOnSphereForClientInRoomRequestAction\n// action: { sphereId, clientId, roomName }\nconst startSphereHoldTimeout = function* ( action ) {\n\n    if( config.timeoutSphereHolds !== true ) {\n        return;\n    }\n\n    let sphereHoldTimeoutTask = yield fork( sphereHoldTimeoutTaskFunction, action.sphereId, action.roomName );\n    yield put(\n        roomDataActions.setHoldTimeoutTaskForSphereInRoomRequestAction(\n            sphereHoldTimeoutTask,\n            action.sphereId,\n            action.roomName\n        )\n    );\n};\n\nconst sphereHoldTimeoutTaskFunction = function* ( sphereId, roomName ) {\n\n    try {\n\n        let sphereState;\n        let lastSphereAction;\n\n        do {\n            sphereState = yield select( ( state ) => { return state.roomDataReducer.rooms[ roomName ].content.spheres; } );\n\n            if( typeof sphereState[ sphereId ] === 'undefined' ) {\n                // sphere has been deleted\n                break;\n            }\n\n            if( typeof sphereState[ sphereId ].hold === 'undefined' ) {\n                // sphere has been released\n                break;\n            }\n\n            // check the last time anything was done with the sphere\n            lastSphereAction = sphereState[ sphereId ].hold.ts;\n\n            if( Date.now() - lastSphereAction >= roomDataConstants.SPHERE_INFO.SPHERE_HOLD_TIMEOUT_IN_MS ) {\n\n                // work out which client's holding it\n                let clientId = sphereState[ sphereId ].hold.clientId;\n\n                // get the websocket for the client\n                let serverState = yield select( ( state ) => { return state.serverReducer; } );\n                let client = serverState.websockets[ clientId ];\n\n                let timeoutMsgData = {\n                    [ messageConstants.OUTGOING_MESSAGE_COMPONENTS.SPHERE_HOLD_TIMEOUT.SPHERE_ID ]: sphereId\n                };\n\n                // tell the client it's losing the hold\n                let sphereHoldTimeOutMessage = makeWsReplyMessage(\n                    config.serverId,\n                    messageConstants.ERROR_TYPES.SPHERE_HOLD_TIMEOUT,\n                    timeoutMsgData\n                );\n\n                sendWsMessageWithLogger( client, sphereHoldTimeOutMessage, logger );\n\n                // delete the hold, tell the room\n                yield call( cancelSphereHold, sphereId, clientId, roomName );\n                break;\n            }\n            yield delay( roomDataConstants.SPHERE_INFO.SPHERE_HOLD_TIMEOUT_CHECK_IN_MS );\n\n        } while ( true );\n    }\n    catch( error ) {\n        logger.warn( `error in sphere hold timeout task for sphere ${sphereId}: ${error.message}` );\n    }\n    finally {\n        yield put( roomDataActions.deleteHoldTimeoutTaskForSphereInRoomRequestAction( sphereId, roomName ) );\n    }\n};\n\n/*******************************************************************************\n* PUBLISH A MESSAGE TO CLIENTS IN A ROOM, EXCEPT THE SENDER\n*******************************************************************************/\n\nconst publishMessageToRoom = function* ( action ) {\n\n    let roomName        = action.roomName;\n    let messagePacket   = action.message;\n    let messageFromId   = messagePacket[ outgoingMsgComponents.ALL_MESSAGES.FROM ];\n    let messageDataEnvelope     = messagePacket[ outgoingMsgComponents.ALL_MESSAGES.MSG ];\n    let messageData     = messageDataEnvelope[ outgoingMsgComponents.ALL_MESSAGES.DATA ];\n\n    // construct a message for interested websockets\n    let websocketMessage = {\n        [ outgoingMsgComponents.ALL_MESSAGES.FROM ]:   messageFromId,\n        [ outgoingMsgComponents.ALL_MESSAGES.MSG ]:    messageDataEnvelope\n    };\n\n    let { roomData, serverState } = yield select(\n        ( state ) => {\n            return {\n                roomData:       state.roomDataReducer.rooms,\n                serverState:    state.serverReducer\n            }\n        }\n    );\n\n    let roomClients     = roomData[ roomName ].content.clients;\n    let roomClientIds   = Object.keys( roomClients );\n    let clientsLength   = roomClientIds.length;\n    let serverSockets   = serverState.websockets;\n\n    for( let i = 0; i < clientsLength; i++ ) {\n\n        let clientId = roomClientIds[ i ];\n\n        if( clientId === messageFromId ) {\n            continue;\n        }\n\n        if( clientId === messageData[ outgoingMsgComponents.ALL_MESSAGES.CLIENT_ID ] ) {\n            continue;\n        }\n\n        if( typeof serverSockets[ clientId ] === 'undefined' ) {\n            continue;\n        }\n\n        let ws = serverSockets[ clientId ];\n\n        try {\n            sendWsMessageWithLogger( ws, websocketMessage, logger );\n        }\n        catch( error ) {\n            logger.warn( `error sending ${messageType} message to client ${clientId}: ${error.message}` );\n        }\n    }\n};\n\n/*******************************************************************************\n* WATCHER FUNCTIONS TO CATCH REDUX ACTIONS\n*******************************************************************************/\n\nconst watchStartRoomSetupRequests = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.START_ROOM_SETUP,\n        startRoomSetup\n    );\n};\n\nconst watchStartRoomSetupSuccessEvents = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.START_ROOM_SETUP_SUCCESS,\n        handleStartRoomSetupSuccess\n    );\n};\n\nconst watchInitRoomContentSuccessEvents = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.INIT_ROOM_CONTENT_SUCCESS,\n        handleInitRoomContentSuccess\n    );\n};\n\nconst watchStartHeartbeatRequestEvents = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.START_HEARTBEAT,\n        startHeartbeatForRoom\n    );\n};\n\nconst watchStartHeartbeatSuccessEvents = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.START_HEARTBEAT_SUCCESS,\n        startProcessingQueue\n    );\n};\n\nconst watchProcessQueueSuccessEvents = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.PROCESS_QUEUE_SUCCESS,\n        handleProcessQueueSuccess\n    );\n};\n\nconst watchAddWebsocketToServerStateRequests = function* () {\n    yield takeEvery(\n        serverConstants.ACTION_TYPES.ADD_WEBSOCKET_TO_STATE,\n        connectWebsocketToRoom\n    );\n};\n\nconst watchAddLocalClientToRoomRequests = function* () {\n    yield takeEvery(\n        roomDataConstants.ACTION_TYPES.ADD_LOCAL_CLIENT_TO_ROOM,\n        handleAddLocalClientToRoom\n    );\n};\n\nconst watchPublishMessageRequestActions = function* () {\n    yield takeEvery(\n        roomDataConstants.ACTION_TYPES.PUBLISH_MESSAGE_TO_ROOM,\n        publishMessageToRoom\n    );\n};\n\nconst watchStopHeartbeatRequests = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.STOP_HEARTBEAT,\n        stopHeartbeatForRoom\n    );\n};\n\nconst watchStopHeartbeatSuccessEvents = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.STOP_HEARTBEAT_SUCCESS,\n        handleStopHeartbeatSuccessEvent\n    );\n};\n\nconst watchRoomCloseEvents = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.CLOSE,\n        handleRoomCloseEvent\n    );\n};\n\nconst watchCreateHoldOnSphereEvents = function* () {\n    yield takeEvery(\n        roomDataConstants.ACTION_TYPES.CREATE_HOLD_ON_SPHERE_FOR_CLIENT_IN_ROOM,\n        startSphereHoldTimeout\n    );\n};\n\nconst watchRemoveLocalClientFromRoomRequests = function* () {\n    yield takeEvery(\n        roomDataConstants.ACTION_TYPES.REMOVE_LOCAL_CLIENT_FROM_ROOM,\n        handleRemoveLocalClientFromRoom\n    );\n};\n\nconst watchCheckIfRoomEmptyRequests = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.CHECK_IF_EMPTY,\n        checkIfRoomEmpty\n    );\n};\n\nconst watchRoomEmptyCheckSuccessEvents = function* () {\n    yield takeEvery(\n        roomStateConstants.EVENT_TYPES.EMPTY_CHECK_SUCCESS,\n        handleRoomEmptyCheckSuccess\n    );\n};\n\n/*******************************************************************************\n* EXPORT SAGAS\n*******************************************************************************/\n\nexport default {\n    setupSaga: function* () {\n        const sagas = [\n            watchStartRoomSetupRequests,\n            watchStartRoomSetupSuccessEvents,\n            watchInitRoomContentSuccessEvents,\n            watchStartHeartbeatRequestEvents,\n            watchStartHeartbeatSuccessEvents,\n            watchProcessQueueSuccessEvents,\n        ];\n\n        yield call( sagaUtils.spawnAutoRestartingSagas, sagas );\n    },\n    connectionSaga: function* () {\n\n        const sagas = [\n            watchAddWebsocketToServerStateRequests,\n            watchAddLocalClientToRoomRequests,\n        ];\n\n        yield call( sagaUtils.spawnAutoRestartingSagas, sagas );\n    },\n    publishSaga: function* () {\n        const sagas = [\n            watchPublishMessageRequestActions\n        ];\n\n        yield call( sagaUtils.spawnAutoRestartingSagas, sagas );\n    },\n    heartbeatSaga: function* () {\n\n        const sagas = [\n           watchStopHeartbeatRequests,\n           watchStopHeartbeatSuccessEvents,\n           watchRoomCloseEvents\n        ];\n\n        yield call( sagaUtils.spawnAutoRestartingSagas, sagas );\n    },\n    sphereHoldTimeoutSaga: function* () {\n\n        const sagas = [\n            watchCreateHoldOnSphereEvents\n        ];\n\n        yield call( sagaUtils.spawnAutoRestartingSagas, sagas );\n    },\n    disconnectionSaga: function* () {\n\n        const sagas = [\n            watchRemoveLocalClientFromRoomRequests,\n            watchCheckIfRoomEmptyRequests,\n            watchRoomEmptyCheckSuccessEvents\n        ];\n\n        yield call( sagaUtils.spawnAutoRestartingSagas, sagas );\n    }\n};\n"
  },
  {
    "path": "backend/src/rooms/room-state-actions.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport roomStateConstants from './room-state-constants';\n\n// setup room\nconst startRoomSetupRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.START_ROOM_SETUP;\n    return { type, roomName };\n};\n\nconst notifyStartRoomSetupSuccessRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.START_ROOM_SETUP_SUCCESS;\n    return { type, roomName };\n};\n\nconst notifyStartRoomSetupFailureRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.START_ROOM_SETUP_FAILURE;\n    return { type, roomName };\n};\n\n// initialise room content\nconst initRoomContentRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.INIT_ROOM_CONTENT;\n    return { type, roomName };\n};\n\nconst notifyInitRoomContentSuccessRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.INIT_ROOM_CONTENT_SUCCESS;\n    return { type, roomName };\n};\n\nconst notifyInitRoomContentFailureRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.INIT_ROOM_CONTENT_FAILURE;\n    return { type, roomName };\n};\n\n// start a heartbeat for a room\nconst startRoomHeartbeatRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.START_HEARTBEAT;\n    return { type, roomName };\n};\n\nconst notifyStartRoomHeartbeatSuccessRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.START_HEARTBEAT_SUCCESS;\n    return { type, roomName };\n};\n\nconst notifyStartRoomHeartbeatFailureRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.START_HEARTBEAT_FAILURE;\n    return { type, roomName };\n};\n\n// processing the queue for a newly-inited room\nconst startProcessingQueueRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.PROCESS_QUEUE;\n    return { type, roomName };\n};\n\nconst notifyProcessQueueSuccessRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.PROCESS_QUEUE_SUCCESS;\n    return { type, roomName };\n};\n\nconst notifyProcessQueueFailureRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.PROCESS_QUEUE_FAILURE;\n    return { type, roomName };\n};\n\n// mark a room full or not-full\nconst setRoomFullRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.SET_ROOM_FULL;\n    return { type, roomName };\n};\n\nconst unsetRoomFullRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.UNSET_ROOM_FULL;\n    return { type, roomName };\n};\n\n// check if a room's empty when a client leaves\nconst checkIfRoomEmptyRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.CHECK_IF_EMPTY;\n    return { type, roomName };\n};\n\nconst notifyRoomEmptyCheckSuccessRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.EMPTY_CHECK_SUCCESS;\n    return { type, roomName };\n};\n\nconst notifyRoomEmptyCheckFailureRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.EMPTY_CHECK_FAILURE;\n    return { type, roomName };\n};\n\n// stop a heartbeat for a room\nconst stopHeartbeatForRoomRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.STOP_HEARTBEAT;\n    return { type, roomName };\n};\n\nconst notifyStopHeartbeatSuccessRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.STOP_HEARTBEAT_SUCCESS;\n    return { type, roomName };\n};\n\nconst notifyStopHeartbeatFailureRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.STOP_HEARTBEAT_FAILURE;\n    return { type, roomName };\n};\n\n// close a room so it can be re-opened\nconst closeRoomRequestAction = ( roomName ) => {\n    let type = roomStateConstants.EVENT_TYPES.CLOSE;\n    return { type, roomName };\n};\n\nexport default {\n\n    // setup a room on request\n    startRoomSetupRequestAction,\n    notifyStartRoomSetupSuccessRequestAction,\n    notifyStartRoomSetupFailureRequestAction,\n\n    // initialise room content\n    initRoomContentRequestAction,\n    notifyInitRoomContentSuccessRequestAction,\n    notifyInitRoomContentFailureRequestAction,\n\n    // start a heartbeat for a room\n    startRoomHeartbeatRequestAction,\n    notifyStartRoomHeartbeatSuccessRequestAction,\n    notifyStartRoomHeartbeatFailureRequestAction,\n\n    // processing the queue for a newly-inited room\n    startProcessingQueueRequestAction,\n    notifyProcessQueueSuccessRequestAction,\n    notifyProcessQueueFailureRequestAction,\n\n    // mark a room full or not-full\n    setRoomFullRequestAction,\n    unsetRoomFullRequestAction,\n\n    // check if a room's empty when a client leaves\n    checkIfRoomEmptyRequestAction,\n    notifyRoomEmptyCheckSuccessRequestAction,\n    notifyRoomEmptyCheckFailureRequestAction,\n\n    // stop a heartbeat for a room\n    stopHeartbeatForRoomRequestAction,\n    notifyStopHeartbeatSuccessRequestAction,\n    notifyStopHeartbeatFailureRequestAction,\n\n    // close a room so it can be re-opened\n    closeRoomRequestAction\n\n};\n"
  },
  {
    "path": "backend/src/rooms/room-state-constants.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst MODULE_NAME   = 'room-state';\n\nconst EVENT_TYPES = ( () => {\n    let eventTypes = {\n\n        START_ROOM_SETUP:               'start_room_setup',\n        START_ROOM_SETUP_SUCCESS:       'start_room_setup_success',\n        START_ROOM_SETUP_FAILURE:       'start_room_setup_failure',\n\n        INIT_ROOM_CONTENT:              'init_room_content',\n        INIT_ROOM_CONTENT_SUCCESS:      'init_room_content_success',\n        INIT_ROOM_CONTENT_FAILURE:      'init_room_content_failure',\n        \n        START_HEARTBEAT:                'start_heartbeat',\n        START_HEARTBEAT_SUCCESS:        'start_heartbeat_success',\n        START_HEARTBEAT_FAILURE:        'start_heartbeat_failure',\n\n        PROCESS_QUEUE:                  'process_queue',\n        PROCESS_QUEUE_SUCCESS:          'process_queue_success',\n        PROCESS_QUEUE_FAILURE:          'process_queue_failure',\n\n        SET_ROOM_FULL:                  'set_room_full',\n        UNSET_ROOM_FULL:                'unset_room_full',\n\n        CHECK_IF_EMPTY:                 'check_if_empty',\n        EMPTY_CHECK_SUCCESS:            'empty_check_success',\n        EMPTY_CHECK_FAILURE:            'empty_check_failure',\n\n        STOP_HEARTBEAT:                 'stop_heartbeat',\n        STOP_HEARTBEAT_SUCCESS:         'stop_heartbeat_success',\n        STOP_HEARTBEAT_FAILURE:         'stop_heartbeat_failure',\n\n        CLOSE:                          'close',\n        RESET:                          'reset',\n        RESET_DONE:                     'reset_done'\n    };\n    \n    Object.keys( eventTypes ).forEach(\n        ( eventType ) => {\n            eventTypes[ eventType ] = `${MODULE_NAME}/${eventTypes[ eventType ]}`;\n        }\n    );\n\n    return eventTypes;\n\n})();\n\nconst STATES = ( () => {\n    let stateNames = {\n        INIT:                   'init',\n        ERROR:                  'error',\n\n        STARTING_ROOM_SETUP:    'starting_room_setup',\n        ROOM_SETUP_STARTED:     'room_setup_started',\n\n        INITING_ROOM_CONTENT:   'initing_room_content',\n        ROOM_CONTENT_INITED:    'room_content_inited',\n\n        STARTING_HEARTBEAT:     'starting_heartbeat',\n        HEARTBEAT_STARTED:      'heartbeat_started',\n\n        STOPPING_HEARTBEAT:     'stopping_heartbeat',\n        HEARTBEAT_STOPPED:      'heartbeat_stopped',\n\n        PROCESSING_QUEUE:       'processing_queue',\n\n        ROOM_FULL:              'room_full',\n        READY:                  'ready',\n\n        CHECKING_IF_EMPTY:      'checking_if_empty',\n        ROOM_EMPTY:             'room_empty',\n\n        RESETTING:              'resetting'\n    };\n\n    Object.keys( stateNames ).forEach(\n        ( stateName ) => {\n            stateNames[ stateName ] = `${MODULE_NAME}/${stateNames[ stateName ]}`;\n        }\n    );\n\n    return stateNames;\n\n})();\n\nexport {\n    EVENT_TYPES,\n    STATES\n};\n\nexport default {\n    EVENT_TYPES,\n    STATES\n};\n"
  },
  {
    "path": "backend/src/rooms/room-state-machine.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * heavily inspired by, and modified to\n * accommodate multiple-room states from:\n * https://github.com/realb0t/redux-state-machine\n * \n * basically provides a simple Redux wrapper around\n * https://github.com/jakesgordon/javascript-state-machine\n *\n * dynamically creates a new FSM/state-store per room\n * when requested in Redux actions, e.g.:\n * { type: 'ACTION_TYPE', roomName: 'my-room' }\n */\nimport StateMachine         from 'javascript-state-machine';\nimport sizeof               from 'object-sizeof';\nimport { fill, zipObject }  from 'lodash';\n\nimport { logger }           from '../logger';\nimport { initStateObject }  from '../utils/reducer-utils';\n\nimport roomNames            from './room-names';\nimport roomStateConstants   from './room-state-constants';\n\nconst initialFsmState = roomStateConstants.STATES.INIT;\n\n/**\n * Return new state object with status param\n * @param  {String} status status name param\n * @param  {Object} { action, error } addition params\n * @return {Object} new state object\n */\nconst buildState = ( status, { action = null, error = null } = {} ) => {\n    return { [ status ]: true, action, error, status };\n}\n\n/**\n * Default state object\n * @type {Object}\n */\nconst defaultState = {};\n\n/**\n * @param {Object} fsmConfig - Config as for javascript-state-machine\n */\nconst reducerBuilder = ( fsmConfig ) => {\n\n    const { events }    = fsmConfig;\n    const eventNames    = events.map( ( event ) => { return event.name; } );\n    const eventExists    = zipObject(\n        eventNames,\n        fill( Array( eventNames.length ), true, 0, eventNames.length )\n    );\n\n    // list of state machine objects keyed by room name\n    const machines = ( () => {\n        let createFunction = () => {\n            return StateMachine.create(\n                Object.assign( {}, fsmConfig, { initialFsmState } )\n            );\n        };\n\n        return initStateObject( roomNames, createFunction, 'room state machines', logger );\n    } )();\n\n    // dictionary of\n    // .rooms:          machine-state objects for rooms to be keyed by room name\n    // .roomsByState:   lists of rooms keyed by current state\n    const states = ( () => {\n\n        let createFunction = () => {\n            return buildState( initialFsmState );\n        };\n\n        return {\n            // objects containing the state for the machine for each room\n            rooms: initStateObject( roomNames, createFunction, 'room state objects', logger ),\n\n            // lookup table of which rooms are in which state on this server\n            roomsByState: ( () => {\n\n                let r = {};\n\n                Object.keys( roomStateConstants.STATES ).forEach(\n                    ( stateName ) => {\n                        r[ roomStateConstants.STATES[ stateName ] ] = {};\n                    }\n                );\n\n                roomNames.forEach(\n                    ( roomName ) => {\n                        r[ roomStateConstants.STATES.INIT ][ roomName ] = true;\n                    }\n                );\n\n                logger.trace( `initialised lookup table of ${Object.keys( r[ roomStateConstants.STATES.INIT ] ).length} rooms to INIT state` );\n\n                return r;\n            } )()\n        };\n\n    } )();\n\n    // Create reducer function\n    const reducer = ( state = states, action ) => {\n\n        // ignore events the FSM doesn't handle\n        if (typeof eventExists[ action.type ] === 'undefined') {\n            return state;\n        }\n\n        if( typeof action.type === 'undefined' ) {\n            logger.warn( `state-machine cannot execute ${action.type} action.type, check room-state-constants` );\n            return state;\n        }\n\n        if( typeof action.roomName === 'undefined' ) {\n            return state;\n        }\n\n        // make sure this is a room we have a state machine for\n        if( typeof machines[ action.roomName ] === 'undefined' ) {\n            logger.warn( `FSM for room ${action.roomName} does not exist` );\n            return state;\n        }\n\n        // make sure the states this module keeps track of includes this room\n        if( typeof states.rooms[ action.roomName ] === 'undefined' ) {\n            logger.warn( `FSM state for room ${action.roomName} does not exist` );\n            return state;\n        }\n\n        /*\n        logger.warn( `state machine asked to do ${action.type} from ${states.rooms[ action.roomName ].status}` );\n        */\n\n        // keep the two in sync\n        if ( machines[ action.roomName ].current !== states.rooms[ action.roomName ].status ) {\n            machines[ action.roomName ].current = states.rooms[ action.roomName ].status;\n        }\n\n        // check the requested transition is possible\n        if ( machines[ action.roomName ].cannot( action.type ) ) {\n            logger.error( `room ${action.roomName} cannot do ${action.type} from ${states.rooms[ action.roomName ].status}` );\n            // set error if not\n            states.rooms[ action.roomName ] = {\n                status: states.rooms[ action.roomName ].status,\n                error: true,\n                [ states.rooms[ action.roomName ].status ]: true,\n                action\n            };\n\n            return states;\n        }\n\n        let previousState = states.rooms[ action.roomName ].status;\n\n        // fire event as function\n        machines[ action.roomName ][ action.type ]( action );\n\n        // return state list containing new state for this room after transition\n        states.rooms[ action.roomName ] = {\n            status: JSON.parse( JSON.stringify( machines[ action.roomName ].current ) ),\n            error: null,\n            [ JSON.parse( JSON.stringify( machines[ action.roomName ].current ) ) ]: true,\n            action\n        };\n\n        // states.roomsByState[ STATES.FETCHING_TOPIC ][ 'mrko' ] = true;\n        states.roomsByState[ states.rooms[ action.roomName ].status ][ action.roomName ] = true;\n        states.roomsByState[ previousState ][ action.roomName ] = null;\n        delete states.roomsByState[ previousState ][ action.roomName ];\n\n        /*\n        logger.warn( `state machine did ${action.type} on ${action.roomName}` );\n        logger.warn( `room ${action.roomName} is now in state ${states.rooms[ action.roomName ].status}` );\n        */\n\n        return states;\n    };\n\n    // Open FSMs\n    reducer.machines = machines;\n    reducer.states = states;\n    reducer.eventExists = eventExists;\n\n    return reducer;\n};\n\nexport default reducerBuilder;\n"
  },
  {
    "path": "backend/src/rooms/room-state-reducer.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport reducerBuilder           from './room-state-machine';\n\nimport { EVENT_TYPES, STATES }  from './room-state-constants';\n\nlet e = EVENT_TYPES, s = STATES;\n\nconst reducer = reducerBuilder({\n    events: [\n        // send a start message so room-sagas can send a PubSub syncRoomSetup message\n        { name: e.INIT_ROOM_CONTENT,            from: s.INIT,                           to: s.INITING_ROOM_CONTENT },\n        { name: e.INIT_ROOM_CONTENT_SUCCESS,    from: s.INITING_ROOM_CONTENT,           to: s.ROOM_CONTENT_INITED },\n        { name: e.INIT_ROOM_CONTENT_FAILURE,    from: s.INITING_ROOM_CONTENT,           to: s.ERROR },\n\n        // once it's initialised the room, it should start a heartbeat\n        { name: e.START_HEARTBEAT,              from: s.ROOM_CONTENT_INITED,            to: s.STARTING_HEARTBEAT },\n        { name: e.START_HEARTBEAT_SUCCESS,      from: s.STARTING_HEARTBEAT,             to: s.HEARTBEAT_STARTED },\n        { name: e.START_HEARTBEAT_FAILURE,      from: s.STARTING_HEARTBEAT,             to: s.ERROR },\n\n        // when it's started a heartbeat, the server should connect websockets from the queue\n        { name: e.PROCESS_QUEUE,                from: s.HEARTBEAT_STARTED,              to: s.PROCESSING_QUEUE },\n\n        // when it's received all state from peers, the server should connect websockets from the queue\n        { name: e.PROCESS_QUEUE,                from: s.ALL_STATE_RECEIVED,             to: s.PROCESSING_QUEUE },\n        { name: e.PROCESS_QUEUE_SUCCESS,        from: s.PROCESSING_QUEUE,               to: s.READY },\n        { name: e.PROCESS_QUEUE_FAILURE,        from: s.PROCESSING_QUEUE,               to: s.ERROR },\n\n        // if all the clients in the queue left before the subscription was completed, start room closedown\n        { name: e.CHECK_IF_EMPTY,               from: s.PROCESSING_QUEUE,               to: s.CHECKING_IF_EMPTY },\n\n        // when config.maxClients have arrived, the server should mark the room as full\n        { name: e.SET_ROOM_FULL,                from: s.READY,                          to: s.ROOM_FULL },\n        { name: e.UNSET_ROOM_FULL,              from: s.ROOM_FULL,                      to: s.READY },\n\n        // when a client leaves the room, the server should check if there are any local clients left\n        { name: e.CHECK_IF_EMPTY,               from: s.READY,                          to: s.CHECKING_IF_EMPTY },\n        { name: e.CHECK_IF_EMPTY,               from: s.ROOM_FULL,                      to: s.CHECKING_IF_EMPTY },\n\n        // if so, it should just go back to waiting for others\n        { name: e.EMPTY_CHECK_FAILURE,          from: s.CHECKING_IF_EMPTY,              to: s.READY },\n        // it not, it should unsubscribe from the room\n        { name: e.EMPTY_CHECK_SUCCESS,          from: s.CHECKING_IF_EMPTY,              to: s.ROOM_EMPTY },\n\n        // if there aren't any local clients left, the handleRoomEmpty saga should work out whether to stop a heartbeat\n        { name: e.STOP_HEARTBEAT,               from: s.ROOM_EMPTY,                     to: s.STOPPING_HEARTBEAT },\n        { name: e.STOP_HEARTBEAT_SUCCESS,       from: s.STOPPING_HEARTBEAT,             to: s.HEARTBEAT_STOPPED },\n        { name: e.STOP_HEARTBEAT_FAILURE,       from: s.STOPPING_HEARTBEAT,             to: s.ERROR },\n\n        // when the heartbeat's stopped, reset the room to its INIT state\n        { name: e.CLOSE,                        from: s.HEARTBEAT_STOPPED,              to: s.INIT },\n\n        // if it gets into an error state, it should trigger a cleanup action\n        { name: e.RESET,                        from: s.ERROR,                          to: s.RESETTING },\n\n        // cleaning up a room resets it to init status\n        { name: e.RESET_SUCCESS,                from: s.RESETTING,                      to: s.INIT }\n    ]\n});\n\nexport default reducer;\n"
  },
  {
    "path": "backend/src/rooms/room-state-utils.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { select }   from 'redux-saga/effects';\n\nimport arrify       from 'arrify';\n\nconst roomIsCurrentlyInState = function* ( roomName, desiredState ) {\n\n    let roomState = yield select( ( state ) => { return state.roomStateReducer; } );\n\n    if( typeof roomState.rooms[ roomName ] === 'undefined' ) {\n        return false;\n    }\n\n    let statesToCheck   = arrify( desiredState );\n    let currentState    = roomState.rooms[ roomName ].status;\n    let roomIsInDesiredState = false;\n\n    for( let state of statesToCheck ) {\n        if( state === currentState ) {\n            return true;\n        }\n    }\n\n    return false;\n\n};\n\nexport default {\n    roomIsCurrentlyInState\n};\n"
  },
  {
    "path": "backend/src/s11n.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst serialize = ( obj ) => {\n    return JSON.stringify( obj );\n};\n\nconst deserialize = ( msg ) => {\n    try {\n        return JSON.parse( msg );\n    }\n    catch( error ) {\n        throw error;\n    }\n\n};\n\nexport { serialize, deserialize };\n"
  },
  {
    "path": "backend/src/server/app-server.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport express                  from 'express';\n\n\nconst lbHealthCheckUrl  = \"/lb-health-check\";\n\n/*\n * here's where we'd implement any custom HTTP routes\n */\nconst createApp = () => {\n\n    let app         = express();\n\n    // don't announce server software\n    app.use(\n        (req, res, next) => {\n            res.removeHeader('X-Powered-By');\n            next();\n        }\n    );\n\n    // say something at the root\n    app.get(\n        '/',\n        ( req, res ) => {\n            res.send( `forest\\n` );\n        }\n    );\n\n    // lb health check 200 response\n    app.get(\n        lbHealthCheckUrl,\n        ( req, res ) => {\n            res.send( `OK` );\n        }\n    );\n\n    return app;\n};\n\nexport default { createApp };\nexport { lbHealthCheckUrl };\n"
  },
  {
    "path": "backend/src/server/balancer.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { logger }           from '../logger';\nimport { messageConstants } from '../messages';\nimport { roomNames }        from '../rooms';\n\nimport {\n    dsClient,\n    datastoreConstants\n}\nfrom '../datastore';\n\nimport config               from '../config';\nimport { urlUtils }         from '../utils';\nimport roomStateConstants   from '../rooms/room-state-constants';\nimport { getStoreState }    from '../store';\n\nimport { privatiseTargetServerIfLocal } from './target-server-utils';\nimport { CUSTOM_PROXY_HEADERS }         from './server-constants';\n\nconst getBalancerFunction = ( proxy ) => {\n\n    let lastPortUsed = 0;\n    let headsetTypes = Object.values( messageConstants.HEADSET_TYPES );\n\n    let serverReducer = getStoreState().serverReducer;\n    let ring = serverReducer.hashRing;\n\n    return ( req, socket, head ) => {\n\n        // make sure client didn't try to use our custom headers\n        Object.values( CUSTOM_PROXY_HEADERS ).forEach(\n            ( headerName ) => {\n                delete req.headers[ headerName ];\n            }\n        );\n\n        let target;\n        let requestInfo;\n\n        // work out headsetType & optional roomName from URL\n        try {\n            requestInfo = urlUtils.getClientInfoFromUrl( req.url );\n\n            // pass headset type in custom proxy request header\n            req.headers[ CUSTOM_PROXY_HEADERS.X_CLIENT_HEADSET_TYPE ] = requestInfo.clientHeadsetType;\n\n            // pass requested room name if present\n            if( typeof requestInfo.roomName !== 'undefined' ) {\n                req.headers[ CUSTOM_PROXY_HEADERS.X_REQUESTED_ROOM_NAME ] = requestInfo.roomName;\n            }\n        }\n        catch( error ) {\n            // WS client requested an invalid URL\n            // forward to any WS server to close the connection gracefully\n            target = 'ws://' + privatiseTargetServerIfLocal( ring.get( req.url ) );\n            req.headers[ CUSTOM_PROXY_HEADERS.X_PLEASE_CLOSE_THIS_CONNECTION ] = true;\n            proxy.ws( req, socket, head, { target } );\n\n            return;\n        }\n\n        // a room was specified\n        if( typeof requestInfo.roomName !== 'undefined' ) {\n\n            // but it wasn't one in the pre-defined list\n            if( roomNames.indexOf( requestInfo.roomName ) < 0 ) {\n\n                // forward to any WS server to close the connection gracefully\n                target = 'ws://' + privatiseTargetServerIfLocal( ring.get( req.url ) );\n                req.headers[ CUSTOM_PROXY_HEADERS.X_PLEASE_CLOSE_THIS_CONNECTION ] = true;\n                proxy.ws( req, socket, head, { target } );\n\n                return;\n            }\n\n            // the specified room name was valid, proxy to the appropriate server\n            target = 'ws://' + privatiseTargetServerIfLocal( ring.get( requestInfo.roomName ) );\n\n            // tell the WS server this room was specified directly by the client\n            req.headers[ CUSTOM_PROXY_HEADERS.X_CLIENT_SPECIFIED_ROOM_NAME ] = true;\n            proxy.ws( req, socket, head, { target } );\n        }\n\n        // no room was specified, choose one\n        else {\n\n            let chosenRoomName;\n\n            // try to find one with availability for this type\n            let state   = getStoreState();\n            let avails  = state.roomDataReducer.avails[ requestInfo.clientHeadsetType ];\n            let availableRoomNames = Object.keys( avails );\n            let availableRoomCount = availableRoomNames.length;\n\n            if( availableRoomCount > 0 ) {\n                // choose one at random from the available list\n                chosenRoomName = availableRoomNames[ Math.floor( Math.random() * availableRoomCount ) ];\n            }\n\n            // otherwise, get an empty room that's servable from this server\n            else {\n\n                let emptyRoomNames      = Object.keys( state.roomStateReducer.roomsByState[ roomStateConstants.STATES.INIT ] );\n                let servableRoomNames   = state.serverReducer.servableRooms;\n\n                let servableEmptyRoomNames  = containsAll( emptyRoomNames, servableRoomNames );\n                let servableEmptyRoomCount  = servableEmptyRoomNames.length;\n\n                if( servableEmptyRoomCount === 0 ) {\n                    // client has to try again\n                    return;\n                }\n\n                chosenRoomName = servableEmptyRoomNames[ Math.floor( Math.random() * servableEmptyRoomCount ) ];\n            }\n\n            // the specified room name was valid, proxy to the appropriate server\n            target = 'ws://' + privatiseTargetServerIfLocal( ring.get( chosenRoomName ) );\n\n            req.headers[ CUSTOM_PROXY_HEADERS.X_REQUESTED_ROOM_NAME ] = chosenRoomName;\n            proxy.ws( req, socket, head, { target } );\n        }\n    }\n};\n\n// http://stackoverflow.com/a/11076088\n// ES6 arrow functions don't bind 'arguments'\n// so this has to be a 'function()'\nconst containsAll = function(/* pass all arrays here */) {\n    var output = [];\n    var cntObj = {};\n    var array, item, cnt;\n\n    // for each array passed as an argument to the function\n    for (var i = 0; i < arguments.length; i++) {\n        array = arguments[i];\n        // for each element in the array\n        for (var j = 0; j < array.length; j++) {\n            item = \"-\" + array[j];\n            cnt = cntObj[item] || 0;\n            // if cnt is exactly the number of previous arrays, \n            // then increment by one so we count only one per array\n            if (cnt == i) {\n                cntObj[item] = cnt + 1;\n            }\n        }\n    }\n\n    // now collect all results that are in all arrays\n    for (item in cntObj) {\n        if (cntObj.hasOwnProperty(item) && cntObj[item] === arguments.length) {\n            output.push(item.substring(1));\n        }\n    }\n\n    return(output);\n};    \n\nexport {\n    getBalancerFunction\n};\n"
  },
  {
    "path": "backend/src/server/index.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport serverActions    from './server-actions';\nimport serverConstants  from './server-constants';\nimport serverReducer    from './server-reducer';\nimport serverSagas      from './server-sagas';\n\nexport {\n    serverActions,\n    serverConstants,\n    serverReducer,\n    serverSagas\n};\n"
  },
  {
    "path": "backend/src/server/server-actions.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport constants from './server-constants';\n\n// reducer actions\nconst setSyncTopicRequestAction = ( syncTopic ) => {\n    let type = constants.ACTION_TYPES.SET_SYNC_TOPIC;\n    return { type, syncTopic };\n};\n\nconst setSyncSubscriptionRequestAction = ( syncSubscription ) => {\n    let type = constants.ACTION_TYPES.SET_SYNC_SUBSCRIPTION;\n    return { type, syncSubscription };\n};\n\nconst setAppServerRequestAction = ( appServer ) => {\n    let type = constants.ACTION_TYPES.SET_APP_SERVER;\n    return { type, appServer };\n};\n\nconst setWebsocketServerRequestAction = ( websocketServer ) => {\n    let type = constants.ACTION_TYPES.SET_WEBSOCKET_SERVER;\n    return { type, websocketServer };\n};\n\nconst setBalancerRequestAction = ( balancer ) => {\n    let type = constants.ACTION_TYPES.SET_BALANCER;\n    return { type, balancer };\n};\n\nconst setMonitorTaskForPeerRequestAction = ( monitorTask, peerId ) => {\n    let type = constants.ACTION_TYPES.SET_MONITOR_TASK_FOR_PEER;\n    return { type, monitorTask, peerId };\n};\n\nconst setLastHeartbeatForPeerRequestAction = ( data, peerId ) => {\n    let type = constants.ACTION_TYPES.SET_LAST_HEARTBEAT_FOR_PEER;\n    return { type, data, peerId };\n};\n\nconst removePeerFromListRequestAction = ( peerId ) => {\n    let type = constants.ACTION_TYPES.REMOVE_PEER_FROM_LIST;\n    return { type, peerId };\n};\n\nconst addWebsocketToStateRequestAction = ( ws ) => {\n    let type = constants.ACTION_TYPES.ADD_WEBSOCKET_TO_STATE;\n    return { type, ws };\n};\n\nconst setWebsocketTimeoutTaskForClientRequestAction = ( timeoutTask, clientId ) => {\n    let type = constants.ACTION_TYPES.SET_WEBSOCKET_TIMEOUT_TASK_FOR_CLIENT;\n    return { type, timeoutTask, clientId };\n};\n\nconst cancelWebsocketRoomJoinTimeoutTaskForClientRequestAction = ( clientId ) => {\n    let type = constants.ACTION_TYPES.CANCEL_WEBSOCKET_ROOM_JOIN_TIMEOUT_TASK_FOR_CLIENT;\n    return { type, clientId };\n};\n\nconst removeWebsocketTimeoutTaskForClientRequestAction = ( clientId ) => {\n    let type = constants.ACTION_TYPES.REMOVE_WEBSOCKET_TIMEOUT_TASK_FOR_CLIENT;\n    return { type, clientId };\n};\n\nconst removeWebsocketFromStateRequestAction = ( ws ) => {\n    let type = constants.ACTION_TYPES.REMOVE_WEBSOCKET_FROM_STATE;\n    return { type, ws };\n};\n\n// saga actions\nconst startServerSetupRequestAction = () => {\n    let type = constants.ACTION_TYPES.START_SERVER_SETUP;\n    return { type };\n};\n\nconst finishServerSetupRequestAction = () => {\n    let type = constants.ACTION_TYPES.FINISH_SERVER_SETUP;\n    return { type };\n};\n\nconst startSyncSubscriptionWarmupRequestAction = () => {\n    let type = constants.ACTION_TYPES.START_SYNC_SUBSCRIPTION_WARMUP;\n    return { type };\n};\n\nconst addSyncSubscriptionWarmupResponseRequestAction = () => {\n    let type = constants.ACTION_TYPES.ADD_SYNC_SUBSCRIPTION_WARMUP_RESPONSE;\n    return { type };\n};\n\nconst startSendingSyncHeartbeatRequestAction = () => {\n    let type = constants.ACTION_TYPES.START_SENDING_SYNC_HEARTBEAT;\n    return { type };\n};\n\nconst startSavingSubscriptionInfoRequestAction = () => {\n    let type = constants.ACTION_TYPES.START_SAVING_SUBSCRIPTION_INFO;\n    return { type };\n};\n\nconst loadRateLimiterInfoRequestAction = () => {\n    let type = constants.ACTION_TYPES.LOAD_RATE_LIMITER_INFO_FROM_DATASTORE;\n    return { type };\n};\n\nconst startCheckingDeadPeerSubscriptionsRequestAction = () => {\n    let type = constants.ACTION_TYPES.START_CHECKING_DEAD_PEER_SUBSCRIPTIONS;\n    return { type };\n};\n\nconst startMonitorForPeerRequestAction = ( serverId ) => {\n    let type = constants.ACTION_TYPES.START_MONITOR_FOR_PEER;\n    return { type, serverId };\n};\n\nexport default {\n    // reducer actions\n    setSyncTopicRequestAction,\n    setSyncSubscriptionRequestAction,\n    setAppServerRequestAction,\n    setWebsocketServerRequestAction,\n    setBalancerRequestAction,\n    setMonitorTaskForPeerRequestAction,\n    setLastHeartbeatForPeerRequestAction,\n    removePeerFromListRequestAction,\n    addWebsocketToStateRequestAction,\n    setWebsocketTimeoutTaskForClientRequestAction,\n    cancelWebsocketRoomJoinTimeoutTaskForClientRequestAction,\n    removeWebsocketTimeoutTaskForClientRequestAction,\n    removeWebsocketFromStateRequestAction,\n\n    // saga actions\n    startServerSetupRequestAction,\n    finishServerSetupRequestAction,\n    startSyncSubscriptionWarmupRequestAction,\n    addSyncSubscriptionWarmupResponseRequestAction,\n    startSendingSyncHeartbeatRequestAction,\n    startSavingSubscriptionInfoRequestAction,\n    loadRateLimiterInfoRequestAction,\n    startCheckingDeadPeerSubscriptionsRequestAction,\n    startMonitorForPeerRequestAction\n};\n"
  },
  {
    "path": "backend/src/server/server-constants.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst ACTION_TYPES = {\n\n    // reducer actions\n    SET_SYNC_TOPIC:                         'set_sync_topic',\n    SET_SYNC_SUBSCRIPTION:                  'set_sync_subscription',\n    SET_APP_SERVER:                         'set_app_server',\n    SET_WEBSOCKET_SERVER:                   'set_websocket_server',\n    SET_BALANCER:                           'set_balancer',\n    SET_MONITOR_TASK_FOR_PEER:              'set_monitor_task_for_peer',\n    SET_LAST_HEARTBEAT_FOR_PEER:            'set_last_heartbeat_for_peer',\n    REMOVE_PEER_FROM_LIST:                  'remove_peer_from_list',\n    ADD_WEBSOCKET_TO_STATE:                 'add_websocket_to_state',\n    SET_WEBSOCKET_TIMEOUT_TASK_FOR_CLIENT:  'set_websocket_timeout_task_for_client',\n    CANCEL_WEBSOCKET_ROOM_JOIN_TIMEOUT_TASK_FOR_CLIENT:   'cancel_websocket_room_join_timeout_task_for_client',\n    REMOVE_WEBSOCKET_TIMEOUT_TASK_FOR_CLIENT:   'remove_websocket_timeout_task_for_client',\n    REMOVE_WEBSOCKET_FROM_STATE:            'remove_websocket_from_state',\n\n    // saga actions\n    START_SERVER_SETUP:                     'start_server_setup',\n    FINISH_SERVER_SETUP:                    'finish_server_setup',\n    START_SYNC_SUBSCRIPTION_WARMUP:         'start_sync_subscription_warmup',\n    ADD_SYNC_SUBSCRIPTION_WARMUP_RESPONSE:  'add_sync_subscription_warmup_response',\n    START_SENDING_SYNC_HEARTBEAT:           'start_sending_sync_heartbeat',\n    START_SAVING_SUBSCRIPTION_INFO:         'start_saving_subscription_info',\n    LOAD_RATE_LIMITER_INFO_FROM_DATASTORE:  'load_rate_limiter_info_from_datastore',\n    START_CHECKING_DEAD_PEER_SUBSCRIPTIONS: 'start_checking_dead_peer_subscriptions',\n    START_MONITOR_FOR_PEER:                 'start_monitor_for_peer',\n};\n\nconst SYNC_INFO = {\n    SYNC_TOPIC_NAME:                        'server-sync',\n    SYNC_WARMUP_MESSAGE_LIST_LENGTH:        10,\n    SYNC_HEARTBEAT_PERIOD_IN_MS:            3000,\n    SYNC_HEARTBEAT_TIMEOUT_IN_MS:           15000,\n};\n\nconst SYNC_MESSAGES =  {\n    SYNC_HEARTBEAT:             'sync_heartbeat',\n    SYNC_WARMUP:                'sync_warmup',\n    LOAD_RATE_LIMITER_INFO:     'load_rate_limiter_info'\n};\n\nconst saveSubscriptionInfoPeriodInMs    = 5000;    // save subscription info to datastore every 5s\nconst checkSubscriptionInfoPeriodInMs   = saveSubscriptionInfoPeriodInMs * 6;   // check for dead peers twice a minute\n\nconst SAVE_INFO = {\n    SAVE_SUBSCRIPTION_INFO_PERIOD_IN_MS:    saveSubscriptionInfoPeriodInMs,\n    CHECK_SUBSCRIPTION_INFO_PERIOD_IN_MS:   checkSubscriptionInfoPeriodInMs\n};\n\nconst CUSTOM_PROXY_HEADERS = {\n    X_CLIENT_HEADSET_TYPE:          'x-client-headset-type',\n    X_CLIENT_SPECIFIED_ROOM_NAME:   'x-client-specified-room-name',\n    X_NO_ROOMS_AVAILABLE:           'x-no-rooms-available',\n    X_ROOM_ABOVE_THRESHOLD:         'x-room-above-threshold',\n    X_PLEASE_CLOSE_THIS_CONNECTION: 'x-please-close-this-connection',\n    X_REQUESTED_ROOM_NAME:          'x-requested-room-name'\n};\n\nconst ERROR_TYPES = {\n    ROOM_NAME_REQUIRED: 'room_name_required',\n    ILLEGAL_ROOM_NAME:  'illegal_room_name',\n\n    BAD_OS_EXIT_ERROR_CODE:    1\n};\n\nexport default {\n    ACTION_TYPES,\n    SYNC_INFO,\n    SYNC_MESSAGES,\n    SAVE_INFO,\n    CUSTOM_PROXY_HEADERS,\n    ERROR_TYPES\n};\n\nexport {\n    CUSTOM_PROXY_HEADERS,\n};\n"
  },
  {
    "path": "backend/src/server/server-reducer.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport redux    from 'redux';\nimport uuid     from 'uuid';\nimport HashRing from 'hashring';\n\nimport { logger }               from '../logger';\nimport roomDataConstants        from '../rooms/room-data-constants';\nimport roomNames                from '../rooms/room-names';\n\nimport { createFilteredActionHandler }  from '../utils/reducer-utils';\n\nimport config                   from '../config';\n\nimport serverConstants          from './server-constants';\n\nconst mapServableRooms = ( roomList, hashRing ) => {\n    return roomList.filter(\n        ( roomName ) => {\n            return hashRing.get( roomName ) === config.localIpPortString;\n        }\n    );\n};\n\nconst initialState = ( () => {\n\n    let obj = {\n        serverId:           config.serverId,\n        syncTopic:          null,\n        syncSubscription:   null,\n        syncWarmup: {\n            warmupStatus:   false,\n            receiptTimes:   []\n        },\n        appServer:          null,\n        websocketServer:    null,\n        balancer:           null,\n        peers:              {},\n        websockets:         {}\n    };\n\n    // used to consistently hash room names to target server addresses\n    obj.hashRing = new HashRing(\n        [ `${config.localIpAddress}:${config.serverPort}` ],\n        'md5',\n        { 'max cache size': 10000 }\n    );\n\n    // balancing based on what's available on the server a client arrives at\n    obj.servableRooms = mapServableRooms( roomNames, obj.hashRing );\n\n    return obj;\n\n} )();\n\nconst addSyncSubscriptionWarmupResponse = ( state, action ) => {\n\n    if( state.syncWarmup.receiptTimes.length >= serverConstants.SYNC_INFO.SYNC_WARMUP_MESSAGE_LIST_LENGTH ) {\n        state.syncWarmup.receiptTimes.shift();\n    }\n\n    state.syncWarmup.receiptTimes.push( Date.now() );\n    return state;\n};\n\n// action: { syncTopic }\nconst setSyncTopic = ( state, action ) => {\n\n    if( typeof action.syncTopic === 'undefined' ) {\n        return state;\n    }\n\n    state.syncTopic = action.syncTopic;\n    return state;\n};\n\n// action: { syncSubscription }\nconst setSyncSubscription = ( state, action ) => {\n\n    if( typeof action.syncSubscription === 'undefined' ) {\n        return state;\n    }\n\n    state.syncSubscription = action.syncSubscription;\n    return state;\n};\n\n// action: { appServer }\nconst setAppServer = ( state, action ) => {\n\n    if( typeof action.appServer === 'undefined' ) {\n        return state;\n    }\n\n    state.appServer = action.appServer;\n    return state;\n};\n\n// action: { websocketServer }\nconst setWebsocketServer = ( state, action ) => {\n\n    if( typeof action.websocketServer === 'undefined' ) {\n        return state;\n    }\n\n    state.websocketServer = action.websocketServer;\n    return state;\n};\n\n// action: { balancer }\nconst setBalancer = ( state, action ) => {\n\n    if( typeof action.peerId === 'undefined' ) {\n        return state;\n    }\n\n    state.balancer = action.balancer;\n    return state;\n};\n\n// action: { monitorTask, peerId }\nconst setMonitorTaskForPeer = ( state, action ) => {\n\n    if( typeof action.peerId === 'undefined' ) {\n        return state;\n    }\n\n    state.peers[ action.peerId ].monitorTask = action.monitorTask;\n    return state;\n};\n\n// action: { peerId, data }\nconst setLastHeartbeatForPeer = ( state, action ) => {\n\n    if( typeof action.peerId === 'undefined' ) {\n        return state;\n    }\n\n    if( !state.peers[ action.peerId ] ) {\n\n        state.peers[ action.peerId ] = {\n            lastHeartbeatTime:  0,\n            ip:                 action.data.ip,\n            port:               action.data.port\n        };\n\n        // add peer to balancer hash ring\n        let peerIpPortString = `${action.data.ip}:${action.data.port}`;\n        logger.info( `adding peer ${peerIpPortString}` );\n        state.hashRing.add( peerIpPortString );\n        logger.trace( `balancer hash ring is now:`, Object.keys( state.hashRing.vnodes ) );\n\n        // remap list of rooms this server should serve, based on new hash ring\n        let start = Date.now();\n        state.servableRooms = mapServableRooms( roomNames, state.hashRing );\n        let end = Date.now();\n        logger.info( `addPeerToList remapped ${state.servableRooms.length} rooms in ${end - start}ms` );\n    }\n\n    state.peers[ action.peerId ].lastHeartbeatTime = Date.now();\n    return state;\n};\n\n// action: { serverId }\nconst removePeerFromList = ( state, action ) => {\n\n    if( typeof action.peerId === 'undefined' ) {\n        return state;\n    }\n\n    if( state.peers[ action.peerId ] ) {\n\n        let peer = state.peers[ action.peerId ];\n        let peerIpPortString = `${peer.ip}:${peer.port}`;\n\n        // remove peer from balancer hash ring\n        logger.info( `removing peer ${peerIpPortString}` );\n        state.hashRing.remove( peerIpPortString );\n        logger.trace( `balancer hash ring is now:`, Object.keys( state.hashRing.vnodes ) );\n\n        // remap list of rooms this server should serve, based on new hash ring\n        let start = Date.now();\n        state.servableRooms = mapServableRooms( roomNames, state.hashRing );\n        let end = Date.now();\n        logger.info( `removePeerFromList remapped ${state.servableRooms.length} rooms in ${end - start}ms` );\n\n        state.peers[ action.peerId ] = null;\n        delete state.peers[ action.peerId ];\n    }\n\n    let remainingPeers = Object.keys( state.peers );\n    if( remainingPeers.length > 0 ) {\n        logger.info( `remaining peers: `, Object.keys( state.peers ));\n    }\n    else {\n        logger.info( `no remaining peers` );\n    }\n\n    return state;\n};\n\n// action: { ws }\nconst addWebsocketToState = ( state, action ) => {\n\n    if( typeof action.ws === 'undefined' ) {\n        return state;\n    }\n\n    state.websockets[ action.ws.id ] = action.ws;\n    let count = Object.keys( state.websockets ).length;\n    logger.trace( `[+] this server now has ${count} websockets` );\n\n    return state;\n};\n\nconst setWebsocketTimeoutTaskForClient = ( state, action ) => {\n\n    if( typeof action.clientId === 'undefined' ) {\n        return state;\n    }\n\n    state.websockets[ action.clientId ].timeoutTask = action.timeoutTask;\n    return state;\n};\n\nconst removeWebsocketTimeoutTaskFromClient = ( state, action ) => {\n\n    if( typeof action.clientId === 'undefined' ) {\n        return state;\n    }\n\n    state.websockets[ action.clientId ].timeoutTask = null;\n    delete state.websockets[ action.clientId ].timeoutTask;\n\n    return state;\n};\n\n// action: { ws, roomName }\nconst removeWebsocketFromState = ( state, action ) => {\n\n    if( typeof action.ws === 'undefined' ) {\n        return state;\n    }\n\n    if( !state.websockets[ action.ws.id ] ) {\n        return state;\n    }\n\n//    state.websockets[ action.ws.id ] = null;\n    delete state.websockets[ action.ws.id ];\n    let count = Object.keys( state.websockets ).length;\n    logger.trace( `[-] this server now has ${count} websockets` );\n\n    return state;\n};\n\n// action: { roomName, wsId }\nconst setQueuedRoomForWebsocket = ( state, action ) => {\n\n    if( typeof action.wsId === 'undefined' ) {\n        return state;\n    }\n\n    state.websockets[ action.wsId ].queuedRoom = action.roomName;\n\n    return state;\n};\n\n// action: { roomName, wsId }\nconst removeQueuedRoomForWebsocket = ( state, action ) => {\n\n    if( typeof action.wsId === 'undefined' ) {\n        return state;\n    }\n\n    state.websockets[ action.wsId ].queuedRoom = null;\n    delete state.websockets[ action.wsId ].queuedRoom;\n\n    return state;\n};\n\n// action: { clientId, roomName }\nconst setCurrentRoomForWebsocket = ( state, action ) => {\n\n    if( typeof action.clientId === 'undefined' ) {\n        return state;\n    }\n\n    if( !state.websockets[ action.clientId ] ) {\n        return state;\n    }\n\n    state.websockets[ action.clientId ].currentRoom = action.roomName;\n    state.websockets[ action.clientId ].queuedRoom = null;\n    delete state.websockets[ action.clientId ].queuedRoom;\n\n    return state;\n};\n\n// action: { clientId, roomName }\nconst removeCurrentRoomFromWebsocket = ( state, action ) => {\n\n    if( typeof action.clientId === 'undefined' ) {\n        return state;\n    }\n\n    if( !state.websockets[ action.clientId ] ) {\n        return state;\n    }\n\n    state.websockets[ action.clientId ].currentRoom = null;\n    delete state.websockets[ action.clientId ].currentRoom;\n\n    return state;\n};\n\nexport default createFilteredActionHandler( {\n    [ serverConstants.ACTION_TYPES.ADD_SYNC_SUBSCRIPTION_WARMUP_RESPONSE ]:     addSyncSubscriptionWarmupResponse,\n    [ serverConstants.ACTION_TYPES.SET_SYNC_TOPIC ]:                            setSyncTopic,\n    [ serverConstants.ACTION_TYPES.SET_SYNC_SUBSCRIPTION ]:                     setSyncSubscription,\n    [ serverConstants.ACTION_TYPES.SET_APP_SERVER ]:                            setAppServer,\n    [ serverConstants.ACTION_TYPES.SET_WEBSOCKET_SERVER ]:                      setWebsocketServer,\n    [ serverConstants.ACTION_TYPES.SET_BALANCER ]:                              setBalancer,\n    [ serverConstants.ACTION_TYPES.SET_MONITOR_TASK_FOR_PEER ]:                 setMonitorTaskForPeer,\n    [ serverConstants.ACTION_TYPES.SET_LAST_HEARTBEAT_FOR_PEER ]:               setLastHeartbeatForPeer,\n    [ serverConstants.ACTION_TYPES.REMOVE_PEER_FROM_LIST ]:                     removePeerFromList,\n\n    [ serverConstants.ACTION_TYPES.ADD_WEBSOCKET_TO_STATE ]:                    addWebsocketToState,\n    [ serverConstants.ACTION_TYPES.SET_WEBSOCKET_TIMEOUT_TASK_FOR_CLIENT ]:     setWebsocketTimeoutTaskForClient,\n    [ serverConstants.ACTION_TYPES.REMOVE_WEBSOCKET_TIMEOUT_TASK_FOR_CLIENT ]:  removeWebsocketTimeoutTaskFromClient,\n    [ serverConstants.ACTION_TYPES.REMOVE_WEBSOCKET_FROM_STATE ]:               removeWebsocketFromState,\n    [ roomDataConstants.ACTION_TYPES.ADD_WEBSOCKET_TO_QUEUE_FOR_ROOM ]:         setQueuedRoomForWebsocket,\n    [ roomDataConstants.ACTION_TYPES.ADD_LOCAL_CLIENT_TO_ROOM ]:                setCurrentRoomForWebsocket,\n    [ roomDataConstants.ACTION_TYPES.REMOVE_LOCAL_CLIENT_FROM_ROOM ]:           removeCurrentRoomFromWebsocket,\n}, initialState );\n"
  },
  {
    "path": "backend/src/server/server-sagas.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { delay, takeEvery, takeLatest } from 'redux-saga';\nimport { call, cancel, cancelled, put, fork, select } from 'redux-saga/effects';\nimport datastore from '@google-cloud/datastore';\n\nimport fetch from 'node-fetch';\n\nimport { logger }                   from '../logger';\nimport { messageConstants }         from '../messages';\nimport { pubsubClient }             from '../pubsub';\nimport { sendWsMessageWithLogger }  from '../utils/websocket-utils';\n\nimport {\n    setPerClientRateLimiterOptions,\n    setPerMessageTypeRateLimiterOptions\n} from '../messages/message-rate-limiter';\n\nimport {\n    dsClient,\n    datastoreConstants\n} from '../datastore';\n\nimport {\n    roomDataActions,\n    roomDataConstants,\n    roomStateActions,\n    roomStateConstants\n} from '../rooms';\n\nimport config               from '../config';\nimport { sagaUtils }        from '../utils';\n\nimport serverActions        from './server-actions';\nimport serverConstants      from './server-constants';\nimport serverSetup          from './server-setup';\nimport serverSync           from './server-sync';\n\nimport { lbHealthCheckUrl } from './app-server';\n\n/*******************************************************************************\n* SERVERS SEND SYNC HEARTBEATS UNTIL THEY DIE\n*******************************************************************************/\n\nconst startSendingSyncHeartbeat = function* () {\n\n    logger.info(\n        `starting to send sync heartbeats every ` +\n        `${serverConstants.SYNC_INFO.SYNC_HEARTBEAT_PERIOD_IN_MS}ms`\n    );\n\n    do {\n        try {\n            yield serverSync.sendSyncMessageOfType(\n                // send the local ip and the port of the websocket app server\n                // for remote balancers to add to their lists\n                // and check every SYNC_HEARTBEAT_TIMEOUT_IN_MS\n                { ip: config.localIpAddress, port: config.serverPort },\n                serverConstants.SYNC_MESSAGES.SYNC_HEARTBEAT\n            );\n        }\n        catch( error ) {\n            logger.error( `error sending sync heartbeat: ${error.message}` );\n        }\n\n        yield delay( serverConstants.SYNC_INFO.SYNC_HEARTBEAT_PERIOD_IN_MS );\n\n    } while ( true );\n};\n\n/*******************************************************************************\n* SERVERS CAN LOAD/RELOAD THEIR RATE LIMIT INFO FROM DATASTORE\n*******************************************************************************/\n\nconst loadRateLimiterInfo = function* () {\n    \n    logger.info( `loading rate limiter info for ${config.environmentName}` );\n\n    // check if any limit info is in the datastore for this environment\n    let query = dsClient.createQuery( datastoreConstants.DS_ENTITY_KEY_PER_ENVIRONMENT_RATE_LIMIT_INFO )\n        .filter( 'environmentName', '=', config.environmentName );\n\n    let queryResult = yield dsClient.runQuery( query );\n\n    if( queryResult[ 0 ].length <= 0 ) {\n        logger.trace( `no rate limiter info stored for ${config.environmentName}` );\n        return;\n    }\n\n    let rateLimitInfo = queryResult[ 0 ][ 0 ];\n\n    const limitTypes = [\n        'perClient_threshold',\n        'perClient_ttl_millisec',\n        'perMsgType_threshold',\n        'perMsgType_ttl_millisec'\n    ];\n\n    for( let t of limitTypes ) {\n        if( typeof rateLimitInfo[ t ] !== 'number' ) {\n            logger.trace( `${t} ${rateLimitInfo[ t ]} is not a number, discarding rate limit info` );\n            return;\n        }\n    }\n\n    // update the per-client rate limiter\n    let perClientRateLimiterOptions = {\n        threshold:      rateLimitInfo.perClient_threshold,\n        ttl_millisec:   rateLimitInfo.perClient_ttl_millisec\n    };\n\n    setPerClientRateLimiterOptions( perClientRateLimiterOptions );\n\n    // update the per-message-type rate limiter\n    let perMessageTypeRateLimiterOptions = {\n        threshold:      rateLimitInfo.perMsgType_threshold,\n        ttl_millisec:   rateLimitInfo.perMsgType_ttl_millisec\n    };\n\n    setPerMessageTypeRateLimiterOptions( perMessageTypeRateLimiterOptions );\n};\n\n/*******************************************************************************\n* SERVERS SAVE THEIR SUBSCRIPTION NAME WITH CURRENT TIMESTAMP UNTIL THEY DIE\n*******************************************************************************/\n\nconst startSavingSubscriptionInfo = function* () {\n\n    logger.info(\n        `starting to save subscription info every ` +\n        `${serverConstants.SAVE_INFO.SAVE_SUBSCRIPTION_INFO_PERIOD_IN_MS}ms`\n    );\n\n    let { roomState, serverState } = yield select(\n        ( state ) => {\n            return {\n                roomState: state.roomStateReducer,\n                serverState: state.serverReducer\n            };\n        }\n    );\n\n    let clientCount = Object.keys( serverState.websockets ).length;\n    let roomsInUse  = Object.keys( roomState.roomsByState[ roomStateConstants.STATES.READY ] ).length;\n\n    const topicName         = serverState.syncTopic.name.split( '/' ).pop();\n    const subscriptionName  = serverState.syncSubscription.name.split( '/' ).pop();\n\n    const key = dsClient.key( [ datastoreConstants.DS_ENTITY_KEY_SERVER_SUBSCRIPTION_INFO, config.serverId ] );\n\n    let data = {\n        serverId:   config.serverId,\n        ip:         config.localIpAddress,\n        port:       config.serverPort,\n        topicName,\n        subscriptionName,\n        clientCount,\n        roomsInUse\n    };\n\n    do {\n\n        ( { roomState, serverState } = yield select(\n            ( state ) => {\n                return {\n                    roomState: state.roomStateReducer,\n                    serverState: state.serverReducer\n                };\n            }\n        ) );\n\n        data.clientCount = Object.keys( serverState.websockets ).length;\n        data.roomsInUse  =\n            Object.keys( roomState.roomsByState[ roomStateConstants.STATES.READY ] ).length +\n            Object.keys( roomState.roomsByState[ roomStateConstants.STATES.ROOM_FULL ] ).length;\n\n        data.ts = new Date();\n\n        let entity = {\n            key,\n            method: 'upsert',\n            data\n        };\n\n        try {\n            let result = yield dsClient.save( entity );\n        }\n        catch( error ) {\n            logger.error( `error saving subscription info to datastore: ${error.message}` );\n        }\n\n        yield delay( serverConstants.SAVE_INFO.SAVE_SUBSCRIPTION_INFO_PERIOD_IN_MS );\n\n    } while ( true );\n};\n\n/*******************************************************************************\n* SERVERS PERIODICALLY CHECK DATASTORE FOR DEAD PEER SUBSCRIPTION INFO\n*******************************************************************************/\n\nconst startCheckingDeadPeerSubscriptions = function* () {\n\n    let possiblyDeadPeerPeriod = serverConstants.SAVE_INFO.SAVE_SUBSCRIPTION_INFO_PERIOD_IN_MS * 5;\n\n    logger.info( `starting to check peer subscriptions every ${possiblyDeadPeerPeriod}ms`);\n\n    do {\n\n        try {\n\n            let deadPeer = yield call( findFirstDeadPeerForCurrentDeployment, possiblyDeadPeerPeriod );\n\n            // any results\n            if( typeof deadPeer !== 'undefined' ) {\n\n                // that aren't somehow from this server\n                if( deadPeer.serverId !== config.serverId ) {\n                    // should maybe be deleted\n                    yield maybeDeletePeerSubscription( deadPeer );\n                }\n                else {\n                    logger.trace( `not deleting my own sync subscription` );\n                }\n            }\n\n        }\n        catch( error ) {\n            logger.error( `error trying to check dead peer subscriptions: ${error.message}` );\n        }\n\n        yield delay( serverConstants.SAVE_INFO.CHECK_SUBSCRIPTION_INFO_PERIOD_IN_MS );\n\n    } while( true );\n};\n\nconst findFirstDeadPeerForCurrentDeployment = ( possiblyDeadPeerPeriod ) => {\n\n    return new Promise(\n\n        ( resolve, reject ) => {\n\n            let dateNow = Date.now();\n            let olderThanDateThreshold = new Date( dateNow - possiblyDeadPeerPeriod );\n\n            let query = dsClient.createQuery( datastoreConstants.DS_ENTITY_KEY_SERVER_SUBSCRIPTION_INFO )\n                .filter( 'ts', '<', olderThanDateThreshold )\n                .order( 'ts', { descending: true } );\n\n            let stream = query.runStream();\n\n            let deadPeer;\n\n            stream\n                .on(\n                    'error',\n                    reject\n                )\n                .on(\n                    'data',\n                    ( entity ) => {\n\n                        if( typeof entity[ 'topicName' ] === 'undefined' ) {\n                            return;\n                        }\n\n                        if( entity.topicName !== config.syncTopicName ) {\n                            return;\n                        }\n\n                        let timeAgo = new Date( dateNow ) - entity.ts;\n                        if( timeAgo < possiblyDeadPeerPeriod ) {\n                            return;\n                        }\n\n                        deadPeer = entity;\n                        stream.end();\n                    }\n                )\n                .on(\n                    'info',\n                    ( info ) => {}\n                )\n                .on(\n                    'end',\n                    () => {\n                        resolve( deadPeer );\n                    }\n                )\n        }\n    );\n};\n\nconst maybeDeletePeerSubscription = function* ( entity ) {\n\n    let shouldDeletePeerSubscription = false;\n\n    try {\n        // first, check against the HTTP endpoint to see if it's still alive\n        let healthCheckUrl = `${'http://'}${entity.ip}:${entity.port}${lbHealthCheckUrl}`;\n        let options = {\n            timeout: 10000\n        };\n\n        logger.info( `health-checking possibly-dead peer: ${entity.topicName} | ${entity.serverId} | ${entity.ip}:${entity.port} | ${entity.ts.toISOString().replace(/^([^T]+)T([^Z]+)Z$/, '$1 $2' )}` );\n\n        // fetch() throws on network errors & timeouts\n        let result = yield fetch(\n            healthCheckUrl,\n            options\n        );\n\n        logger.trace( `got HTTP status ${result.status} from health-check for peer ${entity.serverId}` );\n\n    }\n    catch( error ) {\n        logger.info( `got network/timeout error health-checking peer ${entity.serverId}, marking for deletion` );\n        shouldDeletePeerSubscription = true;\n    }\n\n    if( shouldDeletePeerSubscription === false ) {\n\n        logger.trace( `found peer alive at ${entity.ip}:${entity.port} while checking subscription for peer ${entity.serverId}` );\n\n        let peerState = yield select( ( state ) => { return state.serverReducer.peers; } );\n\n        // if the peer's IP is alive but the ID isn't in the peer list,\n        // another server must have taken over the IP address, so this\n        // server should carry on and delete the old subscription info\n        if( Object.keys( peerState ).indexOf( entity.serverId ) > 0 ) {\n            return;\n        }\n\n        logger.info( `IP is responding but server ${entity.serverId} is not in peer list, deleting old subscription info` );\n    }\n\n    let topic = yield pubsubClient.getPubsubTopic( entity.topicName );\n\n    if( !topic.exists() ) {\n        logger.warn( `not deleting subscription to topic ${entity.topicName}, no such topic exists` );\n        return;\n    }\n\n    let subscription = topic.subscription( entity.subscriptionName );\n\n    if( !subscription.exists() ) {\n        logger.warn( `not deleting subscription ${entity.subscriptionName}, no such subscription exists` );\n        return;\n    }\n\n    try {\n        logger.info( `deleting PubSub subscription ${entity.subscriptionName}` );\n        let pubsubDeleteResult = yield subscription.delete();\n    }\n    catch( error ) {\n        // 404 just means another peer deleted this first\n        if( error.code !== 404 ) {\n            // other errors need to be flagged\n            logger.error( `error deleting subscription ${entity.subscriptionName} for supposedly-dead peer ${entity.serverId}: ${error.message}` );\n            return;\n        }\n    }\n\n    try {\n        logger.info( `deleting Datastore record for subscription ${entity.subscriptionName}` );\n        let datastoreDeleteResult = yield dsClient.delete( entity[ datastore.KEY ] );\n    }\n    catch( error ) {\n        logger.error( `error deleting Datastore record for supposedly-dead peer ${entity.serverId}: ${error.message}` );\n    }\n\n};\n\nconst checkMonitorForPeer = function* ( action ) {\n\n    let serverState = yield select( ( state ) => { return state.serverReducer; } );\n\n    // check if a monitor already exists\n    if( typeof serverState.peers[ action.peerId ].monitorTask === 'undefined' ) {\n\n        try {\n            // create one\n            let task = yield fork(\n                peerMonitorTaskFunction,\n                action.peerId,\n                action.data.ip,\n                action.data.port\n            );\n\n            // add to state so next check passes\n            yield put(\n                serverActions.setMonitorTaskForPeerRequestAction(\n                    task,\n                    action.peerId\n                )\n            );\n        }\n        catch( error ) {\n            logger.error( `error trying to create monitor task for peer ${action.peerId}` );\n        }\n    }\n};\n\nconst peerMonitorTaskFunction = function* ( peerId, ip, port )  {\n\n    try {\n        logger.info( `starting monitor for peer ${peerId} at ${ip}:${port}` );\n\n        do {\n\n            let peerState = yield select( ( state ) => { return state.serverReducer.peers; } );\n            let lastHeartbeatTime = peerState[ peerId ].lastHeartbeatTime;\n\n            // assuming the sync heartbeat has started\n            if( lastHeartbeatTime > 0 ) {\n\n                // if the last one was too long ago\n                if( ( Date.now() - lastHeartbeatTime ) > serverConstants.SYNC_INFO.SYNC_HEARTBEAT_TIMEOUT_IN_MS ) {\n\n                    try {\n\n                        // first, check against the HTTP endpoint to see if it's still alive\n                        let healthCheckUrl = `${'http://'}${ip}:${port}${lbHealthCheckUrl}`;\n                        let options = {\n                            timeout: 5000\n                        };\n\n                        // fetch() throws on network errors & timeouts\n                        let result = yield fetch(\n                            healthCheckUrl,\n                            options\n                        );\n\n                        // if it's not returning HTTP 200 OK, throw to trigger removal from peer list\n                        if( result.status !== 200 ) {\n                            throw new Error( `received HTTP status ${result.status} from health-check` );\n                        }\n                    }\n                    catch( error ) {\n\n                        // assume it's died\n                        logger.warn( `error health-checking peer ${peerId} after monitor timeout: ${error.message}` );\n\n                        // remove from peer list\n                        yield put(\n                            serverActions.removePeerFromListRequestAction( peerId )\n                        );\n\n                        break;\n                    }\n\n                }\n            }\n\n            // check every half second or so, assuming 5-second timeout\n            yield delay( Math.floor( serverConstants.SYNC_INFO.SYNC_HEARTBEAT_TIMEOUT_IN_MS / 8 ) );\n\n        } while ( true );\n\n    }\n    catch( error ) {\n        logger.warn( `error starting monitor for peer ${peerId} at ${ip}:${port}` );\n    }\n    finally {\n        if( yield cancelled() ) {\n            logger.trace( `cancelled monitor for peer ${peerId}` );\n        }\n    }\n\n};\n\n/*******************************************************************************\n* SAGA WATCHERS\n*******************************************************************************/\n\nconst watchStartServerSetupRequests = function* () {\n    yield takeEvery(\n        serverConstants.ACTION_TYPES.START_SERVER_SETUP,\n        serverSetup.startServerSetup\n    );\n};\n\nconst watchStartSyncSubscriptionWarmupRequests = function* () {\n    yield takeEvery(\n        serverConstants.ACTION_TYPES.START_SYNC_SUBSCRIPTION_WARMUP,\n        serverSetup.startSyncSubscriptionWarmup\n    );\n};\n\nconst watchFinishServerSetupRequests = function* () {\n    yield takeEvery(\n        serverConstants.ACTION_TYPES.FINISH_SERVER_SETUP,\n        serverSetup.finishServerSetup\n    );\n};\n\nconst watchStartSendingSyncHeartbeatRequests = function* () {\n    yield takeEvery(\n        serverConstants.ACTION_TYPES.START_SENDING_SYNC_HEARTBEAT,\n        startSendingSyncHeartbeat\n    );\n};\n\nconst watchLoadRateLimiterInfoRequests = function* () {\n    yield takeEvery(\n        serverConstants.ACTION_TYPES.LOAD_RATE_LIMITER_INFO_FROM_DATASTORE,\n        loadRateLimiterInfo\n    );\n};\n\nconst watchStartSavingSubscriptionInfoRequests = function* () {\n    yield takeEvery(\n        serverConstants.ACTION_TYPES.START_SAVING_SUBSCRIPTION_INFO,\n        startSavingSubscriptionInfo\n    );\n};\n\nconst watchStartCheckingDeadPeerSubscriptionsRequests = function* () {\n    yield takeEvery(\n        serverConstants.ACTION_TYPES.START_CHECKING_DEAD_PEER_SUBSCRIPTIONS,\n        startCheckingDeadPeerSubscriptions\n    );\n};\n\n\n/*******************************************************************************\n* WHEN SERVERS INTRODUCE THEMSELVES, START MONITORING THEIR HEARTBEATS\n*******************************************************************************/\nconst watchPeerHeartbeatEvents = function* () {\n    yield takeEvery(\n        serverConstants.ACTION_TYPES.SET_LAST_HEARTBEAT_FOR_PEER,\n        checkMonitorForPeer\n    );\n};\n\nexport default {\n    setupServerSaga: function* () {\n\n        const sagas = [\n            watchStartServerSetupRequests,\n            watchFinishServerSetupRequests\n        ];\n\n        yield sagaUtils.spawnAutoRestartingSagas( sagas );\n    },\n    syncSaga: function* () {\n\n        const sagas = [\n            watchStartSyncSubscriptionWarmupRequests,\n            watchStartSendingSyncHeartbeatRequests,\n            watchLoadRateLimiterInfoRequests,\n            watchStartSavingSubscriptionInfoRequests,\n            watchStartCheckingDeadPeerSubscriptionsRequests,\n            watchPeerHeartbeatEvents,\n        ];\n\n        yield sagaUtils.spawnAutoRestartingSagas( sagas );\n    }\n};\n"
  },
  {
    "path": "backend/src/server/server-setup.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport storage              from '@google-cloud/storage';\n\nimport { put, select }      from 'redux-saga/effects';\nimport { delay }            from 'redux-saga';\n\nimport { logger }           from '../logger';\nimport { pubsubClient }     from '../pubsub';\nimport { getStoreState }    from '../store';\n\nimport config               from '../config';\n\nimport serverActions        from './server-actions';\nimport serverConstants      from './server-constants';\nimport serverSync           from './server-sync';\nimport serverStartup        from './server-startup';\n\n/*\n * kick off setup, get sync channel topic & subscription\n */\nconst startServerSetup = function* ( action ) {\n\n    logger.info( `connecting to sync channel '${config.syncTopicName}'` );\n\n    // get the PubSub sync topic\n    let topic;\n    \n    try {\n        topic = yield getPubsubSyncTopic();\n    }\n    catch( error ) {\n        logger.error( `pubsub error creating sync channel: ${error.message}` );\n        process.exit( 1 );\n    }\n\n    // create a subscription for the sync topic\n    let subscription;\n\n    try {\n        subscription = yield getPubsubSyncSubscriptionForTopic( topic ); \n    }\n    catch( error ) {\n        logger.error( `pubsub error subscribing to sync channel: ${error.message}` ); \n        process.exit( 1 );\n    }\n\n    // set message & error handlers for the sync subscription\n    setSyncSubscriptionListeners( subscription );\n\n    // add sync subscription to server state\n    yield put( serverActions.setSyncSubscriptionRequestAction( subscription ) );\n\n    // add sync topic to server state\n    yield put( serverActions.setSyncTopicRequestAction( topic ) );\n\n    logger.trace( `connected to sync channel, subscription ${subscription.name}` );\n\n    // make sure the pubsub sync topic is warmed up so it doesn't kill monitors\n    yield put( serverActions.startSyncSubscriptionWarmupRequestAction() );\n\n    yield put( serverActions.loadRateLimiterInfoRequestAction() );\n};\n\n/*\n * warm up the sync channel Pubsub subscription\n */\nconst startSyncSubscriptionWarmup = function* ( action ) {\n\n    logger.info( `starting sync channel subscription warmup` );\n\n    let syncWarmupStartTime = Date.now();\n\n    while( true ) {\n        try {\n            yield serverSync.sendSyncMessageOfType(\n                null,\n                serverConstants.SYNC_MESSAGES.SYNC_WARMUP\n            );\n        }\n        catch( error ) {\n            // this will be a PubSub operational issue, and thus transient\n        };\n\n        // small delay period = lots of messages = hopefully warm up faster\n        yield delay( 10 );\n\n        let syncIsWarmedUp = yield getSyncWarmedUpStatus();\n\n        if( syncIsWarmedUp ) {\n            break;\n        }\n    }\n\n    // ready to roll\n    let syncWarmupTotalTime = (Date.now() - syncWarmupStartTime) / 1000;\n    logger.trace( `sync channel subscription warmup took ${syncWarmupTotalTime} seconds` );\n\n    // sync channel subscription is warmed up, start up the app/websocket servers\n    yield put( serverActions.finishServerSetupRequestAction() );\n};\n\n/*\n * work out whether the sync channel is warmed up yet\n */\nconst getSyncWarmedUpStatus = function* () {\n\n    let receiptTimes = getStoreState().serverReducer.syncWarmup.receiptTimes;\n\n    // make sure we have enough receipt times to make it worth calculating\n    if( receiptTimes.length < serverConstants.SYNC_INFO.SYNC_WARMUP_MESSAGE_LIST_LENGTH ) {\n        return false;\n    }\n\n    // work out the differences between the receipt times\n    let differences = [];\n    for( let i = 0; i < receiptTimes.length - 1 ; i++ ) {\n        differences[ i ] = receiptTimes[ i+1 ] - receiptTimes[ i ];\n    }\n\n    // 0 difference means they're still being batched\n    let nonZeroDifferences = differences.reduce(\n        ( acc, difference ) => {\n            if( difference == 0 ) {\n                return acc;\n            }\n            acc.push( 1 );\n            return acc;\n        },\n        []\n    );\n\n    // fail if less than half the list are still being batched\n    if( nonZeroDifferences.length < ( Math.ceil( serverConstants.SYNC_INFO.SYNC_WARMUP_MESSAGE_LIST_LENGTH ) / 2 ) ) {\n        return false;\n    }\n\n    // should be warmed up by now\n    return true;\n}\n\n/*\n * broadcast intro on sync channel, start app/websocket servers\n */\nconst finishServerSetup = function* ( action ) {\n\n    // decide whether to serve SSL, & get certs from storage if so\n    let sslInfo = config.sslInfo.useSsl ? yield getSslCertInfo() : { useSsl: false };\n\n    // start sending heartbeats to the sync channel\n    yield put( serverActions.startSendingSyncHeartbeatRequestAction() );\n\n    // start saving ID & sync subscription name to datastore\n    // so that peers can clear the subscription if this server dies\n    yield put( serverActions.startSavingSubscriptionInfoRequestAction() );\n\n    // start checking the datastore for peer subscription info saved\n    // long enough ago that the peer might be dead\n    yield put( serverActions.startCheckingDeadPeerSubscriptionsRequestAction() );\n\n    // create the app server & websocket server\n    let { app, wsServer, balancer } = serverStartup.startServer( sslInfo );\n\n    // save the app/websocket server & balancer references in the server state\n    yield put( serverActions.setAppServerRequestAction( app ) );\n    yield put( serverActions.setWebsocketServerRequestAction( wsServer ) );\n    yield put( serverActions.setBalancerRequestAction( balancer ) );\n\n};\n\n/*\n * retrieve SSL certificate data from Cloud Storage\n */\nconst getSslCertInfo = function* () {\n\n    logger.info(\n        `retrieving SSL cert data for ${config.sslInfo.sslCertHostName} ` +\n        `from bucket ${config.sslInfo.sslStorageBucketName}`\n    );\n\n    const gcs           = storage( { projectId: config.projectId } );\n    const bucket        = gcs.bucket( config.sslInfo.sslStorageBucketName );\n    const privKeyFile   = bucket.file( config.sslInfo.privKeyFileName );\n    const fullChainFile = bucket.file( config.sslInfo.fullChainFileName );\n\n    try {\n\n        let privKeyResult = yield privKeyFile.download();\n        let privKeyData = privKeyResult[ 0 ];\n\n        let fullChainResult = yield fullChainFile.download();\n        let fullChainData = fullChainResult[ 0 ];\n\n        return {\n            useSsl: true,\n            options: {\n                key:    privKeyData,\n                cert:   fullChainData\n            }\n        };\n\n    }\n\n    catch( error ) {\n\n        logger.error(\n            `error retrieving SSL cert data for ${config.sslInfo.sslCertHostName}, ` +\n            `unable to start SSL: got error code ${error.code} (${error.message}) ` +\n            `fetching ${error.response.req.path}`\n        );\n\n        process.exit( serverConstants.ERROR_TYPES.BAD_OS_EXIT_ERROR_CODE );\n    }\n};\n\nconst getPubsubSyncTopic = function* () {\n\n    // get the sync topic\n    let topic = yield pubsubClient.getPubsubTopic( config.syncTopicName );\n\n    // it should already exist, autoCreate only on first server/app boot.\n    // if it throws, let it throw, and be caught & exited above -\n    // this is critical to the app running.\n    let topicResult = yield topic.get( { autoCreate: true } );\n    topic = topicResult[ 0 ];\n    return topic;\n};\n\nconst getPubsubSyncSubscriptionForTopic = function* ( topic ) {\n\n    // create a sync channel subscription for this server\n    let subscriptionName    = [ config.syncTopicName, '-', config.serverId ].join( '' ); \n\n    let subscriptionOptions = {\n        autoAck: true,\n        maxInProgress: 20\n    };\n\n    // subscribe it to the topic\n    let subscriptionResult  = yield topic.subscribe( subscriptionName, subscriptionOptions );\n    let subscription        = subscriptionResult[ 0 ];\n    return subscription;\n};\n\nconst setSyncSubscriptionListeners = ( subscription ) => {\n\n    // set a message listener\n    let syncChannelMessageHandler = ( message ) => {\n        serverSync.handleSyncMessageWithLocalServerId( message, config.serverId );\n    };\n\n    subscription.on( 'message', syncChannelMessageHandler );\n\n    // set error handler\n    let syncChannelErrorHandler = ( error ) => {\n        serverSync.handleSyncErrorWithLocalServerId( error, config.serverId );\n    };\n\n    subscription.on( 'error', syncChannelErrorHandler );\n};\n\nexport default {\n    startServerSetup,\n    startSyncSubscriptionWarmup,\n    finishServerSetup\n};\n"
  },
  {
    "path": "backend/src/server/server-startup.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport http                     from 'http';\nimport https                    from 'https';\nimport httpProxy                from 'http-proxy';\nimport { FastRateLimit }        from 'fast-ratelimit';\n\nimport { logger }               from '../logger';\n\nimport config                   from '../config';\n\nimport { getBalancerFunction }  from './balancer';\n\nimport app_server               from './app-server';\nimport ws_server                from './websocket-server';\nimport serverConstants          from './server-constants';\n\nconst errorHandler = ( error ) => {\n    logger.error( `error starting server: ${error.message}` );\n    process.exit( serverConstants.ERROR_TYPES.BAD_OS_EXIT_ERROR_CODE );\n};\n\nconst startServer = ( sslInfo ) => {\n\n    // http server structure for room-server\n    let server = http.createServer();\n\n    // create websocket server to run the websocket app\n    let wsServer    = ws_server.startWebsocketServer( server );\n    wsServer.on( 'error', errorHandler );\n\n    // create express app to handle HTTP requests\n    let app         = app_server.createApp();\n    app.on( 'error', errorHandler );\n\n    // attach express app to plain-http server\n    server.on( 'request', app );\n\n    // create balancing proxy server to send room requests to correct nodes\n    let proxyServer;\n    let proxyTimeout = 2000;\n    let balancer;\n\n    // don't serve non-WS HTTP(S) requests\n    let httpFunction = ( req, res ) => {\n        res.statusCode = 403;\n        res.end();\n        return;\n    };\n\n    // decide whether to serve HTTPS ...\n    if( sslInfo.useSsl ) {\n\n        logger.info( `serving app over SSL using certs for hostname ${config.sslInfo.sslCertHostName}` );\n\n        let proxyOptions = {\n            ssl: sslInfo.options,\n            proxyTimeout\n        };\n\n        proxyServer = httpProxy.createProxyServer( proxyOptions );\n        balancer    = https.createServer( sslInfo.options, httpFunction );\n    }\n    // ... or HTTP\n    else {\n\n        logger.info( `serving app over plain HTTP` );\n\n        let proxyOptions = { proxyTimeout };\n\n        proxyServer = httpProxy.createProxyServer( proxyOptions );\n        balancer    = http.createServer( httpFunction );\n    }\n\n    // attach error handler to whichever of http/https is being used\n    proxyServer.on(\n        'error',\n        ( err ) => {\n            logger.error( `proxy error ${err.code} trying to '${err.syscall}' to ${err.address}:${err.port}` );\n        }\n    );\n\n    balancer.on(\n        'error',\n        ( err ) => {\n            logger.error( `balancer error:`, err );\n        }\n    );\n\n    // add the node-balancing websocket handler to the balancer server\n    balancer.on( 'upgrade', getBalancerFunction( proxyServer ) );\n\n    // run the server & the balancer\n    try {\n        server.listen(\n            config.serverPort,\n            () => {\n                logger.info( `starting WS server on port ${config.serverPort}` );\n                logger.info( `allowing ${config.maxClientsPerRoom} clients per room` );\n            }\n        );\n\n        balancer.listen(\n            config.balancerPort,\n            () => {\n                logger.info( `starting balancer on port ${config.balancerPort}` );\n            }\n        );\n\n        return {\n            app,\n            wsServer,\n            balancer\n        };\n    }\n    catch( error ) {\n        logger.error( `=> error starting server: ${error.message}` );\n        process.exit( serverConstants.ERROR_TYPES.BAD_OS_EXIT_ERROR_CODE );\n    }\n\n};\n\nexport default {\n    startServer\n};\n"
  },
  {
    "path": "backend/src/server/server-sync-handlers.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport serverActions                from './server-actions';\nimport serverConstants              from './server-constants';\n\nimport { logger }                   from '../logger';\n\nimport {\n    dispatchStoreAction,\n    getStoreState\n} from '../store';\n\n// new server, possibly this one, has joined & is warming up its sync channel subscription\nconst handleSyncWarmup = ( msgPayload, myId ) => {\n\n    // not interested in other servers' subscription warmup messages\n    if( msgPayload.from !== myId ) {\n        return;\n    }\n\n    // update our state with a new warmup response\n    dispatchStoreAction( serverActions.addSyncSubscriptionWarmupResponseRequestAction() );\n};\n\n// store peer's last sync heartbeat so as to be able to monitor over time\nconst handleSyncHeartbeat = ( msgPayload, myId ) => {\n    dispatchStoreAction(\n        serverActions.setLastHeartbeatForPeerRequestAction(\n            msgPayload.data,\n            msgPayload.from\n        )\n    );\n};\n\n// handle pubsub request to reload rater limiter info from datastore\nconst handleLoadRateLimiterInfo = () => {\n    dispatchStoreAction( serverActions.loadRateLimiterInfoRequestAction() );\n};\n\nexport default {\n    handleSyncWarmup,\n    handleSyncHeartbeat,\n    handleLoadRateLimiterInfo\n};\n"
  },
  {
    "path": "backend/src/server/server-sync.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { select }                   from 'redux-saga/effects';\n\nimport { logger }                   from '../logger';\nimport { serialize, deserialize }   from '../s11n';\n\nimport config                       from '../config';\n\nimport serverConstants              from './server-constants';\nimport serverSyncHandlers           from './server-sync-handlers';\n\n// wrapped in a closure because node.js doesn't seem to parse dots in object literal key definitions\nconst handlerFunctions = ( () => {\n    let m = serverConstants.SYNC_MESSAGES;\n\n    let obj = {};\n    obj[ m.SYNC_WARMUP ]            = serverSyncHandlers.handleSyncWarmup;\n    obj[ m.SYNC_HEARTBEAT ]         = serverSyncHandlers.handleSyncHeartbeat;\n    obj[ m.LOAD_RATE_LIMITER_INFO ] = serverSyncHandlers.handleLoadRateLimiterInfo;\n\n    return obj;\n} )();\n\nconst handleSyncMessageWithLocalServerId = ( message, localServerId ) => {\n\n    let msgPayload = deserialize( message.data );\n\n    // the only self-sent messages servers care about is warmup\n    let interestingSelfSentMessageTypes = [\n        serverConstants.SYNC_MESSAGES.SYNC_WARMUP\n    ];\n\n    // disregard uninteresting local messages\n    if( interestingSelfSentMessageTypes.indexOf( msgPayload.type ) < 0 ) {\n        if( msgPayload.from === localServerId ) {\n            return;\n        }\n    }\n\n    if( handlerFunctions[ msgPayload.type ] ) {\n        handlerFunctions[ msgPayload.type ]( msgPayload, localServerId );\n    }\n    else {\n        logger.trace( `got sync message of type ${msgPayload.type}: `, msgPayload );\n    }\n\n};\n\nconst handleSyncErrorWithLocalServerId = ( error, localServerId ) => {\n    logger.error( `server ${localServerId} exiting because of error on sync channel: ${error.message}` );\n    process.exit( 1 );\n};\n\nconst sendSyncMessageOfType = function* ( message, type ) {\n\n    let serverState     = yield select( ( state ) => { return state.serverReducer; } );\n    let syncTopic       = serverState.syncTopic;\n\n    let messageObject   = {\n        from:   config.serverId,\n        type:   type\n    };\n\n    if( message ) {\n        messageObject.data = message;\n    }\n\n    let serializedMessage   = serialize( messageObject );\n\n    return yield syncTopic.publish( serializedMessage );\n};\n\nexport default {\n    handleSyncMessageWithLocalServerId,\n    handleSyncErrorWithLocalServerId,\n    sendSyncMessageOfType\n};\n"
  },
  {
    "path": "backend/src/server/target-server-utils.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport config                   from '../config';\n\nconst localTargetServer     = `${config.localIpAddress}:${config.serverPort}`;\nconst privateTargetServer   = `127.0.0.1:${config.serverPort}`;\n\nconst privatiseTargetServerIfLocal = ( targetServer ) => {\n    if( targetServer === localTargetServer ) {\n        return privateTargetServer;\n    }\n    return targetServer;\n};\n\nexport {\n    privatiseTargetServerIfLocal\n};\n"
  },
  {
    "path": "backend/src/server/websocket-server.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Server }           from 'uws';\nimport uuid                 from 'uuid';\nimport url                  from 'url';\n\nimport { logger }           from '../logger';\nimport { messageHandler }   from '../messages';\n\nimport {\n    makeWsReplyMessage,\n    sendWsMessageWithLogger\n} from '../utils/websocket-utils';\n\nimport {\n    roomDataActions,\n    roomStateConstants\n} from '../rooms';\n\nimport {\n    dispatchStoreAction,\n    getStoreState\n} from '../store';\n\nimport config                   from '../config';\nimport messageConstants         from '../messages/message-constants';\nimport { perClientRateLimiter } from '../messages/message-rate-limiter';\nimport { urlUtils }             from '../utils';\n\nimport serverActions            from './server-actions';\nimport serverConstants          from './server-constants';\n\nconst CUSTOM_PROXY_HEADERS  = serverConstants.CUSTOM_PROXY_HEADERS;\nconst outgoingMsgComponents = messageConstants.OUTGOING_MESSAGE_COMPONENTS;\n\nconst startWebsocketServer = ( http_server ) => {\n\n    // create websocket server object\n    let wssConf = { server: http_server };\n    let wss     = new Server( wssConf );\n\n    // predefined 'invalid url' message\n    const badUrlMsg = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.ERROR_TYPES.INVALID_URL\n    );\n\n    // predefined 'no rooms available' message\n    const noRoomsAvailableMsg = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.ERROR_TYPES.NO_ROOMS_AVAILABLE\n    );\n    \n    // predefined 'room full' message\n    const roomFullMsg = makeWsReplyMessage(\n        config.serverId,\n        messageConstants.ERROR_TYPES.ROOM_FULL\n    );\n    \n    // set up an incoming websocket connection\n    wss.on( 'connection', ( ws ) => {\n\n        let headers = ws.upgradeReq.headers;\n\n        // if the proxy flagged no rooms available, close the connection\n        if( typeof headers[ CUSTOM_PROXY_HEADERS.X_NO_ROOMS_AVAILABLE ] !== 'undefined' ) {\n            sendWsMessageWithLogger( ws, noRoomsAvailableMsg, logger );\n            ws.close();\n            return;\n        }\n\n        // if the proxy marked the request to be closed, close the connection\n        if( typeof headers[ CUSTOM_PROXY_HEADERS.X_PLEASE_CLOSE_THIS_CONNECTION ] !== 'undefined' ) {\n            sendWsMessageWithLogger( ws, badUrlMsg, logger );\n            ws.close();\n            return;\n        }\n\n        // if some kind of problem stopped a name being specified, close the connection\n        if( typeof headers[ CUSTOM_PROXY_HEADERS.X_REQUESTED_ROOM_NAME ] === 'undefined' ) {\n            sendWsMessageWithLogger( ws, badUrlMsg, logger );\n            ws.close();\n            return;\n        }\n\n        // if this server's running in production ...\n        if( config.projectId === config.productionEnvironmentProjectId ) {\n            // ... and the client didn't provide the correct Origin header\n            let parsedUrl = url.parse( headers.origin );\n            if( parsedUrl.hostname !== config.productionEnvironmentRequiredOrigin ) {\n                sendWsMessageWithLogger( ws, badUrlMsg, logger );\n                ws.close();\n                return;\n            }\n        }\n\n        // generate UUID to identify connection\n        ws.id = uuid();\n        ws.lastAction = Date.now();\n\n        // get the headset type & optional requested room from custom headers set by proxy\n        ws.headsetType = ws.upgradeReq.headers[ CUSTOM_PROXY_HEADERS.X_CLIENT_HEADSET_TYPE ];\n        ws.requestedRoomName = ws.upgradeReq.headers[ CUSTOM_PROXY_HEADERS.X_REQUESTED_ROOM_NAME ];\n\n        // if the client requested the room directly\n        if( typeof headers[ CUSTOM_PROXY_HEADERS.X_CLIENT_SPECIFIED_ROOM_NAME ] !== 'undefined' ) {\n\n            let specifiedRoomName = headers[ CUSTOM_PROXY_HEADERS.X_REQUESTED_ROOM_NAME ];\n            let roomState = getStoreState().roomStateReducer;\n\n            // belt & braces\n            if( typeof roomState.rooms[ specifiedRoomName ] === undefined ) {\n                sendWsMessageWithLogger( ws, badUrlMsg, logger );\n                ws.close();\n                return;\n            }\n\n            // only MAX_CLIENTS_PER_ROOM allowed into client-specified room names\n            if( roomState.rooms[ specifiedRoomName ].status === roomStateConstants.STATES.ROOM_FULL ) {\n                sendWsMessageWithLogger( ws, roomFullMsg, logger );\n                ws.close();\n                return;\n            }\n\n            // TODO: clients only allowed into rooms with availability for their headset type\n            // check against roomDataReducer.avails[ clientHeadsetType ]\n            // -- empty rooms also allowed ( roomStateReducer.roomsByState )\n        }\n\n        // CLIENT: tell the client its id and the id of the server it's connected to\n        let clientMsgData = {\n            [ outgoingMsgComponents.CONNECTION_INFO.CLIENT_ID ]: ws.id,\n            [ outgoingMsgComponents.CONNECTION_INFO.SERVER_ID ]: config.serverId\n        };\n\n        let clientMsg = makeWsReplyMessage(\n            config.serverId,\n            messageConstants.OUTGOING_MESSAGE_TYPES.CONNECTION_INFO,\n            clientMsgData\n        );\n\n        try{\n            sendWsMessageWithLogger( ws, clientMsg, logger );\n        }\n        catch( error ) {\n            logger.trace( `error sending error message to client ${ws.id} while sending room info:`, error.message );\n        }\n\n        // add to the server's state, to make accessible via id\n        dispatchStoreAction(\n            serverActions.addWebsocketToStateRequestAction( ws )\n        );\n\n        // route messages from the connection via message-handler/validator/sagas\n        ws.on( 'message', ( message, flags ) => {\n\n            // drop all viewer messages in production?\n            if( config.dropViewerMessagesInProduction === true ) {\n                if( config.projectId === config.productionEnvironmentProjectId ) {\n                    if( ws.headsetType === messageConstants.HEADSET_TYPES.HEADSET_TYPE_VIEWER ) {\n                        return;\n                    }\n                }\n            }\n\n            // keep track of last time client did anything for inactivity-timeout\n            ws.lastAction = Date.now();\n\n            if( config.rateLimitInfo.perClientRateLimit === true ) {\n\n                // rate-limit overall msg rate per client\n                perClientRateLimiter.consume( ws.id )\n                    .then( () => {\n                        messageHandler.handleWebsocketMessage( ws, message );\n                     })\n                    .catch( () => {} );\n            }\n            else {\n                messageHandler.handleWebsocketMessage( ws, message );\n            }\n\n        } );\n\n        // add a handler for when the connection closes\n        ws.on( 'close', () => {\n\n            if( ws.currentRoom ) {\n\n                logger.trace( `client ${ws.id} disconnected, leaving room ${ws.currentRoom}` );\n                dispatchStoreAction(\n                    roomDataActions.removeLocalClientFromRoomRequestAction(\n                        ws.id,\n                        ws.currentRoom\n                    )\n                );\n            }\n            else {\n                logger.trace( `client ${ws.id} disconnected, not from any room` );\n            }\n\n            // remove from the server's state, to make inaccessible via id\n            dispatchStoreAction(\n                serverActions.removeWebsocketFromStateRequestAction( ws )\n            );\n\n        } );\n\n        // belt & braces\n        ws.on( 'error', ( error ) => {\n           logger.warn( `websocket error on connection ${ws.id}: ${error.message}` );\n        } );\n\n    } );\n\n    return wss;\n};\n\nexport default {\n    startWebsocketServer\n};\n"
  },
  {
    "path": "backend/src/spheres/index.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport sphereConstants  from './sphere-constants';\n\nimport messageConstants from '../messages/message-constants';\n\nconst incomingMsgComponents = messageConstants.INCOMING_MESSAGE_COMPONENTS;\nconst toneLabel         = incomingMsgComponents.CREATE_SPHERE_OF_TONE_AT_POSITION.TONE;\nconst positionLabel     = incomingMsgComponents.CREATE_SPHERE_OF_TONE_AT_POSITION.POSITION;\nconst connectionsLabel  = incomingMsgComponents.SET_SPHERE_CONNECTIONS.CONNECTIONS;\n\nconst makeSphereOfToneAtPosition = ( tone, position ) => {\n    return {\n        [ toneLabel ]:          tone,\n        [ positionLabel ]:      position,\n        [ connectionsLabel ]:   []\n    };\n};\n\nconst trees = [\n        require('./trees/0'),\n        require('./trees/1'),\n        require('./trees/2'),\n        require('./trees/3'),\n        require('./trees/4'),\n        require('./trees/5'),\n    ]\n\nexport {\n    makeSphereOfToneAtPosition,\n    sphereConstants,\n    trees\n};\n"
  },
  {
    "path": "backend/src/spheres/sphere-constants.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst SPHERE_INFO = {\n    DEFAULT_NUMBER_OF_SPHERES_PER_ROOM:     5,\n    MAX_NUMBER_OF_SPHERES_PER_ROOM:         50,\n    MAX_NUMBER_OF_CONNECTIONS_PER_SPHERE:   10,\n    SPHERE_HOLD_TIMEOUT_IN_MS:              5000,\n    MINIMUM_STRIKE_VELOCITY:                0,\n    MAXIMUM_STRIKE_VELOCITY:                127,\n    LOWEST_TONE:                            0,\n    HIGHEST_TONE:                           17\n};\n\nexport default {\n    SPHERE_INFO\n};\n"
  },
  {
    "path": "backend/src/spheres/trees/0.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport serverMessageConstants from '../../messages/message-constants';\nconst serverMsgComponents = serverMessageConstants.OUTGOING_MESSAGE_COMPONENTS.ROOM_STATUS_INFO;\nconst toneLabel = serverMsgComponents.TONE;\nconst positionLabel = serverMsgComponents.POSITION;\nconst meristemLabel = serverMsgComponents.MERISTEM;\nconst connectionsLabel = serverMsgComponents.CONNECTIONS;\n\nmodule.exports = {\n\t\"0\": {\n\t\t[ toneLabel ]: 7,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.3,\n\t\t\t\"z\": 0,\n\t\t\t\"y\": 1.2\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: []\n\t},\n\t\"1\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.14999999999999994,\n\t\t\t\"z\": 0.2598076211353316,\n\t\t\t\"y\": 1.2\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: []\n\t},\n\t\"2\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.15000000000000013,\n\t\t\t\"z\": -0.2598076211353315,\n\t\t\t\"y\": 1.2\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: []\n\t},\n\t\"3\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 3.061616997868383e-17,\n\t\t\t\"z\": 0.5,\n\t\t\t\"y\": 1.6\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: []\n\t},\n\t\"4\": {\n\t\t[ toneLabel ]: 12,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.5,\n\t\t\t\"z\": 6.123233995736766e-17,\n\t\t\t\"y\": 1.6\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: []\n\t},\n\t\"5\": {\n\t\t[ toneLabel ]: 8,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -9.184850993605148e-17,\n\t\t\t\"z\": -0.5,\n\t\t\t\"y\": 1.6\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: []\n\t},\n\t\"6\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.5,\n\t\t\t\"z\": -1.2246467991473532e-16,\n\t\t\t\"y\": 1.6\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: []\n\t},\n\t\"7\": {\n\t\t[ toneLabel ]: 0,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0,\n\t\t\t\"z\": 0,\n\t\t\t\"y\": 2\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: []\n\t}\n}"
  },
  {
    "path": "backend/src/spheres/trees/1.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport serverMessageConstants from '../../messages/message-constants';\nconst serverMsgComponents = serverMessageConstants.OUTGOING_MESSAGE_COMPONENTS.ROOM_STATUS_INFO;\nconst toneLabel = serverMsgComponents.TONE;\nconst positionLabel = serverMsgComponents.POSITION;\nconst meristemLabel = serverMsgComponents.MERISTEM;\nconst connectionsLabel = serverMsgComponents.CONNECTIONS;\n\nmodule.exports = {\n\t\"0\": {\n\t\t[ toneLabel ]: 21,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0,\n\t\t\t\"z\": 0,\n\t\t\t\"y\": 2\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: []\n\t},\n\t\"1\": {\n\t\t[ toneLabel ]: 14,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.21,\n\t\t\t\"z\": 0,\n\t\t\t\"y\": 1.7\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t2\n\t\t]\n\t},\n\t\"2\": {\n\t\t[ toneLabel ]: 16,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.10499999999999995,\n\t\t\t\"z\": 0.18186533479473213,\n\t\t\t\"y\": 1.7\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t1,\n\t\t\t3\n\t\t]\n\t},\n\t\"3\": {\n\t\t[ toneLabel ]: 18,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.1050000000000001,\n\t\t\t\"z\": -0.18186533479473205,\n\t\t\t\"y\": 1.7\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t2\n\t\t]\n\t},\n\t\"4\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.42,\n\t\t\t\"z\": 0,\n\t\t\t\"y\": 1.4\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t5\n\t\t]\n\t},\n\t\"5\": {\n\t\t[ toneLabel ]: 14,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.2099999999999999,\n\t\t\t\"z\": 0.36373066958946426,\n\t\t\t\"y\": 1.4\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t4,\n\t\t\t6\n\t\t]\n\t},\n\t\"6\": {\n\t\t[ toneLabel ]: 16,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.2100000000000002,\n\t\t\t\"z\": -0.3637306695894641,\n\t\t\t\"y\": 1.4\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t5\n\t\t]\n\t},\n\t\"7\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.63,\n\t\t\t\"z\": 0,\n\t\t\t\"y\": 1.1\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t8\n\t\t]\n\t},\n\t\"8\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.31499999999999984,\n\t\t\t\"z\": 0.5455960043841964,\n\t\t\t\"y\": 1.1\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t7,\n\t\t\t9\n\t\t]\n\t},\n\t\"9\": {\n\t\t[ toneLabel ]: 14,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.3150000000000003,\n\t\t\t\"z\": -0.5455960043841962,\n\t\t\t\"y\": 1.1\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t8\n\t\t]\n\t},\n\t\"10\": {\n\t\t[ toneLabel ]: 7,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.84,\n\t\t\t\"z\": 0,\n\t\t\t\"y\": 0.8\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t11\n\t\t]\n\t},\n\t\"11\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.4199999999999998,\n\t\t\t\"z\": 0.7274613391789285,\n\t\t\t\"y\": 0.8\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t10,\n\t\t\t12\n\t\t]\n\t},\n\t\"12\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.4200000000000004,\n\t\t\t\"z\": -0.7274613391789282,\n\t\t\t\"y\": 0.8\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t11\n\t\t]\n\t}\n}"
  },
  {
    "path": "backend/src/spheres/trees/2.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport serverMessageConstants from '../../messages/message-constants';\nconst serverMsgComponents = serverMessageConstants.OUTGOING_MESSAGE_COMPONENTS.ROOM_STATUS_INFO;\nconst toneLabel = serverMsgComponents.TONE;\nconst positionLabel = serverMsgComponents.POSITION;\nconst meristemLabel = serverMsgComponents.MERISTEM;\nconst connectionsLabel = serverMsgComponents.CONNECTIONS;\n\nmodule.exports = {\n\t\"0\": {\n\t\t[ toneLabel ]: 0,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.3,\n\t\t\t\"z\": 0,\n\t\t\t\"y\": 1.2\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t1,\n\t\t\t2\n\t\t]\n\t},\n\t\"1\": {\n\t\t[ toneLabel ]: 2,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.6687355423879241,\n\t\t\t\"z\": -0.20686414466293768,\n\t\t\t\"y\": 1.3\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t0\n\t\t]\n\t},\n\t\"2\": {\n\t\t[ toneLabel ]: 4,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.48296291314453416,\n\t\t\t\"z\": 0.12940952255126037,\n\t\t\t\"y\": 1.45\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t0,\n\t\t\t3,\n\t\t\t4\n\t\t]\n\t},\n\t\"3\": {\n\t\t[ toneLabel ]: 6,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.7938667524116582,\n\t\t\t\"z\": 0.42399950402726544,\n\t\t\t\"y\": 1.55\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t2\n\t\t]\n\t},\n\t\"4\": {\n\t\t[ toneLabel ]: 8,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.6062177826491071,\n\t\t\t\"z\": 0.3499999999999999,\n\t\t\t\"y\": 1.7\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t2,\n\t\t\t5,\n\t\t\t6\n\t\t]\n\t},\n\t\"5\": {\n\t\t[ toneLabel ]: 10,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 1.027104089199748,\n\t\t\t\"z\": 0.3937730183102396,\n\t\t\t\"y\": 1.8\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t4\n\t\t]\n\t},\n\t\"6\": {\n\t\t[ toneLabel ]: 12,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.636396103067893,\n\t\t\t\"z\": 0.6363961030678928,\n\t\t\t\"y\": 1.95\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t4\n\t\t]\n\t},\n\t\"7\": {\n\t\t[ toneLabel ]: 3,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.14999999999999994,\n\t\t\t\"z\": 0.2598076211353316,\n\t\t\t\"y\": 1.2\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t8,\n\t\t\t9\n\t\t]\n\t},\n\t\"8\": {\n\t\t[ toneLabel ]: 5,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.15521816678371875,\n\t\t\t\"z\": 0.6825740404529765,\n\t\t\t\"y\": 1.3\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t7\n\t\t]\n\t},\n\t\"9\": {\n\t\t[ toneLabel ]: 7,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.35355339059327373,\n\t\t\t\"z\": 0.3535533905932738,\n\t\t\t\"y\": 1.45\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t7,\n\t\t\t10,\n\t\t\t11\n\t\t]\n\t},\n\t\"10\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.7641277178854433,\n\t\t\t\"z\": 0.4755090227947147,\n\t\t\t\"y\": 1.55\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t9\n\t\t]\n\t},\n\t\"11\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.6062177826491069,\n\t\t\t\"z\": 0.3500000000000002,\n\t\t\t\"y\": 1.7\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t9,\n\t\t\t12,\n\t\t\t13\n\t\t]\n\t},\n\t\"12\": {\n\t\t[ toneLabel ]: 13,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.854569481781416,\n\t\t\t\"z\": 0.6926117244227405,\n\t\t\t\"y\": 1.8\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t11\n\t\t]\n\t},\n\t\"13\": {\n\t\t[ toneLabel ]: 15,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.8693332436601615,\n\t\t\t\"z\": 0.23293714059226894,\n\t\t\t\"y\": 1.95\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t11\n\t\t]\n\t},\n\t\"14\": {\n\t\t[ toneLabel ]: 4,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.15000000000000013,\n\t\t\t\"z\": -0.2598076211353315,\n\t\t\t\"y\": 1.2\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t15,\n\t\t\t16\n\t\t]\n\t},\n\t\"15\": {\n\t\t[ toneLabel ]: 6,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.5135173756042053,\n\t\t\t\"z\": -0.4757098957900386,\n\t\t\t\"y\": 1.3\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t14\n\t\t]\n\t},\n\t\"16\": {\n\t\t[ toneLabel ]: 8,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.12940952255126076,\n\t\t\t\"z\": -0.48296291314453405,\n\t\t\t\"y\": 1.45\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t14,\n\t\t\t17,\n\t\t\t18\n\t\t]\n\t},\n\t\"17\": {\n\t\t[ toneLabel ]: 10,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.0297390345262156,\n\t\t\t\"z\": -0.8995085268219799,\n\t\t\t\"y\": 1.55\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t16\n\t\t]\n\t},\n\t\"18\": {\n\t\t[ toneLabel ]: 12,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -1.2858791391047207e-16,\n\t\t\t\"z\": -0.7,\n\t\t\t\"y\": 1.7\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t16,\n\t\t\t19,\n\t\t\t20\n\t\t]\n\t},\n\t\"19\": {\n\t\t[ toneLabel ]: 14,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.17253460741833146,\n\t\t\t\"z\": -1.0863847427329798,\n\t\t\t\"y\": 1.8\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t18\n\t\t]\n\t},\n\t\"20\": {\n\t\t[ toneLabel ]: 16,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.2329371405922683,\n\t\t\t\"z\": -0.8693332436601617,\n\t\t\t\"y\": 1.95\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t18\n\t\t]\n\t}\n}"
  },
  {
    "path": "backend/src/spheres/trees/3.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport serverMessageConstants from '../../messages/message-constants';\nconst serverMsgComponents = serverMessageConstants.OUTGOING_MESSAGE_COMPONENTS.ROOM_STATUS_INFO;\nconst toneLabel = serverMsgComponents.TONE;\nconst positionLabel = serverMsgComponents.POSITION;\nconst meristemLabel = serverMsgComponents.MERISTEM;\nconst connectionsLabel = serverMsgComponents.CONNECTIONS;\n\nmodule.exports = {\n\t\"0\": {\n\t\t[ toneLabel ]: 19,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.3801701800236835,\n\t\t\t\"z\": 0.22714110856547276,\n\t\t\t\"y\": 0.9711673841367696\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t1\n\t\t]\n\t},\n\t\"1\": {\n\t\t[ toneLabel ]: 21,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.6427852083066042,\n\t\t\t\"z\": 0.009616761652669642,\n\t\t\t\"y\": 1.1711673841367696\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t0\n\t\t]\n\t},\n\t\"2\": {\n\t\t[ toneLabel ]: 18,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.3717230728255157,\n\t\t\t\"z\": -0.35540364805038743,\n\t\t\t\"y\": 1.0697267415202358\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t3\n\t\t]\n\t},\n\t\"3\": {\n\t\t[ toneLabel ]: 20,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.20030527807571613,\n\t\t\t\"z\": -0.6856251725306349,\n\t\t\t\"y\": 1.2697267415202358\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t2\n\t\t]\n\t},\n\t\"4\": {\n\t\t[ toneLabel ]: 17,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.2775516451627562,\n\t\t\t\"z\": -0.5157773829446608,\n\t\t\t\"y\": 1.3804880182088852\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t5\n\t\t]\n\t},\n\t\"5\": {\n\t\t[ toneLabel ]: 19,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.6683914801829096,\n\t\t\t\"z\": -0.4130372477082535,\n\t\t\t\"y\": 1.5804880182088852\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t4\n\t\t]\n\t},\n\t\"6\": {\n\t\t[ toneLabel ]: 16,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.6406669137194841,\n\t\t\t\"z\": 0.14622804231414932,\n\t\t\t\"y\": 1.5521284395792219\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t7\n\t\t]\n\t},\n\t\"7\": {\n\t\t[ toneLabel ]: 18,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.6283301758541369,\n\t\t\t\"z\": 0.5830052038036452,\n\t\t\t\"y\": 1.7521284395792218\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t6\n\t\t]\n\t},\n\t\"8\": {\n\t\t[ toneLabel ]: 15,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.03268723354108966,\n\t\t\t\"z\": 0.7278378056229577,\n\t\t\t\"y\": 1.4375443946415316\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t9\n\t\t]\n\t},\n\t\"9\": {\n\t\t[ toneLabel ]: 17,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.42773941827558637,\n\t\t\t\"z\": 0.8241868040756578,\n\t\t\t\"y\": 1.6375443946415316\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t8\n\t\t]\n\t},\n\t\"10\": {\n\t\t[ toneLabel ]: 14,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.7608452130361227,\n\t\t\t\"z\": 0.24721359549995825,\n\t\t\t\"y\": 1.3097886967409693\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t11\n\t\t]\n\t},\n\t\"11\": {\n\t\t[ toneLabel ]: 16,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.9781476007338058,\n\t\t\t\"z\": -0.2079116908177584,\n\t\t\t\"y\": 1.5097886967409693\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t10\n\t\t]\n\t},\n\t\"12\": {\n\t\t[ toneLabel ]: 13,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.48006736955111823,\n\t\t\t\"z\": -0.7272709782428493,\n\t\t\t\"y\": 1.461249175138151\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t13\n\t\t]\n\t},\n\t\"13\": {\n\t\t[ toneLabel ]: 15,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.06407587922682341,\n\t\t\t\"z\": -1.0695108533225732,\n\t\t\t\"y\": 1.6612491751381508\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t12\n\t\t]\n\t},\n\t\"14\": {\n\t\t[ toneLabel ]: 12,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.6203422273145859,\n\t\t\t\"z\": -0.7100388108034049,\n\t\t\t\"y\": 1.7744448880450852\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t15\n\t\t]\n\t},\n\t\"15\": {\n\t\t[ toneLabel ]: 14,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 1.0815170100561873,\n\t\t\t\"z\": -0.36938246566224114,\n\t\t\t\"y\": 1.9744448880450851\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t14\n\t\t]\n\t},\n\t\"16\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.9138398517295967,\n\t\t\t\"z\": 0.4400820782478085,\n\t\t\t\"y\": 1.8944794878661984\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t17\n\t\t]\n\t},\n\t\"17\": {\n\t\t[ toneLabel ]: 13,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.6840314990772564,\n\t\t\t\"z\": 1.0032899402408502,\n\t\t\t\"y\": 2.0944794878661983\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t16\n\t\t]\n\t},\n\t\"18\": {\n\t\t[ toneLabel ]: 10,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.19386177149566014,\n\t\t\t\"z\": 1.0682664104785127,\n\t\t\t\"y\": 1.7500029067545588\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t19\n\t\t]\n\t},\n\t\"19\": {\n\t\t[ toneLabel ]: 12,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.8313423444203109,\n\t\t\t\"z\": 0.9807808781086307,\n\t\t\t\"y\": 1.9500029067545588\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t18\n\t\t]\n\t},\n\t\"20\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -1.1524845401944908,\n\t\t\t\"z\": 0.10372548601683089,\n\t\t\t\"y\": 1.6579479983438095\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t21\n\t\t]\n\t},\n\t\"21\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -1.2314153711992457,\n\t\t\t\"z\": -0.5704848098486944,\n\t\t\t\"y\": 1.8579479983438094\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t20\n\t\t]\n\t},\n\t\"22\": {\n\t\t[ toneLabel ]: 8,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.4316890695856524,\n\t\t\t\"z\": -1.1502314125002475,\n\t\t\t\"y\": 1.8582964637551596\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t23\n\t\t]\n\t},\n\t\"23\": {\n\t\t[ toneLabel ]: 10,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.2340255877359067,\n\t\t\t\"z\": -1.4092722770336028,\n\t\t\t\"y\": 2.0582964637551595\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t22\n\t\t]\n\t},\n\t\"24\": {\n\t\t[ toneLabel ]: 7,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 1.051722092687431,\n\t\t\t\"z\": -0.7641208279802159,\n\t\t\t\"y\": 2.1618033988749894\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t25\n\t\t]\n\t},\n\t\"25\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 1.4917828430524098,\n\t\t\t\"z\": -0.15679269490148218,\n\t\t\t\"y\": 2.3618033988749896\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t24\n\t\t]\n\t}\n}"
  },
  {
    "path": "backend/src/spheres/trees/4.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport serverMessageConstants from '../../messages/message-constants';\nconst serverMsgComponents = serverMessageConstants.OUTGOING_MESSAGE_COMPONENTS.ROOM_STATUS_INFO;\nconst toneLabel = serverMsgComponents.TONE;\nconst positionLabel = serverMsgComponents.POSITION;\nconst meristemLabel = serverMsgComponents.MERISTEM;\nconst connectionsLabel = serverMsgComponents.CONNECTIONS;\n\nmodule.exports = {\n\t\"0\": {\n\t\t[ toneLabel ]: 7,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.75,\n\t\t\t\"z\": 0,\n\t\t\t\"y\": 1\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t1\n\t\t]\n\t},\n\t\"1\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.9310632489491795,\n\t\t\t\"z\": 0.18873586425530814,\n\t\t\t\"y\": 1.25\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t0\n\t\t]\n\t},\n\t\"2\": {\n\t\t[ toneLabel ]: 8,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.21822143065055674,\n\t\t\t\"z\": 0.2736410188638104,\n\t\t\t\"y\": 1.1428571428571428\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t3\n\t\t]\n\t},\n\t\"3\": {\n\t\t[ toneLabel ]: 12,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.384135321897057,\n\t\t\t\"z\": 0.3936242554404447,\n\t\t\t\"y\": 1.4928571428571429\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t2\n\t\t]\n\t},\n\t\"4\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.16689070046723575,\n\t\t\t\"z\": 0.7311959341363677,\n\t\t\t\"y\": 1.2857142857142856\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t5\n\t\t]\n\t},\n\t\"5\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.3911849258208314,\n\t\t\t\"z\": 0.8657218686221058,\n\t\t\t\"y\": 1.5357142857142856\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t4\n\t\t]\n\t},\n\t\"6\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.6757266509268144,\n\t\t\t\"z\": -0.3254128043381685,\n\t\t\t\"y\": 1.5714285714285714\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t7\n\t\t]\n\t},\n\t\"7\": {\n\t\t[ toneLabel ]: 15,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.8927946788297243,\n\t\t\t\"z\": -0.32468086092858855,\n\t\t\t\"y\": 1.9214285714285713\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t6\n\t\t]\n\t},\n\t\"8\": {\n\t\t[ toneLabel ]: 12,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.0778823268847101,\n\t\t\t\"z\": -0.34122476926363826,\n\t\t\t\"y\": 1.7142857142857144\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t9\n\t\t]\n\t},\n\t\"9\": {\n\t\t[ toneLabel ]: 14,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.01341837989470709,\n\t\t\t\"z\": -0.5498362911640168,\n\t\t\t\"y\": 1.9642857142857144\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t8\n\t\t]\n\t},\n\t\"10\": {\n\t\t[ toneLabel ]: 14,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.35,\n\t\t\t\"z\": -8.572527594031472e-17,\n\t\t\t\"y\": 2\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: []\n\t}\n}"
  },
  {
    "path": "backend/src/spheres/trees/5.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport serverMessageConstants from '../../messages/message-constants';\nconst serverMsgComponents = serverMessageConstants.OUTGOING_MESSAGE_COMPONENTS.ROOM_STATUS_INFO;\nconst toneLabel = serverMsgComponents.TONE;\nconst positionLabel = serverMsgComponents.POSITION;\nconst meristemLabel = serverMsgComponents.MERISTEM;\nconst connectionsLabel = serverMsgComponents.CONNECTIONS;\n\nmodule.exports = {\n\t\"0\": {\n\t\t[ toneLabel ]: 7,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.5,\n\t\t\t\"z\": 0,\n\t\t\t\"y\": 1.3\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t1\n\t\t]\n\t},\n\t\"1\": {\n\t\t[ toneLabel ]: 5,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.8955037487502232,\n\t\t\t\"z\": 0.08985007498214534,\n\t\t\t\"y\": 1.4000000000000001\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t0\n\t\t]\n\t},\n\t\"2\": {\n\t\t[ toneLabel ]: 8,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.11126046697815722,\n\t\t\t\"z\": 0.4874639560909118,\n\t\t\t\"y\": 1.3357142857142859\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t3\n\t\t]\n\t},\n\t\"3\": {\n\t\t[ toneLabel ]: 6,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.1116709845215571,\n\t\t\t\"z\": 0.8930451227211232,\n\t\t\t\"y\": 1.435714285714286\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t2\n\t\t]\n\t},\n\t\"4\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.4504844339512095,\n\t\t\t\"z\": 0.21694186955877912,\n\t\t\t\"y\": 1.3714285714285714\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t5\n\t\t]\n\t},\n\t\"5\": {\n\t\t[ toneLabel ]: 7,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.8458054852071072,\n\t\t\t\"z\": 0.3075923945639262,\n\t\t\t\"y\": 1.4714285714285715\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t4\n\t\t]\n\t},\n\t\"6\": {\n\t\t[ toneLabel ]: 10,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.31174490092936685,\n\t\t\t\"z\": -0.39091574123401485,\n\t\t\t\"y\": 1.4071428571428573\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t7\n\t\t]\n\t},\n\t\"7\": {\n\t\t[ toneLabel ]: 8,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.4880898375488758,\n\t\t\t\"z\": -0.756153628888675,\n\t\t\t\"y\": 1.5071428571428573\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t6\n\t\t]\n\t},\n\t\"8\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.3117449009293667,\n\t\t\t\"z\": -0.39091574123401496,\n\t\t\t\"y\": 1.4428571428571428\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t9\n\t\t]\n\t},\n\t\"9\": {\n\t\t[ toneLabel ]: 9,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.6285850721951838,\n\t\t\t\"z\": -0.6441124179934553,\n\t\t\t\"y\": 1.542857142857143\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t8\n\t\t]\n\t},\n\t\"10\": {\n\t\t[ toneLabel ]: 12,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.45048443395120963,\n\t\t\t\"z\": 0.21694186955877895,\n\t\t\t\"y\": 1.4785714285714286\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t11\n\t\t]\n\t},\n\t\"11\": {\n\t\t[ toneLabel ]: 10,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.7678365122206151,\n\t\t\t\"z\": 0.46949663523914764,\n\t\t\t\"y\": 1.5785714285714287\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t10\n\t\t]\n\t},\n\t\"12\": {\n\t\t[ toneLabel ]: 13,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.11126046697815704,\n\t\t\t\"z\": 0.48746395609091187,\n\t\t\t\"y\": 1.5142857142857142\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t13\n\t\t]\n\t},\n\t\"13\": {\n\t\t[ toneLabel ]: 11,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.2868656765450031,\n\t\t\t\"z\": 0.8530580775189798,\n\t\t\t\"y\": 1.6142857142857143\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t12\n\t\t]\n\t},\n\t\"14\": {\n\t\t[ toneLabel ]: 14,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.5,\n\t\t\t\"z\": 1.8369701987210297e-16,\n\t\t\t\"y\": 1.55\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t15\n\t\t]\n\t},\n\t\"15\": {\n\t\t[ toneLabel ]: 12,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.8955037487502232,\n\t\t\t\"z\": -0.08985007498214469,\n\t\t\t\"y\": 1.6500000000000001\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t14\n\t\t]\n\t},\n\t\"16\": {\n\t\t[ toneLabel ]: 15,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.1112604669781574,\n\t\t\t\"z\": -0.48746395609091175,\n\t\t\t\"y\": 1.5857142857142859\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t17\n\t\t]\n\t},\n\t\"17\": {\n\t\t[ toneLabel ]: 13,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.11167098452155783,\n\t\t\t\"z\": -0.8930451227211231,\n\t\t\t\"y\": 1.685714285714286\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t16\n\t\t]\n\t},\n\t\"18\": {\n\t\t[ toneLabel ]: 16,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.45048443395120946,\n\t\t\t\"z\": -0.21694186955877928,\n\t\t\t\"y\": 1.6214285714285714\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t19\n\t\t]\n\t},\n\t\"19\": {\n\t\t[ toneLabel ]: 14,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.8458054852071069,\n\t\t\t\"z\": -0.3075923945639269,\n\t\t\t\"y\": 1.7214285714285715\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t18\n\t\t]\n\t},\n\t\"20\": {\n\t\t[ toneLabel ]: 17,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.31174490092936696,\n\t\t\t\"z\": 0.39091574123401474,\n\t\t\t\"y\": 1.6571428571428573\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t21\n\t\t]\n\t},\n\t\"21\": {\n\t\t[ toneLabel ]: 15,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.4880898375488761,\n\t\t\t\"z\": 0.7561536288886749,\n\t\t\t\"y\": 1.7571428571428573\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t20\n\t\t]\n\t},\n\t\"22\": {\n\t\t[ toneLabel ]: 18,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.31174490092936585,\n\t\t\t\"z\": 0.3909157412340156,\n\t\t\t\"y\": 1.6928571428571428\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t23\n\t\t]\n\t},\n\t\"23\": {\n\t\t[ toneLabel ]: 16,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.6285850721951823,\n\t\t\t\"z\": 0.6441124179934566,\n\t\t\t\"y\": 1.792857142857143\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t22\n\t\t]\n\t},\n\t\"24\": {\n\t\t[ toneLabel ]: 19,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.4504844339512097,\n\t\t\t\"z\": -0.21694186955877878,\n\t\t\t\"y\": 1.7285714285714286\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t25\n\t\t]\n\t},\n\t\"25\": {\n\t\t[ toneLabel ]: 17,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": -0.7678365122206144,\n\t\t\t\"z\": -0.4694966352391487,\n\t\t\t\"y\": 1.8285714285714287\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t24\n\t\t]\n\t},\n\t\"26\": {\n\t\t[ toneLabel ]: 20,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.11126046697815686,\n\t\t\t\"z\": -0.48746395609091187,\n\t\t\t\"y\": 1.7642857142857142\n\t\t},\n\t\t[ meristemLabel ]: true,\n\t\t[ connectionsLabel ]: [\n\t\t\t27\n\t\t]\n\t},\n\t\"27\": {\n\t\t[ toneLabel ]: 18,\n\t\t[ positionLabel ]: {\n\t\t\t\"x\": 0.2868656765450043,\n\t\t\t\"z\": -0.8530580775189793,\n\t\t\t\"y\": 1.8642857142857143\n\t\t},\n\t\t[ meristemLabel ]: false,\n\t\t[ connectionsLabel ]: [\n\t\t\t26\n\t\t]\n\t}\n}"
  },
  {
    "path": "backend/src/store.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createStore, applyMiddleware, combineReducers } from 'redux';\nimport createSagaMiddleware from 'redux-saga';\n\nimport { logger }           from './logger';\nimport { messageSagas }     from './messages';\nimport { roomSagas }        from './rooms';\n\nimport {\n    roomDataReducer,\n    roomStateReducer\n} from './rooms';\n\nimport {\n    serverConstants,\n    serverReducer,\n    serverSagas\n} from './server';\n\nlet store = ( () => {\n\n    let sagaErrorTrap = ( error ) => {\n        logger.error( `sagaMiddleware trapped uncaught error, exiting - error was '${error.message}'` );\n        process.exit( serverConstants.ERROR_TYPES.BAD_OS_EXIT_ERROR_CODE );\n    };\n\n    let sagaMiddleware = createSagaMiddleware({\n        onError: sagaErrorTrap\n    });\n\n    let reducers = {\n        roomDataReducer,\n        roomStateReducer,\n        serverReducer\n    };\n\n    let rootReducer     = combineReducers( reducers );\n    let allMiddleware   = applyMiddleware( sagaMiddleware );\n    let store           = createStore( rootReducer, {}, allMiddleware );\n\n    // see https://github.com/yelouafi/redux-saga/blob/master/examples/real-world/store/configureStore.prod.js\n    store.runSaga       = ( saga ) => { sagaMiddleware.run( saga ); };\n\n    // when two sagas listen for the same action, they're run in this order\n    let sagaListsToRun = [\n        messageSagas,\n        serverSagas,\n        roomSagas\n    ];\n\n    sagaListsToRun.forEach(\n        ( sagaList ) => {\n            // run each named saga exported from this saga list\n            Object.keys( sagaList ).forEach(\n                ( sagaName ) => {\n                    store.runSaga( sagaList[ sagaName ] );\n                }\n            );\n        }\n    );\n\n    return store;\n})();\n\nconst getStoreState         = store.getState;\nconst dispatchStoreAction   = store.dispatch;\n\nexport  {\n    dispatchStoreAction,\n    getStoreState\n};\n"
  },
  {
    "path": "backend/src/utils/index.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport reducerUtils from    './reducer-utils';\nimport sagaUtils from       './saga-utils';\nimport stringUtils from     './string-utils';\nimport urlUtils from        './url-utils';\n\nexport {\n    reducerUtils,\n    sagaUtils,\n    stringUtils,\n    urlUtils\n};\n"
  },
  {
    "path": "backend/src/utils/reducer-utils.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport sizeof           from 'object-sizeof';\n\nimport { handleActions }    from 'redux-actions';\n\nconst initStateObject = ( roomNames, createFunction, objectLabel, logger ) => {\n\n    let obj = {};\n\n    let start = Date.now();\n\n    roomNames.forEach(\n        ( name ) => {\n            obj[ name ] = createFunction();\n        }\n    );\n\n    let finish = Date.now();\n\n    let millis  = finish - start;\n    let seconds = millis / 1000;\n\n    let sizeofBytesUsed     = sizeof( obj );\n    let sizeofKbytesUsed    = Math.round( sizeofBytesUsed / 1024 );\n    let sizeofMbytesUsed    = sizeofBytesUsed / 1024 / 1024;\n\n    logger.trace(\n        `initialised ${Object.keys( obj ).length} ${objectLabel} `\n        + `using ~${sizeofKbytesUsed}KiB `\n        + `(${sizeofMbytesUsed.toFixed( 2 )}MiB) `\n        + `in ${seconds} seconds`\n    );\n\n    return obj;\n\n};\n\n// redux-ignore higher order reducer\n// adapted from https://github.com/omnidan/redux-ignore\nconst filterActions = ( () => {\n\n    return function ( reducer ) {\n\n        var actions = arguments.length <= 1 || arguments[ 1 ] === undefined ? [] : arguments[ 1 ];\n\n        var isInList = ( action ) => {\n            return actions.indexOf( action.type ) >= 0;\n        };\n\n        var initialState = reducer( undefined, {} );\n\n        return function () {\n\n            var state = arguments.length <= 0 || arguments[ 0 ] === undefined ? initialState : arguments[ 0 ];\n            var action = arguments[ 1 ];\n\n            if ( !isInList( action ) ) {\n                return state;\n            }\n\n            return  reducer( state, action );\n        };\n    };\n})();\n\nconst createFilteredActionHandler = ( actionHandlers, initialState ) => {\n\n    let actionHandlerNames = Object.keys( actionHandlers );\n\n    const actionHandler = filterActions(\n        handleActions( actionHandlers, initialState ),\n        actionHandlerNames\n    );\n\n    return actionHandler;\n}\n\nexport {\n    initStateObject,\n    filterActions,\n    createFilteredActionHandler\n};\n"
  },
  {
    "path": "backend/src/utils/saga-utils.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { call, put, spawn } from 'redux-saga/effects';\n\nimport { logger }           from '../logger';\n\n/*\n * https://github.com/redux-saga/redux-saga/pull/644#issuecomment-272236599\n *\n * \"Use spawn to start sagas so that an uncaught exception doesn't terminate all of them.\n * Restart a saga on an asynchronous exception. Terminate it on a sync exception.\"\n */\nconst spawnAutoRestartingSagas = function* ( sagas ) {\n\n    yield sagas.map( ( saga  ) => {\n\n        let restarter = function* () {\n\n            let isSyncError         = false;\n            let previouslyStarted   = false;\n\n            while (!isSyncError) {\n\n                isSyncError = true;\n\n                try {\n                    setTimeout( () => { isSyncError = false; } );\n\n                    if( previouslyStarted ) {\n                        logger.info( `restarting crashed saga '${saga.name}'` );\n                    }\n\n                    previouslyStarted = true;\n                    yield call( saga );\n                    break;\n                }\n\n                catch ( error ) {\n\n                    if ( isSyncError ) {\n                        throw new Error( `saga '${saga.name}' was terminated because it threw an exception on startup.` );\n                    }\n\n                    logger.error( `uncaught error in saga '${saga.name}': ${error.message}` );\n                }\n            }\n        }\n\n        return spawn( restarter );\n    })\n};\n\nexport default {\n    spawnAutoRestartingSagas\n};\n"
  },
  {
    "path": "backend/src/utils/string-utils.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst formatStringAsGcpResourceName = ( str ) => {\n    let charPattern = /[^A-Za-z0-9-]/gi;\n    let dashPattern = /-{2,}/gi;\n    return str.replace(charPattern, \"-\").replace(dashPattern, \"\");\n};\n\nexport { formatStringAsGcpResourceName };\n"
  },
  {
    "path": "backend/src/utils/url-utils.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport messageConstants from '../messages/message-constants';\n\nconst headsetTypes = Object.values( messageConstants.HEADSET_TYPES );\n\nconst getClientInfoFromUrl = ( url ) => {\n\n    let info = url.split( '/' );\n    info.shift();\n\n    if( info.length === 1 && info[ 0 ] === '' ) {\n        throw new Error( `bad URL: ${url}` );\n    }\n\n    if( info.length > 2 ) {\n        throw new Error( `bad URL: ${url}` );\n    }\n\n    let clientHeadsetType = info[ 0 ];\n\n    if( headsetTypes.indexOf( clientHeadsetType ) < 0 ) {\n        throw new Error( `bad headset type: ${clientHeadsetType}` );\n    }\n\n    let roomName = info.length > 1 ? info[ 1 ] : undefined;\n\n    if( typeof roomName !== 'undefined' && roomName.length !== 4 ) {\n        throw new Error( `bad room name: ${roomName}` );\n    }\n\n    return {\n        clientHeadsetType,\n        roomName\n    };\n\n};\n\nexport default {\n    getClientInfoFromUrl\n};\n"
  },
  {
    "path": "backend/src/utils/websocket-utils.js",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport WebSocket            from 'uws';\n\nimport { serialize }        from '../s11n';\n\nimport messageConstants     from '../messages/message-constants';\n\nconst incomingMsgComponents = messageConstants.INCOMING_MESSAGE_COMPONENTS;\nconst outgoingMsgComponents = messageConstants.OUTGOING_MESSAGE_COMPONENTS;\n\nconst makeWsReplyMessage = ( from, type, data ) => {\n    return {\n        [ outgoingMsgComponents.ALL_MESSAGES.FROM ]: from,\n        [ outgoingMsgComponents.ALL_MESSAGES.MSG ]: {\n            [ incomingMsgComponents.ALL_MESSAGES.TYPE ]: type,\n            [ incomingMsgComponents.ALL_MESSAGES.DATA ]: data\n        }\n    };\n};\n\nconst makeWsBroadcastMessage = ( from, type, data ) => {\n    return {\n        [ outgoingMsgComponents.ALL_MESSAGES.TYPE ]: type,\n        [ outgoingMsgComponents.ALL_MESSAGES.FROM ]: from,\n        [ outgoingMsgComponents.ALL_MESSAGES.MSG ]: {\n            [ incomingMsgComponents.ALL_MESSAGES.TYPE ]: type,\n            [ incomingMsgComponents.ALL_MESSAGES.DATA ]: data\n        }\n    };\n};\n\nconst ackWsSendWithId = ( error, wsId ) => {\n    if( !error ) {\n        return;\n    }\n\n};\n\nconst sendWsMessageWithLogger = ( ws, msgObj, logger ) => {\n\n    if( ws.readyState !== WebSocket.OPEN ) {\n        return;\n    }\n\n    let msg = serialize( msgObj );\n\n    // wrap the ack function so we can log the WS id if it errors\n    let ackWsSend = ( ackError ) => {\n        try {\n            ackWsSendWithId( ackError, ws.id );\n        }\n        // handle this error because otherwise it's stuck in the callback\n        catch( error ) {\n            logger.trace( error.message, msg );\n        }\n    };\n\n    try {\n        ws.send( msg, ackWsSend );\n    }\n    catch( error ) {\n        logger.trace( `websocket error trying to send message to client id ${ws.id}: ${error.message}` );\n        throw( error );\n    }\n};\n\nconst sendWsErrorWithLogger = ( serverId, ws, errType, errMsg ) => {\n    let msgObj = {\n        from: serverId,\n        msg: {\n            type: 'error',\n            data: {\n                errType,\n                detail: errMsg\n            }\n        }\n    };\n    sendWsMessageWithLogger( ws, msgObj, logger );\n};\n\nexport {\n    makeWsReplyMessage,\n    makeWsBroadcastMessage,\n    sendWsMessageWithLogger,\n    sendWsErrorWithLogger\n};\n"
  },
  {
    "path": "config.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Note, this file is only used on local development.\n// Running it under appengine, the config.template is used instead\n\nvar CONFIG = {};\nCONFIG.DEFAULT_REGION= \"us\";\nCONFIG.SERVERS = {\"us\": \"<your-backend-server-hostname>\"};\n"
  },
  {
    "path": "config.template",
    "content": "var CONFIG = {}\nCONFIG.DEFAULT_REGION = \"{{default_region}}\";\n\nCONFIG.SERVERS = {\n{% for server in servers %}\n    \"{{server.name}}\":\"{{server.hostname}}\",\n{% endfor %}\n};\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>The Musical Forest</title>\n    <meta name=\"description\" content=\"Musical Forest\">\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no\">\n    <meta name=\"apple-touch-fullscreen\" content=\"yes\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n    <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\n\n    <!-- Facebook Share -->\n    <meta property=\"og:url\" content=\"https://forest.webvrexperiments.com\" />\n    <meta property=\"og:type\" content=\"website\" />\n    <meta property=\"og:title\" content=\"The Musical Forest\" />\n    <meta property=\"og:description\" content=\"Join users from around the world in a musical forest. A WebVR Experiment.\" />\n    <meta property=\"og:image\" content=\"https://forest.webvrexperiments.com/static/img/fbshare.jpg\" />\n\n    <!-- Twitter Share -->\n    <meta name=\"twitter:card\" content=\"summary_large_image\">\n    <meta name=\"twitter:title\" content=\"The Musical Forest\">\n    <meta name=\"twitter:description\" content=\"Join users from around the world in a musical forest. A WebVR Experiment.\">\n    <meta name=\"twitter:image\" content=\"https://forest.webvrexperiments.com/static/img/twittershare.jpg\">\n\n    <!-- Origin Trial Token, feature = WebVR, origin = https://webvrexperiments.com, expires = 2017-05-03 -->\n    <meta http-equiv=\"origin-trial\" data-feature=\"WebVR\" data-expires=\"2017-05-03\" content=\"Allgoq39kmaNiIkhQWskjqassvbzrw3qS4XHzr+Q9UajnZMkgtHH1kWQK5JV5EgBDYynHxNdtmpF6pbE8cu4HwAAAABmeyJvcmlnaW4iOiJodHRwczovL3dlYnZyZXhwZXJpbWVudHMuY29tOjQ0MyIsImZlYXR1cmUiOiJXZWJWUiIsImV4cGlyeSI6MTQ5MzgxNzg5OCwiaXNTdWJkb21haW4iOnRydWV9\">\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"./build/style.css\">\n    <link rel=\"icon\" type=\"image/png\" sizes=\"33x33\" href=\"static/img/favicon.png\">\n    <script src=\"config.js\"></script>\n</head>\n<body>\n\n<!-- SPLASH SCREEN -->\n<div id=\"splash\">\n    <div id=\"headphones\" class=\"headphones\">\n        <img src=\"static/img/Headphone.png\" alt=\"headphone icon\" class=\"icon\">\n        <span class=\"text\">Headphones Recommended</span>\n    </div>\n    <div id=\"enter-container\">\n        <div id=\"loader\">LOADING<img src=\"static/img/loader.gif\"></div>\n        <div id=\"enterButton\">\n            <div id=\"enter-360\">No VR? Take a peek in 360 mode</div>\n        </div>\n    </div>\n    <div id=\"badges\">\n        <div class=\"webvr-logo\">\n            <a href=\"https://webvrexperiments.com\" target=\"_blank\"><img src=\"static/img/webVR_experiment@3x.png\" alt=\"WebVR Logo\"></a>\n        </div>\n        <div class=\"pipe\"></div>\n        <div class=\"friends-with\">\n            <img src=\"static/img/friends_with_g@3x.png\" alt=\"Friends With Google\">\n        </div>\n    </div>\n    <div id=\"about-button\" class=\"about-button visible\">\n        ?\n    </div>\n    <div id=\"about\">\n        <div class=\"content\">\n            <h1 class=\"about-head unselectable\">The Musical Forest</h1>\n            <div class=\"description\">\n                <p>\n                    Join users from around the world in a musical forest. Tap or click a shape to play it. If you're using a headset like the HTC VIVE, you can add shapes too. This experiment demonstrates cross-device co-presence in VR, allowing anyone to join in, no matter what device they're using.\n                </p>\n                <p>\n                    Technologies used: AFrame, Three.js, Tone.js, Node.js, PubSub\n                </p>\n                <p>\n                    Made by Google Creative Lab. Check out other WebVR Experiments <a href=\"https://webvrexperiments.com/\" target=\"_blank\">here</a> and check out the open-source code on <a href=\"https://github.com/googlecreativelab/webvr-musicalforest\" target=\"_blank\">GitHub</a>.\n                </p>\n            </div>\n            <img src=\"static/img/aboutbg.gif\" alt=\"about\">\n            <div class=\"close xbutton\" id=\"about-xbutton\"></div>\n        </div>\n    </div>\n    <div id=\"legal\">\n        <a href=\"https://www.google.com/intl/en/policies/privacy/\" target=\"_blank\">Privacy</a> &amp; <a href=\"https://www.google.com/intl/en/policies/terms/\" target=\"_blank\">Terms</a>\n    </div>\n</div>\n\n<!-- MAIN -->\n<script src=\"/build/main.js\"></script>\n\n<div id=\"spacer\"></div>\n\n</body>\n</html>\n"
  },
  {
    "path": "js/ascene.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nlet aframeContent = `\n<a-scene\n    webvr-ui\n    splash\n    copresence-server\n    controllers\n    tree\n    fake-light\n    fog=\"type: exponential; color: #e9a69a; density: 0.065;\">\n<a-assets>\n    <img id=\"ball_circles_134\" src=\"/static/img/ball_circles_134.png\">\n    <img id=\"ball_circles_567\" src=\"/static/img/ball_circles_567.png\">\n    <img id=\"ball_triangles_134\" src=\"/static/img/ball_triangles_134.png\">\n    <img id=\"ball_triangles_567\" src=\"/static/img/ball_triangles_567.png\">\n    <img id=\"ball_squares_134\" src=\"/static/img/ball_squares_134.png\">\n    <img id=\"ball_squares_567\" src=\"/static/img/ball_squares_567.png\">\n    <img id=\"bg-tree-tex\" src=\"/static/img/mobile-trees.png\">\n\n    <a-asset-item id=\"target-obj\" src=\"/static/models/target.obj\"></a-asset-item>\n    <a-asset-item id=\"head-obj\" src=\"/static/models/headset.dae\"></a-asset-item>\n    <a-asset-item id=\"controller-obj\" src=\"/static/models/controller.dae\"></a-asset-item>\n    <a-asset-item id=\"bg-tree-1-obj\" src=\"/static/models/bg-tree-1.obj\"></a-asset-item>\n    <a-asset-item id=\"bg-tree-2-obj\" src=\"/static/models/bg-tree-2.obj\"></a-asset-item>\n    <a-asset-item id=\"bg-tree-3-obj\" src=\"/static/models/bg-tree-3.obj\"></a-asset-item>\n    <a-asset-item id=\"bg-tree-ring-obj\" src=\"/static/models/bg-tree-ring.obj\"></a-asset-item>\n\n    <a-mixin\n        id=\"avatar-head\"\n        collada-model=\"#head-obj\"\n        headset-material></a-mixin>\n    <a-mixin\n        id=\"avatar-hand\"\n        collada-model=\"#controller-obj\"></a-mixin>\n    <a-mixin\n        id=\"obj-ball\"\n        geometry=\"primitive: sphere; radius:0.01; segments-width:2; segments-height:2;\"\n        material=\"\"></a-mixin>\n    <a-mixin\n        id=\"obj-hit\"\n        geometry=\"primitive: cylinder; radius: 0.35; height: 0.001;\"\n        material=\"color: #cf4e51;\"></a-mixin>\n</a-assets>\n\n<a-entity\n    id=\"player\"\n    position=\"1 2 10\"\n    rotation=\"-25 25 0\">\n    <a-entity\n        id=\"avatar\"\n        proximity-check\n        raycaster=\"objects: .selectable; near:0.01; far:10; recursive: true; interval:100;\"\n        camera=\"userHeight: 1.6; near:0.01;\"\n        mixin=\"avatar-head\"\n        gaze\n        touch-color\n        position=\"0 0 0\"\n        rotation=\"0 0 0\"\n        listener\n        copresence=\"components: mixin, position, rotation; decimals:3; playerpart:head\"></a-entity>\n</a-entity>\n\n<a-entity\n    id=\"gaze-floor\"\n    class=\"selectable\"\n    geometry=\"primitive: circle; radius: 4;\"\n    rotation=\"-90 0 0\"\n    position=\"0 -0.0005 0\"\n    material=\"shader: flat; color: #e9a69a;\"></a-entity>\n\n<a-entity\n    id=\"gaze-hit\"\n    position=\"0.0 0.0 0\"\n    obj-model=\"obj: #target-obj;\"\n    material=\"shader:flat; color: #fff;\"\n    visible=\"false\">\n    </a-entity>\n\n<a-entity\n    id=\"floorStatic\"\n    geometry=\"primitive: plane; width:80; height:80\"\n    rotation=\"-90 0 0\"\n    position=\"0 -0.01 0\"\n    material=\"shader: flat; color: #e7a094;\"></a-entity>\n\n<a-entity\n    id=\"mobile-tree-ring-1\"\n    bg-tree-ring-material=\"0\"\n    position=\"0, 0, 0\"\n    scale=\"0.2, 1, 0.2\"></a-entity>\n\n<a-entity\n    id=\"mobile-tree-ring-2\"\n    bg-tree-ring-material=\"1\"\n    position=\"0, 0, 0\"\n    scale=\"0.3, 1, 0.3\"\n    rotation=\"0, 250, 0\"></a-entity>\n\n<a-entity\n    id=\"mobile-tree-ring-3\"\n    bg-tree-ring-material=\"2\"\n    position=\"0, 0, 0\"\n    scale=\"0.4, 1, 0.4\"\n    rotation=\"0, 53, 0\"></a-entity>\n\n<a-entity\n    id=\"background-objects\"\n    background-objects></a-entity>\n\n<a-sky id=\"skyStatic\" color=\"#e7a094\"></a-sky>\n\n\n</a-scene>`;\n\nlet div = document.createElement(\"div\");\ndiv.innerHTML = aframeContent;\ndocument.body.appendChild(div);\n"
  },
  {
    "path": "js/components/background-objects.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { BgTreeColors, ShadowColor } from '../core/colors';\n\nconst DEG2RAD = Math.PI / 180;\nconst HALFPI = Math.PI / 2;\n\nconst VECTOR_ZERO     = new THREE.Vector3( 0, 0, 0 );\nconst SHADOW_X_AXIS   = new THREE.Vector3( 1, 0, 0 );\nconst SHADOW_Z_AXIS   = new THREE.Vector3( 0, 1, 0 );\nconst SHADOW_LENGTH   = 20;\nconst SHADOW_GEOMETRY = new THREE.PlaneGeometry( 0.25, 1 );\nconst SHADOW_MATERIAL = new THREE.MeshBasicMaterial({\n    color: ShadowColor,\n    side: THREE.DoubleSide\n});\n\nAFRAME.registerComponent( 'background-objects', {\n\n    nLoaded: 0,\n\n    init: function() {\n        this.treeObjs = [ 'bg-tree-1', 'bg-tree-2', 'bg-tree-3' ];\n        this.treeGeometry = [];\n\n        let objLoader = new THREE.OBJLoader();\n\n        if ( !AFRAME.utils.device.isMobile() ) {\n            this.treeObjs.forEach( function( obj ) {\n                objLoader.load( '/static/models/' + obj + '.obj', this.checkLoadComplete.bind( this ) );\n            }, this );\n        }\n    },\n\n    checkLoadComplete: function( objModel ) {\n        this.treeGeometry.push( objModel.children[ 0 ].geometry );\n\n        if ( ++this.nLoaded >= this.treeObjs.length ) {\n            this.generateTrees();\n        }\n    },\n\n    generateTrees: function() {\n        let total = AFRAME.utils.device.isMobile() ? 20 : 200;\n        let theta, x, z, mesh, shadow;\n        let treeDistance, treeColor, treeIndex, randomSeed;\n        let perimeter = 8;\n\n        let materials = [\n            new THREE.MeshBasicMaterial( { color: new THREE.Color( BgTreeColors[ 0 ] ), side: THREE.DoubleSide } ),\n            new THREE.MeshBasicMaterial( { color: new THREE.Color( BgTreeColors[ 1 ] ), side: THREE.DoubleSide } ),\n            new THREE.MeshBasicMaterial( { color: new THREE.Color( BgTreeColors[ 2 ] ), side: THREE.DoubleSide } )\n        ];\n\n        for ( let i = 0; i < total; i++ ) {\n\n            theta = DEG2RAD * (i / total) * 360;\n            randomSeed = Math.random();\n            treeDistance = randomSeed * 18 + perimeter;\n            treeColor = Math.floor( randomSeed * 3 );\n            treeIndex = Math.floor( i % 3 );\n            x = Math.cos( theta ) * treeDistance;\n            z = Math.sin( theta ) * treeDistance;\n\n            // Create and position tree\n            mesh = new THREE.Mesh( this.treeGeometry[ treeIndex ], materials[ treeColor ] );\n            mesh.position.set( x, 0, z );\n            mesh.scale.set( 1, 3 + Math.random(), 1 );\n            mesh.scale.multiplyScalar( 0.0002 );\n            mesh.lookAt( VECTOR_ZERO );\n            mesh.rotation.y += HALFPI;\n            mesh.rotation.y += ( Math.random() - 0.5 ) * 0.05;\n            mesh.position.setY( Math.random() * -2 );\n\n            // Create and position tree shadow\n            shadow = new THREE.Mesh( SHADOW_GEOMETRY, SHADOW_MATERIAL );\n            shadow.position.set( x, 0, z );\n            shadow.lookAt( VECTOR_ZERO );\n            shadow.rotation.order = 'ZYX';\n            shadow.rotation.x += HALFPI;\n            shadow.scale.setY( SHADOW_LENGTH );\n            shadow.translateOnAxis( SHADOW_Z_AXIS, -SHADOW_LENGTH / 2 );\n\n            this.el.object3D.add( mesh );\n            this.el.object3D.add( shadow );\n        }\n    }\n});\n"
  },
  {
    "path": "js/components/ball.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { Note } from '../notes/note';\n\nAFRAME.registerComponent( 'ball', {\n\n    grow: null,\n    shrink: null,\n    has3dContext: false,\n\n    schema: {\n        radius: { default: 0.1 },\n        grabbed: { type: 'boolean', default: false },\n        hand: { default: 'rightHand' },\n\n        // the node that connects to the trunk where other balls grow from\n        meristem: {\n            default: false,\n            type: 'boolean'\n        },\n        meristemNode: {\n            default: null\n        },\n    },\n\n    init: function() {\n\n        this.el.addEventListener( 'componentchanged', event => {\n            this.update();\n            if ( event.detail.name === 'tone' ) {\n                this.updateTone();\n            }\n        }, false);\n\n        // the hit animiation\n        this.el.addEventListener( 'hit', event => this.hit( event ) );\n        this.el.addEventListener( 'highlight', event => this.highlight( event ) );\n        this.el.addEventListener( 'unHighlight', event => this.unHighlight( event ) );\n    },\n\n    play: function() {\n\n        this.createShape();\n        this.updateTone();\n\n        document.dispatchEvent( new CustomEvent(\"BALL_CREATED\", {\n            detail: {\n                id: this.el.id,\n                el: this.el,\n                grab: true,\n                hand: this.data.hand\n            }\n        }));\n\n        // takes a refresh to register the 3d context\n        setTimeout( () => {\n            this.has3dContext = true;\n        }, 0 );\n    },\n\n    createShape: function() {\n        this.note = new Note( this.el.sceneEl.components.palette );\n        this.note.lightPosition = this.el.sceneEl.components[\"fake-light\"].getLightPosition();\n        this.note.setTone( this.el.getAttribute( 'tone' ) );\n        /*\n         * can't replace root mesh of node\n         * due to tight coupling of gaze.js & raycaster\n         * added note as child instead.\n         */\n        // this.el.setObject3D(\"mesh\", this.note.group)\n        this.el.object3D.add(this.note.group);\n    },\n\n    tick: function( t, dt ) {\n        this.note.tick();\n    },\n\n    updateTone: function() {\n        this.note.setTone( this.el.getAttribute( 'tone' ) );\n        // this.animateTexture(0.5, 0, null);\n    },\n\n    hit: function( event ) {\n        this.note.hit( event );\n    },\n    highlight: function( event ) {\n        this.note.highlight( event );\n    },\n    unHighlight: function( event ) {\n        this.note.unHighlight( event );\n    }\n});\n"
  },
  {
    "path": "js/components/bg-tree-ring-material.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { EnvColors, BgTreeColors } from '../core/colors';\n\nAFRAME.registerComponent( 'bg-tree-ring-material', {\n\n\tschema: {\n        type: 'int',\n        default: 0\n    },\n\n\tinit: function() {\n\t\tlet objLoader = new THREE.OBJLoader();\n\n        if ( AFRAME.utils.device.isMobile() ) {\n            objLoader.load( '/static/models/bg-tree-ring.obj', this.onLoaded.bind( this ) );\n        }\n\t},\n\n\tonLoaded: function( object ) {\n\t\tlet mesh = object.children[ 0 ];\n\t\tlet tex = new THREE.Texture( document.getElementById( 'bg-tree-tex' ) );\n\t\ttex.needsUpdate = true;\n\t\ttex.minFilter = tex.magFilter = THREE.NearestFilter;\n\n\t\tlet shader = THREE.BGTreeShader;\n\t\tlet uniforms = THREE.UniformsUtils.clone( shader.uniforms );\n\t\tuniforms.map.value = tex;\n\t\tuniforms.color.value = new THREE.Color( BgTreeColors[ this.data ] );\n\t\tuniforms.fogColor.value = new THREE.Color ( EnvColors.fog );\n\t\tuniforms.fogDensity.value = 0.065;\n\n\t\tmesh.material = new THREE.ShaderMaterial( {\n\t\t\tuniforms: uniforms,\n\t\t\tvertexShader: shader.vertexShader,\n\t\t\tfragmentShader: shader.fragmentShader,\n\t\t\tside: THREE.DoubleSide\n\t\t} );\n\n\t\tthis.el.setObject3D( 'mesh', mesh );\n\t}\n});\n"
  },
  {
    "path": "js/components/clicker.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\n\nAFRAME.registerComponent( 'clicker', {\n    raycaster: null,\n    mouse: null,\n    canvas: null,\n    camera: null,\n    intersected: null,\n    teleporter:null,\n    schema: {\n        // radius: { default: 0.1 },\n    },\n\n    init: function() {\n        document.addEventListener(\"INTRO_COMPLETED\", () => {\n            // creates references\n            this.raycaster = new THREE.Raycaster();\n    \t\tthis.mouse = new THREE.Vector2();\n            this.canvas = this.el.sceneEl.canvas;\n            this.camera = this.getCameraFromEntity(document.querySelector(\"a-entity[camera]\"));\n            this.teleporter = document.querySelector(\"#gaze-floor\").components.teleport;\n\n            // on click\n            // get all balls\n            // filter out the ones out of range\n            // do a check for mouse position\n            // unproject ray to mouse with vector z = -1\n            // get ray intersects\n            // if intersect length > 1 & if intersect is same as mousedown, hit\n\n            document.addEventListener(\"touchstart\", (event) => {});\n            document.addEventListener(\"touchend\", (event) => {});\n\n            document.addEventListener(\"mousedown\", (event) => {\n                let entity = this.getIntersectObject({x:event.clientX, y:event.clientY});\n                if( !entity ) { return; }\n                this.intersected = entity;\n            });\n\n            document.addEventListener(\"mouseup\", (event) => {\n                let entity = this.getIntersectObject({x:event.clientX, y:event.clientY});\n                if( !entity ) { return; }\n                if(this.intersected === entity){\n                    entity.emit(\"controllerhit\", {velocity:0.5});\n                }\n                this.intersected = null;\n                event.wasSphereHit = true;\n\n            });\n        });\n\n    },\n\n\n\n    play: function() {\n\n    },\n\n    pause: function() {\n\n    },\n\n    getIntersectObject: function(intersect) {\n        this.mouse.x = ( intersect.x / document.body.clientWidth ) * 2 - 1;\n        this.mouse.y = - ( intersect.y / document.body.clientHeight ) * 2 + 1;\n        this.raycaster.setFromCamera( this.mouse, this.camera );\n\n        let selectables = tree.getElementsByClassName(\"selectable\");\n        let balls = [];\n        for(let i=0; i<selectables.length; i++){\n            balls.push(selectables[i].components.ball.note.head.mesh);\n        }\n        let intersects = this.raycaster.intersectObjects( balls, false);\n        if ( intersects.length > 0 ) {\n            return intersects[0].object.parent.parent.el;\n        } else {\n            return null;\n        }\n    },\n\n    getCameraFromEntity: function(camera) {\n        for(let i=0; i<camera.object3D.children.length; i++){\n            if(camera.object3D.children[i].type === \"PerspectiveCamera\") {\n                return camera.object3D.children[i];\n            }\n        }\n    }\n});\n"
  },
  {
    "path": "js/components/controller-material.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { HeadsetColor } from '../core/colors';\n\nAFRAME.registerComponent( 'controller-material', {\n\tisLoaded: false,\n\n\tschema: {\n\t\t// bandColor: { type: 'string', default: HeadsetColor },\n\t\tbandColor: { type: 'number', default: HeadsetColor },\n\t\topacity: {\n\t\t\tdefault: 1.0\n\t\t},\n\t},\n\n\tinit: function() {\n\t\tthis.el.addEventListener( 'model-loaded', this.onLoaded.bind( this ) );\n\t\tthis.el.addEventListener(\"componentchanged\", (event) => {\n\t\t\tthis.onUpdated();\n\t\t}, false);\n\t\tdocument.addEventListener(\"SPHERE_TEXTURE_LOADED\", (event) => {\n            this.update();\n        });\n\t},\n\n\tonLoaded: function() {\n\t\tlet mesh = this.el.components[\"collada-model\"].model.children[ 0 ].children[ 0 ];\n\t\tmesh.material = new THREE.MultiMaterial([\n\t\t\tnew THREE.MeshBasicMaterial( { color: HeadsetColor } ),\n\t\t\tnew THREE.MeshBasicMaterial( { color: this.data.bandColor } )\n\t\t]);\n\t\tthis.isLoaded = true;\n\t\tthis.onUpdated();\n\t},\n\n\tupdate: function( oldData ) {\n        if ( !this.isLoaded) { return; }\n\t\tlet mesh = this.el.components[\"collada-model\"].model.children[ 0 ].children[ 0 ];\n\t\tmesh.material = new THREE.MultiMaterial([\n\t\t\tnew THREE.MeshBasicMaterial( { color: HeadsetColor } ),\n\t\t\tnew THREE.MeshBasicMaterial( { color: this.data.bandColor } )\n\t\t]);\n\t},\n\tgetColor: function() {\n        return new THREE.Color( this.el.sceneEl.components.palette.controllerColors()[0] );\n    },\n\n\tonUpdated: function() {\n\t\tif(!this.isLoaded) { return; }\n\t\tlet mesh = this.el.components[\"collada-model\"].model.children[ 0 ].children[ 0 ];\n\t\tlet transparent = (this.data.opacity<1) ? true : false;\n\n\t\tmesh.material.materials[0].transparent = transparent;\n\t\tmesh.material.materials[1].transparent = transparent;\n\n\t\tmesh.material.materials[0].opacity = this.data.opacity;\n\t\tmesh.material.materials[1].opacity = this.data.opacity;\n\n\t},\n});\n"
  },
  {
    "path": "js/components/controllers.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nAFRAME.registerSystem('controllers', {\n    controllerExists:false,\n    schema: {\n\n    },\n\n    init: function() {\n\n        document.addEventListener(\"INTRO_COMPLETED\", (event) => {\n            let timeout;\n            let loopCount = 0;\n            let loopFunction = () => {\n                if (loopCount > 240 ) {\n\n                } else if(this.controllerExists){\n\n                } else {\n                    timeout = setTimeout(() => {\n                        loopCount++;\n                        this.testForController();\n                        loopFunction();\n                    }, 1000);\n                }\n            };\n            loopFunction();\n        }, false);\n\n    },\n    getGamePad: function() {\n        let vrGamepad = null;\n        if (!navigator.getGamepads) {\n            return vrGamepad;\n        }\n        let gamepads = navigator.getGamepads();\n        for (let i = 0; i < gamepads.length; ++i) {\n            let gamepad = gamepads[i];\n            if (gamepad) {\n                return gamepad;\n            }\n        }\n        return null;\n    },\n    testForController: function() {\n        let vrGamepad = this.getGamePad();\n        if (!vrGamepad) {\n            return;\n        }\n\n        let player;\n        let controller;\n        let rightHand;\n        let leftHand;\n\n        switch(vrGamepad.id){\n            case \"Daydream Controller\":\n                player = document.querySelector('#player');\n                controller = this.createDayDreamCoprescenceController(\"rightHand\", \"right\");\n                player.appendChild(controller);\n                this.createDayDreamManager();\n                break;\n            case \"OpenVR Gamepad\":\n                player = document.querySelector('#player');\n                rightHand = this.createViveController(\"rightHand\", \"right\");\n                player.appendChild(rightHand);\n                leftHand = this.createViveController(\"leftHand\", \"left\");\n                player.appendChild(leftHand);\n                break;\n            case \"Oculus Touch (Left)\":\n            case \"Oculus Touch (Right)\":\n                player = document.querySelector('#player');\n                rightHand = this.createOcculusController(\"rightHand\", \"right\");\n                player.appendChild(rightHand);\n                leftHand = this.createOcculusController(\"leftHand\", \"left\");\n                player.appendChild(leftHand);\n                break;\n            default:\n                player = document.querySelector('#player');\n                rightHand = this.createOcculusController(\"rightHand\", \"right\");\n                player.appendChild(rightHand);\n                leftHand = this.createOcculusController(\"leftHand\", \"left\");\n                player.appendChild(leftHand);\n                break;\n        }\n\n        let triggerEvent = new CustomEvent(\"CONTROLLER_CREATED\", {\n            \"detail\": {\n                \"id\": vrGamepad.id\n            }\n        });\n        document.dispatchEvent(triggerEvent);\n\n        this.controllerExists = true;\n    },\n    createViveController: function(id, hand) {\n        let controller = document.createElement('a-entity');\n        controller.id = id;\n        if(this.sceneEl.components.palette && this.sceneEl.components.palette.controllerColors()) {\n            let color = this.sceneEl.components.palette.controllerColors()[1];\n            controller.setAttribute(\"vive-controls\", \"hand:\" + hand + \"; model:false\");\n            this.load6DofControllerParams(controller, hand, color);\n        } else {\n            document.addEventListener(\"SPHERE_TEXTURE_LOADED\", (event) => {\n                let color = this.sceneEl.components.palette.controllerColors()[1];\n                controller.setAttribute(\"vive-controls\", \"hand:\" + hand + \"; model:false\");\n                this.load6DofControllerParams(controller, hand, color);\n            });\n        }\n        return controller;\n    },\n\n    load6DofControllerParams: (entity, hand, color) => {\n        entity.setAttribute(\"mixin\", \"avatar-hand\");\n        entity.setAttribute(\"grab-move\", \"\");\n        entity.setAttribute(\"tool-tips\", \"hand:\"+hand+\";\");\n        entity.setAttribute(\"haptics\", \"hand:\"+ hand );\n        entity.setAttribute(\"touch-color\", \"\");\n        entity.setAttribute(\"controller-material\", \"bandColor:\"+color+\";\");\n        entity.setAttribute(\"copresence\", \"components: mixin, position, rotation; decimals:3; playerpart:\"+hand);\n    },\n\n    createOcculusController: function(id, hand) {\n        let controller = document.createElement('a-entity');\n        controller.id = id;\n        if(this.sceneEl.components.palette && this.sceneEl.components.palette.controllerColors()) {\n            let color = this.sceneEl.components.palette.controllerColors()[1];\n            controller.setAttribute(\"oculus-touch-controls\", \"hand:\" + hand + \"; model:false\");\n            this.load6DofControllerParams(controller, hand, color);\n        } else {\n            document.addEventListener(\"SPHERE_TEXTURE_LOADED\", (event) => {\n                let color = this.sceneEl.components.palette.controllerColors()[1];\n                controller.setAttribute(\"oculus-touch-controls\", \"hand:\" + hand + \"; model:false\");\n                this.load6DofControllerParams(controller, hand, color);\n            });\n        }\n        return controller;\n    },\n    /*\n     * Proxy controller. used for copresence\n     */\n    createDayDreamCoprescenceController: function(id, hand) {\n        let controller = document.createElement('a-entity');\n        controller.id = id;\n        controller.setAttribute(\"position\", \"0 0 0\");\n        controller.setAttribute(\"copresence\", \"components: mixin, position, rotation; decimals:3; playerpart:\"+hand);\n        return controller;\n    },\n\n    createDayDreamManager: function() {\n        let player = document.querySelector('#player');\n        let avatar = document.querySelector('#avatar');\n        let avatarPosition = getComponentProperty(avatar, \"position\");\n        let manager = document.createElement('a-entity');\n        manager.id = \"daydream\";\n        manager.setAttribute(\"daydream-manager\", \"headset: #avatar; proxy: #rightHand; controller: #controller;\");\n        manager.setAttribute(\"position\", avatarPosition.x + \" 0.0 \" + avatarPosition.z);\n        this.sceneEl.appendChild(manager);\n\n        let controller = document.createElement('a-entity');\n        controller.id = \"controller\";\n        controller.setAttribute(\"daydream-controller\", \"\");\n        controller.setAttribute(\"position\", \"0 0.1 0\");\n        controller.setAttribute(\"daydream-pointer\", \"\");\n        controller.setAttribute(\"mixin\", \"avatar-hand\");\n\n        if(this.sceneEl.components.palette && this.sceneEl.components.palette.controllerColors()) {\n            let color = this.sceneEl.components.palette.controllerColors()[1];\n            controller.setAttribute(\"controller-material\", \"bandColor:\"+color+\";\");\n        } else {\n            document.addEventListener(\"SPHERE_TEXTURE_LOADED\", (event) => {\n                let color = this.sceneEl.components.palette.controllerColors()[1];\n                controller.setAttribute(\"controller-material\", \"bandColor:\"+color+\";\");\n            });\n        }\n\n\n        manager.appendChild(controller);\n    },\n\n    doesControllerExists: function() {\n        return this.controllerExists;\n    }\n});\n"
  },
  {
    "path": "js/components/copresence-server-messages.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport messageConstants from '../../backend/src/messages/message-constants';\n\nconst clientMsgTypes = messageConstants.INCOMING_MESSAGE_TYPES;\nconst clientMsgComponents = messageConstants.INCOMING_MESSAGE_COMPONENTS;\nconst serverMsgComponents = messageConstants.OUTGOING_MESSAGE_COMPONENTS;\n\n\nclass Message {\n  constructor(type){\n    this.data = {};\n    this.type = type;\n  }\n\n  serialize(){\n    let msg = JSON.stringify({\n      [ clientMsgComponents.ALL_MESSAGES.TYPE ]: this.type,\n      [ clientMsgComponents.ALL_MESSAGES.DATA ]: this.data\n    });\n    return msg;\n  }\n}\n\n\nexport class ExitRoom extends  Message {\n  constructor(){\n    super( clientMsgTypes.EXIT_ROOM );\n  }\n}\n\n\nexport class RoomClientPositionUpdate extends Message {\n  constructor(playerData, spheresData){\n    super( clientMsgTypes.UPDATE_CLIENT_COORDS );\n\n    const clientParts = clientMsgComponents.UPDATE_CLIENT_COORDS;\n    const coordParts = clientMsgComponents.REF_COORDINATE_SET;\n\n    this.data[clientParts.HEAD] = {};\n    this.data[clientParts.LEFT] = {};\n    this.data[clientParts.RIGHT] = {};\n\n    this.data[clientParts.HEAD][coordParts.POSITION] = playerData['head']['position'];\n    this.data[clientParts.HEAD][coordParts.ROTATION] = playerData['head']['rotation'];\n    this.data[clientParts.LEFT][coordParts.POSITION] = playerData['left']['position'];\n    this.data[clientParts.LEFT][coordParts.ROTATION] = playerData['left']['rotation'];\n    this.data[clientParts.RIGHT][coordParts.POSITION] = playerData['right']['position'];\n    this.data[clientParts.RIGHT][coordParts.ROTATION] = playerData['right']['rotation'];\n\n    this.data[clientParts.SPHERES] = []\n    for(let sphere of spheresData){\n      let s = {};\n      s[clientMsgComponents.UPDATE_CLIENT_COORDS.SPHERE_ID] = sphere.id;\n      s[clientMsgComponents.UPDATE_CLIENT_COORDS.SPHERE_POSITION] = sphere.position;\n      this.data[clientParts.SPHERES].push(s)\n    }\n  }\n}\n\n\nexport class SpherePositionUpdate extends Message {\n  constructor(uuid, position){\n    super( clientMsgTypes.UPDATE_SPHERE_POSITION );\n    this.data[ clientMsgComponents.UPDATE_SPHERE_POSITION.SPHERE_ID ] = uuid;\n    this.data[ clientMsgComponents.UPDATE_SPHERE_POSITION.POSITION ] = position;\n  }\n}\n\nexport class SphereToneUpdate extends Message {\n  constructor(uuid, tone){\n    super( clientMsgTypes.SET_SPHERE_TONE );\n    this.data[ clientMsgComponents.SET_SPHERE_TONE.SPHERE_ID ] = uuid;\n    this.data[ clientMsgComponents.SET_SPHERE_TONE.TONE ] = tone;\n  }\n}\n\nexport class SphereConnectionUpdate extends Message {\n  constructor(uuid, connections){\n    super( clientMsgTypes.SET_SPHERE_CONNECTIONS );\n    this.data[ clientMsgComponents.SET_SPHERE_CONNECTIONS.SPHERE_ID ] = uuid;\n    this.data[ clientMsgComponents.SET_SPHERE_CONNECTIONS.CONNECTIONS ] =  connections\n  }\n}\n\nexport class GrabSphere extends Message {\n  constructor(uuid){\n    super( clientMsgTypes.GRAB_SPHERE );\n    this.data[ clientMsgComponents.GRAB_SPHERE.SPHERE_ID ] = uuid;\n  }\n}\n\nexport class ReleaseSphere extends Message {\n  constructor(uuid){\n    super( clientMsgTypes.RELEASE_SPHERE );\n    this.data[ clientMsgComponents.RELEASE_SPHERE.SPHERE_ID ] = uuid;\n  }\n}\n\nexport class StrikeSphere extends Message {\n  constructor(uuid, velocity=1){\n    super( clientMsgTypes.STRIKE_SPHERE );\n    this.data[ clientMsgComponents.STRIKE_SPHERE.SPHERE_ID ] = uuid;\n    this.data[ clientMsgComponents.STRIKE_SPHERE.VELOCITY ] = velocity;\n  }\n}\n\nexport class DeleteSphere extends Message {\n  constructor(uuid){\n    super( clientMsgTypes.DELETE_SPHERE );\n    this.data[ clientMsgComponents.DELETE_SPHERE.SPHERE_ID ] = uuid;\n  }\n}\n\n\nexport class CreateSphereAtPosition extends Message {\n  constructor(position, tone=1){\n    super( clientMsgTypes.CREATE_SPHERE_OF_TONE_AT_POSITION );\n    this.data[ clientMsgComponents.CREATE_SPHERE_OF_TONE_AT_POSITION.POSITION ] = position;\n    this.data[ clientMsgComponents.CREATE_SPHERE_OF_TONE_AT_POSITION.TONE ] = tone;\n  }\n}\n"
  },
  {
    "path": "js/components/copresence-server.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport serverMessageConstants from '../../backend/src/messages/message-constants';\n\nimport {  ExitRoom, RoomClientPositionUpdate, CreateSphereAtPosition, DeleteSphere, SpherePositionUpdate, SphereToneUpdate, SphereConnectionUpdate, GrabSphere, ReleaseSphere, StrikeSphere } from './copresence-server-messages';\nimport _ from 'underscore';\nimport { getParameterByName, getViewerType, showErrorMessage } from '../util';\n\nconst msgTypes = serverMessageConstants.OUTGOING_MESSAGE_TYPES;\nconst errorTypes = serverMessageConstants.ERROR_TYPES;\nconst clientMsgComponents = serverMessageConstants.INCOMING_MESSAGE_COMPONENTS;\nconst serverMsgComponents = serverMessageConstants.OUTGOING_MESSAGE_COMPONENTS;\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\nconst stringify = AFRAME.utils.coordinates.stringify;\n\nconst MESSAGE_THROTTLE_INTERVAL = 200;\n\n///\n/// ------------------------------- SYSTEM --------------------------------------\n///\n\nAFRAME.registerSystem('copresence-server', {\n    // Holds the local players data (head and arm location)\n    playerDataCache: {\n        head: { position: {x:0, y:1.6, z:0}, rotation: {x:0, y:0, z:0}},\n        left: { position: {x:0.25, y:-10001.3, z:0}, rotation: {x:0, y:0, z:0}},\n        right: { position: {x:-0.25, y:-10001.3, z:0}, rotation: {x:0, y:0, z:0}},\n        userdata: {}\n    },\n    playerDataDirty: true,\n    entities: {},\n    sphereCache: {},\n    connected: false,\n    throttledSendMsg: null,\n    schema: {\n        hostname: {type: 'string'},\n        port: {type: 'number'}\n    },\n\n    init: function(){\n        this.throttledServerUpdate = _.throttle(()=>{\n            this.sendClientUpdate();\n        }, MESSAGE_THROTTLE_INTERVAL, {leading:true, trailing:true});\n        // this.throttledSendMsg = _.throttle((msg)=>{\n        //     this.sendMsg(msg);\n        // }, MESSAGE_THROTTLE_INTERVAL, {leading:true, trailing:true});\n    },\n\n    tick: function(){\n        // Check if player data has changed\n        if(this.playerDataDirty && this.connected){\n            this.throttledServerUpdate();\n        }\n    },\n\n    sendClientUpdate: function () {\n      this.playerDataDirty = false;\n\n      let spheres = [];\n      for(let sphereId in this.sphereCache){\n        if(this.sphereCache[sphereId].dirty){\n          this.sphereCache[sphereId].dirty = false;\n          spheres.push({\n              id: sphereId,\n              position: this.sphereCache[sphereId].position\n          });\n        }\n      }\n\n      let playerMsg = new RoomClientPositionUpdate(this.playerDataCache, spheres);\n      this.sendMsg(playerMsg);\n    },\n\n    //returns a promise which is resolved when the server connects\n    connectToServer: function(){\n        if(this.connected) {\n            return;\n        }\n\n        this.region = CONFIG.DEFAULT_REGION;\n        this.roomname = undefined;\n\n        if(window.location.hash){\n          var matches = window.location.hash.match(/#(\\w+)-(\\w{4})/);\n          if(matches.length >= 3){\n            this.region = matches[1];\n            this.roomname = matches[2];\n          }\n        }\n\n        // If server doesnt exist, pick another available server\n        if(!CONFIG.SERVERS[this.region]){\n            this.region = Object.keys(CONFIG.SERVERS)[0];\n        }\n\n        let ws_url = [\n            \"wss://\",CONFIG.SERVERS[this.region], \":\", \"443\"\n        ].join('');\n\n        if(getParameterByName(\"server\")){\n            ws_url = getParameterByName(\"server\");\n        }\n\n        // client type is asynchronous\n        let connectWithClientType = (clientType) => {\n            ws_url = ws_url + '/' + clientType;\n            if(this.roomname){\n                ws_url = ws_url + '/' + this.roomname;\n            }\n\n            return new Promise((connected, error) => {\n                // window.UI.connecting();\n                this.ws = new WebSocket( ws_url );\n                this.ws.onopen = connected;\n                this.ws.onmessage = (msg) => {\n                    let msg_data;\n                    try {\n                        msg_data = JSON.parse(msg.data);\n                    } catch (e) {\n                        console.error(e);\n                        showErrorMessage(\"error_shake.gif\", \"Hmm, something went wrong\", \"Home\");\n                    }\n\n                    if(msg_data) {\n                        this.parseWebsocketMessage(\n                            msg_data[ clientMsgComponents.ALL_MESSAGES.MSG ],\n                            msg_data[ clientMsgComponents.ALL_MESSAGES.FROM ]\n                        );\n                    }\n                };\n\n                this.ws.onclose = () => {\n                    // window.UI.connectionError();\n                };\n\n                this.ws.onerror = (err) =>{\n                    if(this.ws.readyState > 1){\n                        // TODO: Error handling\n                        error(err);\n                        showErrorMessage(\"error_shake.gif\", \"Hmm, something went wrong\", \"Home\");\n                    }\n                };\n            });\n        };\n\n        getViewerType((clientType) => {\n            connectWithClientType(clientType);\n        });\n    },\n\n    parseWebsocketMessage: function(msg, from){\n        if(!msg) return;\n\n        let msgType = msg[ clientMsgComponents.ALL_MESSAGES.TYPE ];\n        let msgData = msg[ clientMsgComponents.ALL_MESSAGES.DATA ];\n\n        switch( msgType ){\n\n            // Room\n            case msgTypes.CONNECTION_INFO:\n                this.clientId = msgData[ serverMsgComponents.CONNECTION_INFO.CLIENT_ID ];\n                this.serverId = msgData[ serverMsgComponents.CONNECTION_INFO.SERVER_ID ];\n                break;\n\n            case msgTypes.ROOM_STATUS_INFO:\n                this.handleRoomStatusInfo(msgData);\n                break;\n\n            case msgTypes.ROOM_EXIT_SUCCESS:\n                break;\n\n            case msgTypes.ROOM_HEARTBEAT:\n                break;\n\n            case msgTypes.ROOM_CLIENT_JOIN:\n                if(!this.connected) break;\n                this.handleRemoteJoining(msgData);\n                break;\n\n            case msgTypes.ROOM_CLIENT_EXIT:\n                if(!this.connected) break;\n                this.handleRemoveEntity(msgData);\n                break;\n\n            // Remote Clients\n            case msgTypes.ROOM_CLIENT_COORDS_UPDATED:\n                if(!this.connected) break;\n                this.handleUpdatePlayerEntity(from, msgData);\n                if(msgData[ serverMsgComponents.ROOM_CLIENT_COORDS_UPDATED.SPHERES ]){\n                    for(let sphere of msgData[ serverMsgComponents.ROOM_CLIENT_COORDS_UPDATED.SPHERES ]) {\n                        this.handleSpherePositionUpdate(sphere);\n                    }\n                }\n                break;\n\n\n            // Spheres\n            case msgTypes.ROOM_SPHERE_CREATED:\n                if(!this.connected) break;\n                this.handleSphereCreation(msgData, true, msgTypes.ROOM_SPHERE_CREATED);\n                break;\n\n            case msgTypes.ROOM_SPHERE_GRABBED:\n                break;\n\n            case msgTypes.ROOM_SPHERE_POSITION_UPDATED:\n                // Deprecated\n                if(!this.connected) break;\n                this.handleSpherePositionUpdate(msgData);\n                break;\n\n            case msgTypes.ROOM_SPHERE_TONE_SET:\n                if(!this.connected) break;\n                this.handleSphereToneUpdate(msgData);\n                break;\n\n            case msgTypes.ROOM_SPHERE_CONNECTIONS_SET:\n                if(!this.connected) break;\n                this.handleSphereConnectionUpdate(msgData);\n                break;\n\n            case msgTypes.ROOM_SPHERE_RELEASED:\n                break;\n\n            case msgTypes.ROOM_SPHERE_STRUCK:\n                if(!this.connected) break;\n                this.handleSphereStruck(msgData);\n                break;\n\n            case msgTypes.ROOM_SPHERE_DELETED:\n                if(!this.connected) break;\n                this.handleSphereDeletion(msgData, true);\n                break;\n\n\n            case msgTypes.CREATE_SPHERE_DENIED:\n                break;\n\n            case msgTypes.CREATE_SPHERE_SUCCESS:\n                if(!this.connected) break;\n                this.handleSphereCreation(msgData, false, msgTypes.CREATE_SPHERE_SUCCESS);\n                break;\n\n            case msgTypes.GRAB_SPHERE_DENIED:\n                break;\n            case msgTypes.GRAB_SPHERE_SUCCESS:\n                break;\n\n            case msgTypes.UPDATE_SPHERE_POSITION_INVALID:\n                break;\n\n            case msgTypes.UPDATE_SPHERE_POSITION_DENIED:\n                break;\n\n            case msgTypes.RELEASE_SPHERE_DENIED:\n                break;\n\n            case msgTypes.RELEASE_SPHERE_INVALID:\n                break;\n\n            case msgTypes.RELEASE_SPHERE_SUCCESS:\n                break;\n\n            case msgTypes.DELETE_SPHERE_DENIED:\n                break;\n\n            case msgTypes.DELETE_SPHERE_INVALID:\n                break;\n\n            case msgTypes.DELETE_SPHERE_SUCCESS:\n                break;\n\n            case msgTypes.SET_SPHERE_TONE_DENIED:\n                break;\n\n            case msgTypes.SET_SPHERE_TONE_INVALID:\n                break;\n\n            case msgTypes.SET_SPHERE_TONE_SUCCESS:\n                break;\n\n            case msgTypes.SET_SPHERE_CONNECTIONS_DENIED:\n                break;\n\n            case msgTypes.SET_SPHERE_CONNECTIONS_INVALID:\n                break;\n\n            case msgTypes.SET_SPHERE_CONNECTIONS_SUCCESS:\n                break;\n\n            case msgTypes.CONNECT_SPHERES_IDENTICAL:\n                break;\n\n            case msgTypes.CONNECT_SPHERES_INVALID:\n                break;\n\n            case msgTypes.CONNECT_SPHERES_MISSING:\n                break;\n\n\n            case errorTypes.INVALID_URL:\n                showErrorMessage(\"error_shake.gif\", \"Invalid URL\", \"Try Again\");\n                break;\n            case errorTypes.NO_ROOMS_AVAILABLE:\n                showErrorMessage(\"error_full.gif\", \"All rooms are currently occupied. Try again later\", \"Home\");\n                break;\n            case errorTypes.NO_SUCH_ROOM:\n                showErrorMessage(\"error_shake.gif\", \"Hmm, something went wrong\", \"Home\");\n                break;\n            case errorTypes.ROOM_QUEUE_FULL:\n                showErrorMessage(\"error_full.gif\", \"There are too many players in this room\", \"Try Another\");\n                break;\n            case errorTypes.ROOM_FULL:\n                showErrorMessage(\"error_full.gif\", \"There are too many players in this room\", \"Try Another\");\n                break;\n            case errorTypes.ROOM_UNAVAILABLE:\n                showErrorMessage(\"error_knock.gif\", \"This room is temporarily unavailable\", \"Home\");\n                break;\n            case errorTypes.BUSY_TRY_AGAIN:\n                showErrorMessage(\"error_hit.gif\", \"Hmm, something went wrong\", \"Try Again\");\n                break;\n            case errorTypes.ROOM_NOT_READY:\n                showErrorMessage(\"error_knock.gif\", \"Hmm, something went wrong\", \"Try Again\");\n                break;\n            case errorTypes.ROOM_JOIN_TIMEOUT:\n                showErrorMessage(\"error_knock.gif\", \"Hmm, something went wrong\", \"Try Again\");\n                break;\n            case errorTypes.SPHERE_HOLD_TIMEOUT:\n                break;\n            case errorTypes.CLIENT_INACTIVITY_TIMEOUT:\n                break;\n\n            default:\n        }\n    },\n\n    sendMsg: function(msg){\n        if(this.ws && this.ws.readyState == 1) {\n            this.ws.send(msg.serialize());\n        }\n    },\n\n    handleRoomStatusInfo: function(data){\n        this.connected = true;\n        const roomName = data[ serverMsgComponents.ROOM_STATUS_INFO.ROOM_NAME ];\n        const roomSoundbank = data[ serverMsgComponents.ROOM_STATUS_INFO.SOUNDBANK ];\n\n        // list of clients already in the room arranged according to headsetType\n        const clientList = data[ serverMsgComponents.ROOM_STATUS_INFO.CLIENTS ];\n        Object.keys( clientList ).forEach(( headsetType ) => {\n            for(let i = 0; i<clientList[headsetType].length; i++) {\n                this.createRemotePlayerEntity(clientList[headsetType][i],headsetType);\n            }\n        });\n\n        let loadSpheres = (event) => {\n            document.removeEventListener(\"SPHERE_TEXTURE_LOADED\", loadSpheres);\n            // Create spheres\n            // if empty room, trigger tree created event\n            if(Object.keys(data[serverMsgComponents.ROOM_STATUS_INFO.SPHERES]).length===0){\n                this.sceneEl.systems.tree.isTreeCreated = true;\n                document.dispatchEvent(new Event(\"TREE_CREATED\"));\n            } else {\n                _.each(\n                    data[serverMsgComponents.ROOM_STATUS_INFO.SPHERES],\n                    (sphereData, sphereId) =>{\n                        sphereData[ serverMsgComponents.ROOM_SPHERE_POSITION_UPDATED.SPHERE_ID ] = sphereId;\n                        this.handleSphereCreation(sphereData, true,\"PER_SPHERE\");\n                    }\n                );\n            }\n        };\n\n        // texture needs to be loaded before spheres are created\n        // window.UI.joinedRoom(roomName);\n        window.location.hash = this.region+\"-\"+roomName;\n        this.sceneEl.removeAttribute('palette');\n        this.sceneEl.setAttribute('palette', roomSoundbank);\n        document.addEventListener(\"SPHERE_TEXTURE_LOADED\", loadSpheres);\n        this.sceneEl.components.palette.loadSphereTextures();\n    },\n\n    // ENTITY\n    handleRemoveEntity: function(data){\n        let clientId = data[ serverMsgComponents.ROOM_CLIENT_EXIT.CLIENT_ID ];\n        let entity = this.entities[clientId];\n        if(entity) {\n            entity.parentNode.removeChild(entity);\n        }\n    },\n\n    handleRemoveAllEntities: function(){\n        for (let key in this.entities) {\n            let entity = this.entities[key];\n            if(entity) {\n                entity.parentNode.removeChild(entity);\n            }\n        }\n    },\n\n    createAframeEntity: function(id){\n        let entity = document.createElement('a-entity');\n        entity.id = id;\n        this.entities[id] = entity;\n        return entity;\n    },\n\n    // Create new player entity representing remote player\n    createRemotePlayerEntity: function(id, headsetType=\"6dof\"){\n        const playerEntity = this.createAframeEntity(id);\n        setComponentProperty(playerEntity, 'smooth-motion', 'amount:3');\n        this.sceneEl.appendChild(playerEntity);\n\n        switch (headsetType){\n            case \"viewer\":\n                this.addHeadPart(playerEntity, 'head_' + id, 'avatar-head');\n                break;\n            case \"3dof\":\n                this.addHeadPart(playerEntity, 'head_' + id, 'avatar-head');\n                this.add3dofPart(playerEntity, 'right_' + id, 'avatar-hand');\n                break;\n            case \"6dof\":\n                this.addHeadPart(playerEntity, 'head_' + id, 'avatar-head');\n                this.add6dofPart(playerEntity, 'right_' + id, 'avatar-hand');\n                this.add6dofPart(playerEntity, 'left_' + id, 'avatar-hand');\n                break;\n            default:\n                console.warn(clientHeadset);\n        }\n\n        let triggerEvent = new CustomEvent(\"PLAYER_ADDED\", {\n            \"detail\": {\n                \"entity\": playerEntity\n            }\n        });\n        document.dispatchEvent(triggerEvent);\n\n    },\n\n    // a la carte remote player parts\n    addHeadPart: function(playerEntity, id, partType) {\n        const entity = this.createAframeEntity(id);\n        playerEntity.appendChild(entity);\n        setTimeout( () => {\n            setComponentProperty(entity, 'position', '0 1.6 0');\n            setComponentProperty(entity, 'smooth-motion', 'amount:3');\n            setComponentProperty(entity, 'mixin', partType);\n        });\n    },\n\n    add6dofPart: function(playerEntity, id, partType) {\n        const entity = this.createAframeEntity(id);\n        playerEntity.appendChild(entity);\n        setTimeout( () => {\n            setComponentProperty(entity, 'smooth-motion', 'amount:3');\n            setComponentProperty(entity, 'mixin', partType);\n        });\n    },\n\n    add3dofPart: function(playerEntity, id, partType, callback=null) {\n        const entity = this.createAframeEntity(id);\n        playerEntity.appendChild(entity);\n        setTimeout( () => {\n            setComponentProperty(entity, 'smooth-motion', 'amount:3');\n            setComponentProperty(entity, 'daydream-pointer', \"\");\n            setComponentProperty(entity, 'mixin', partType);\n        });\n    },\n\n    // Handle client position data from server\n    handleUpdatePlayerEntity: function(id, data){\n\n        if(!this.entities[id]) {\n            this.createRemotePlayerEntity(id);\n        }\n        // Update position and rotation of head and hands\n        ['head','left', 'right'].forEach((part) => {\n            let dataPart;\n\n            if(part == 'head') dataPart = serverMsgComponents.ROOM_CLIENT_COORDS_UPDATED.HEAD;\n            else if(part == 'left') dataPart = serverMsgComponents.ROOM_CLIENT_COORDS_UPDATED.LEFT;\n            else if(part == 'right') dataPart = serverMsgComponents.ROOM_CLIENT_COORDS_UPDATED.RIGHT;\n\n            const part_id = part + \"_\" + id;\n            let positionData = data[dataPart][clientMsgComponents.REF_COORDINATE_SET.POSITION];\n            let rotationData = data[dataPart][clientMsgComponents.REF_COORDINATE_SET.ROTATION];\n\n            if(part === 'head') {\n                setComponentProperty(this.entities[id], 'position', {x:positionData.x, y:0, z:positionData.z});\n                setComponentProperty(this.entities[part_id], 'position',{x:0, y:positionData.y, z:0});\n            } else {\n                let playerPos = getComponentProperty(this.entities[id], 'position');\n                if(!playerPos) { return; }\n                if(!this.entities[part_id]) { return; }\n                setComponentProperty(this.entities[part_id], 'position', {\n                    x:positionData.x-playerPos.x,\n                    y:positionData.y-playerPos.y,\n                    z:positionData.z-playerPos.z\n                });\n            }\n            setComponentProperty(this.entities[part_id], 'rotation', stringify(rotationData));\n        });\n    },\n\n    // Set client position data on server in next tick\n    setPlayerData: function(part, component, data){\n        if(!this.connected) return;\n\n        if(component === 'position' && part === 'head') {\n            let avatar = document.querySelector(\"#avatar\");\n            let avatarPosition = avatar.object3D.getWorldPosition();\n            this.playerDataCache[part][component].x = avatarPosition.x;\n            this.playerDataCache[part][component].y = avatarPosition.y;\n            this.playerDataCache[part][component].z = avatarPosition.z;\n        } else if(component === 'position') {\n            let hand = document.querySelector(\"#\"+part+\"Hand\");\n            let handPosition = hand.object3D.getWorldPosition();\n            this.playerDataCache[part][component].x = handPosition.x;\n            this.playerDataCache[part][component].y = handPosition.y;\n            this.playerDataCache[part][component].z = handPosition.z;\n        } else if(component == 'rotation') {\n            this.playerDataCache[part][component].x = data.x;\n            this.playerDataCache[part][component].y = data.y;\n            this.playerDataCache[part][component].z = data.z;\n        } else {\n            // this.playerData[part].userdata[component] = data;\n        }\n        this.playerDataDirty = true;\n    },\n\n\n    // SPHERES\n    createSphereOnServer: function(entity){\n        if(!this.connected) return;\n\n        this._addingSphereEntity = entity;\n        let msg = new CreateSphereAtPosition(entity.getAttribute('position'), entity.getAttribute('tone'));\n        this.sendMsg(msg);\n    },\n\n    setSphereData: function(uuid, component, data){\n        if(!this.connected) return;\n        if(!uuid) throw new Error(\"No uuid in setSphereData\");\n\n        if(component == 'position'){\n            if(!_.isEqual(data, this.sphereCache[uuid].position)) {\n                this.sphereCache[uuid].dirty = true;\n                // let msg = new SpherePositionUpdate(uuid, data);\n                // this.throttledSendMsg(msg)\n                this.sphereCache[uuid].position = _.clone(data);\n                this.playerDataDirty = true;\n            }\n        } else if(component == 'ball'){\n            if(data.grabbed != this.sphereCache[uuid].grabbed) {\n                this.sphereCache[uuid].grabbed = data.grabbed;\n                if (data.grabbed) {\n                    this.sendMsg(new GrabSphere(uuid));\n                } else {\n                    this.sendMsg(new ReleaseSphere(uuid));\n                }\n            }\n\n        } else if(component == 'tone'){\n            if(!_.isEqual(data, this.sphereCache[uuid].tone)) {\n                let msg = new SphereToneUpdate(uuid, data);\n                this.sendMsg(msg);\n                this.sphereCache[uuid].tone = _.clone(data);\n            }\n        }\n    },\n\n    removeSphere: function(uuid){\n        if(!this.connected) return;\n\n        if(uuid){\n            this.sendMsg(new DeleteSphere(uuid));\n        }\n    },\n\n    sendSphereStrike: function(uuid, velocity){\n        if(!this.connected) return;\n\n        if(uuid) {\n            this.sendMsg(new StrikeSphere(uuid, velocity));\n        }\n    },\n\n    handleSphereStruck: function (data) {\n        const sphereId = data[ serverMsgComponents.ROOM_SPHERE_STRUCK.SPHERE_ID ];\n        const strikeVelocity = data[ serverMsgComponents.ROOM_SPHERE_STRUCK.VELOCITY ];\n        let entity = document.querySelector(\"#sphere_\"+sphereId);\n        if(!entity) {\n            return;\n        }\n        entity.emit(\"controllerhit\", {\"velocity\":strikeVelocity, \"remote\": true});\n    },\n\n    handleSphereCreation: function(data, remote, message){\n        const sphereId = data[ serverMsgComponents.ROOM_STATUS_INFO.SPHERE_ID ];\n        const spherePosition = data[ serverMsgComponents.ROOM_STATUS_INFO.POSITION ];\n        const sphereTone = data[ serverMsgComponents.ROOM_STATUS_INFO.TONE ];\n        const sphereMeristem = data[ serverMsgComponents.ROOM_STATUS_INFO.MERISTEM ];\n\n        this.sphereCache[sphereId] = {\n            grabbed: false,\n            connections: [],\n            tone: sphereTone\n        };\n\n        if(!remote && this._addingSphereEntity){\n            // Local client created sphere, getting server generated UUID\n            this._addingSphereEntity.id = \"sphere_\"+sphereId;\n            this._addingSphereEntity.setAttribute('copresence','uuid', sphereId);\n            delete this._addingSphereEntity;\n        } else if(spherePosition){\n            // Create entity in tree system\n            document.querySelector('a-scene').systems.tree.createSphere({\n                id: \"sphere_\"+sphereId,\n                uuid: sphereId,\n                position: spherePosition,\n                tone: sphereTone,\n                meristem : sphereMeristem || false\n            });\n\n        }\n    },\n\n    handleSphereDeletion: function(data, remote=true){\n        const sphereId = data[ serverMsgComponents.ROOM_SPHERE_DELETED.SPHERE_ID ];\n        let sphere = document.querySelector(\"#sphere_\"+sphereId);\n        document.querySelector('a-scene').systems.tree.deleteSphere(sphere);\n    },\n\n    handleSpherePositionUpdate: function(data){\n        const sphereId = data[ serverMsgComponents.ROOM_SPHERE_POSITION_UPDATED.SPHERE_ID ];\n        const spherePosition = data[ serverMsgComponents.ROOM_STATUS_INFO.POSITION ];\n\n        this.sphereCache[sphereId].position = _.clone(spherePosition);\n\n        let entity = document.querySelector(\"#sphere_\"+sphereId);\n        if(!entity) {\n            return;\n        }\n        let ballComponent = entity.getAttribute('ball');\n        if(!ballComponent.grabbed) {\n            entity.setAttribute('position', spherePosition);\n        }\n    },\n\n    handleSphereToneUpdate: function(data){\n        const sphereId = data[ serverMsgComponents.ROOM_STATUS_INFO.SPHERE_ID ];\n        const sphereTone = data[ serverMsgComponents.ROOM_STATUS_INFO.TONE ];\n\n        this.sphereCache[sphereId].tone = _.clone(sphereTone);\n\n        let entity = document.querySelector(\"#sphere_\"+sphereId);\n        if(!entity) {\n            return;\n        }\n        entity.setAttribute('tone', sphereTone);\n\n    },\n\n    handleSphereConnectionUpdate: function (data) {\n        const sphereId = data[ serverMsgComponents.ROOM_STATUS_INFO.SPHERE_ID ];\n        const sphereConnections = data[ serverMsgComponents.ROOM_STATUS_INFO.CONNECTIONS ];\n        let connections = _.map(sphereConnections, (c)=> \"#sphere_\"+c );\n        this.sphereCache[sphereId].connections = connections.slice();\n\n        let entity = document.querySelector(\"#sphere_\"+sphereId);\n        if(!entity) {\n            return;\n        }\n        let ballComponent = entity.getAttribute('ball');\n        if(ballComponent && !ballComponent.grabbed) {\n            entity.setAttribute('ball', 'connections', connections);\n        }\n    },\n\n    handleRemoteJoining: function(data){\n        let clientId = data[ serverMsgComponents.ROOM_CLIENT_JOIN.CLIENT_ID ];\n        let clientHeadset = data[ serverMsgComponents.ROOM_CLIENT_JOIN.CLIENT_HEADSET_TYPE ];\n\n        // checks if controllers exists, delete / append based on interaction type\n        let addPointer = () => {\n\n            let right = document.querySelector(\"#right_\" + clientId);\n            let left = document.querySelector(\"#left_\" + clientId);\n            if(right && left){\n                let person = right.parentNode;\n                switch (clientHeadset){\n                    case \"viewer\":\n                        person.removeChild(right);\n                        person.removeChild(left);\n                        break;\n                    case \"3dof\":\n                        right.setAttribute(\"daydream-pointer\", \"\");\n                        person.removeChild(left);\n                        break;\n                    case \"6dof\":\n                        break;\n                    default:\n                }\n\n            } else {\n                requestAnimationFrame(addPointer);\n            }\n        };\n        addPointer();\n    },\n});\n\n\n///\n/// ------------------------------- COMPONENT --------------------------------------\n///\n\nAFRAME.registerComponent('copresence', {\n    uuidset: false,\n    schema: {\n        components: {default: ['position','ball', 'tone'], type: 'array'},\n        decimals: {default: 3},\n        playerpart: {default: undefined, type: 'string'},\n        uuid: {default: undefined, type: 'string'}\n    },\n\n    init: function(){\n\n        const system = this.el.sceneEl.systems[\"copresence-server\"];\n        const player = document.querySelector(\"#player\");\n        const avatar = document.querySelector(\"#avatar\");\n        if (!this.data.components.length) return;\n\n        // sphere is created locally, need to create one for the server\n        if(!this.data.uuid && !this.data.playerpart){\n            system.createSphereOnServer(this.el);\n        } else if(this.data.uuid){\n            this.uuidset = true;\n        }\n\n        let updatePlayerData = (evt) => {\n            this.data.components.forEach( (component) => {\n                // Check the component is in the list of watched components\n                if (evt.detail.name === component) {\n                    let d = evt.detail.newData;\n                    // Round data\n                    if (this.data.decimals !== false) {\n                        if (_.isObject(d)) {\n                            d = _.mapObject(d, (n) => {\n                                if (_.isNumber(n)) {\n                                    return Math.round(n * Math.pow(10, this.data.decimals)) / Math.pow(10, this.data.decimals);\n                                }\n                                return n;\n                            });\n                        }\n\n                        if (_.isNumber(d)) {\n                            d = Math.round(d * Math.pow(10, this.data.decimals)) / Math.pow(10, this.data.decimals);\n                        }\n                    }\n\n                    // Let the system know about the data change\n                    if (this.data.playerpart) {\n                        system.setPlayerData(this.data.playerpart, component, d);\n                    } else if(this.data.uuid){\n                        system.setSphereData(this.data.uuid, component, d);\n                    }\n                }\n            });\n        };\n\n        // Listen for component changes (position, etc)\n        this.el.addEventListener(\"componentchanged\", (evt)=>{\n            if(_.isEqual(evt.detail.newData, evt.detail.oldData)) { return; }\n\n            updatePlayerData(evt);\n\n        });\n\n        player.addEventListener(\"componentchanged\", (evt)=>{\n            // if data has updated and if it's avatar\n            if(_.isEqual(evt.detail.newData, evt.detail.oldData)) { return; }\n            if(!_.isEqual(avatar, this.el)) { return; }\n            updatePlayerData(evt);\n        });\n\n        this.el.addEventListener('controllerhit', (evt)=>{\n            if(!evt.detail.remote) {\n                let system = this.el.sceneEl.systems[\"copresence-server\"];\n                system.sendSphereStrike(this.data.uuid, evt.detail.velocity);\n            }\n        });\n    },\n\n    update: function(){\n        // Check if UUID is being set for the first time, if thats the case, send the sphere data\n        if(!this.uuidset && this.data.uuid){\n            this.uuidset = true;\n\n            let system = this.el.sceneEl.systems[\"copresence-server\"];\n            system.setSphereData(this.data.uuid, 'ball', this.el.getAttribute(\"ball\"));\n        }\n    },\n\n    remove: function(){\n        let system = this.el.sceneEl.systems[\"copresence-server\"];\n        if(this.data.uuid) {\n            system.removeSphere(this.data.uuid);\n        }\n    }\n});\n"
  },
  {
    "path": "js/components/daydream-manager.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nAFRAME.registerComponent('daydream-manager', {\n    controllerBall: null,\n    headset: null,\n    proxy: null,\n    controller: null,\n    isHitByRayCast:false,\n    schema: {\n        headset: {\n            default: \"#avatar\"\n        },\n        proxy: {\n            default: \"#rightHand\"\n        },\n        controller: {\n            default: \"#controller\"\n        },\n    },\n\n    init: function() {\n\n    },\n\n    play: function() {\n\n        this.headset = document.querySelector(this.data.headset);\n        this.proxy = document.querySelector(this.data.proxy);\n        this.controller = document.querySelector(this.data.controller);\n\n        let controller = document.querySelector(\"#controller\");\n        controller.addEventListener('raycaster-intersection', (event) => {\n            if(this.isHitByRayCast) { return; }\n\n            let proxyPosition = this.proxy.object3D.getWorldPosition();\n            event.detail.intersections.forEach((intersection) => {\n                let entity = intersection.object.el;\n                if(!entity.classList.contains('ball')){ return;}\n\n                let distance = entity.object3D.getWorldPosition().distanceToSquared (proxyPosition);\n\n                if(distance > 1) { return; }\n                entity.emit(\"controllerhit\", { velocity: 1.0 });\n                this.isHitByRayCast = true;\n            });\n\n        });\n\n        controller.addEventListener('raycaster-intersection-cleared', (event) => {\n            this.isHitByRayCast = false;\n        });\n    },\n\n    pause: function() {\n\n    },\n\n    tick: function() {\n\n        let controller = document.querySelector(\"#controller\");\n        if(!controller) { return; }\n\n        let rotation = getComponentProperty(controller, \"rotation\");\n        setComponentProperty(this.proxy, \"rotation\", {x:rotation.x, y:rotation.y, z:rotation.z});\n\n        let avatar = document.querySelector('#avatar');\n        avatar = avatar.object3D;\n        let position = getComponentProperty(controller, \"position\");\n\n        setComponentProperty(this.proxy, \"position\", {\n            x:position.x + avatar.position.x,\n            y:position.y,\n            z:position.z + avatar.position.z});\n    },\n\n    remove: function() {\n        // let prevEntity = document.querySelector(prevId);\n    },\n\n});\n"
  },
  {
    "path": "js/components/daydream-pointer.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nAFRAME.registerComponent('daydream-pointer', {\n    schema: {\n    },\n\n    init: function() {\n\n    },\n\n    play: function() {\n        let cone = document.createElement('a-cone');\n        cone.className = \"ray\";\n        cone.setAttribute(\"position\", \"0 0 -0.44\");\n        cone.setAttribute(\"rotation\", \"-90 0 0\");\n        cone.setAttribute(\"radius-bottom\", \"0.001\");\n        cone.setAttribute(\"radius-top\", \"0.005\");\n        cone.setAttribute(\"height\", \"1.0\");\n        cone.setAttribute(\"color\", \"#FFF\");\n        cone.setAttribute(\"shader\", \"flat\");\n        this.el.appendChild(cone);\n\n        let ball = document.createElement('a-sphere');\n        ball.className = \"ball\";\n        ball.setAttribute(\"segments-width\", \"8\");\n        ball.setAttribute(\"segments-height\", \"8\");\n        ball.setAttribute(\"position\", \"0 0.50 0\");\n        ball.setAttribute(\"radius\", \"0.01\");\n        cone.setAttribute(\"color\", \"#FFF\");\n        cone.setAttribute(\"shader\", \"flat\");\n        cone.appendChild(ball);\n    },\n\n    pause: function() {\n\n    },\n\n    tick: function() {\n\n    },\n});\n"
  },
  {
    "path": "js/components/fake-light.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nAFRAME.registerComponent( 'fake-light', {\n\n\tschema: {\n\t\tposition: { type: 'vec3', default: new THREE.Vector3( 3, 10, 1 ) }\n\t},\n\n\tgetLightPosition: function() {\n\t\treturn this.data.position;\n\t}\n});"
  },
  {
    "path": "js/components/ga.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nAFRAME.registerComponent( 'ga', {\n    init: function(){\n        this.el.addEventListener('controllerhit', ()=>{\n            ga('send', 'event', \"interaction\", \"hit\");\n        });\n\n        this.el.addEventListener('grab', ()=>{\n            ga('send', 'event', \"interaction\", \"grab\");\n        });\n    }\n});"
  },
  {
    "path": "js/components/gaze.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nlet BALL_RADIUS = 0.05;\n\nAFRAME.registerComponent('gaze', {\n    isRayCasterAvailable: false,\n    hoveredEntity: null,\n    schema: {\n\n    },\n\n    // gaze should only work for android, ios, desktop\n    // if vive, occulous, and daydream, disable gaze;\n    init: function() {\n\n        document.addEventListener(\"INTRO_COMPLETED\", (event) => {\n            let mode = this.el.sceneEl.components.splash.mode;\n            if( AFRAME.utils.device.isMobile() && mode !== \"360\" ){\n                this.addRaycaster();\n            }\n            document.addEventListener(\"CONTROLLER_CREATED\", (event) => {\n                if(event.detail.id !== \"Daydream Controller\") { return; }\n\n                this.createRayTargets();\n                this.removeShadows();\n\n                let avatar = document.querySelector('#avatar');\n                let controller = document.querySelector('#controller');\n                // swaps out racaster\n                if(avatar.hasAttribute('raycaster')) { avatar.removeAttribute(\"raycaster\"); }\n\n                let target = document.createElement('a-entity');\n                target.id = \"target\";\n                setComponentProperty(target, \"position\", \"0 0 0.25\");\n                controller.appendChild(target);\n\n\n                target.setAttribute(\"raycaster\", \"objects: .selectable; near:0; far:10; recursive: true; interval:200\");\n\n            }, false);\n\n        }, false);\n\n\n        document.addEventListener(\"BALL_CREATED\", (event) => {\n            if (AFRAME.utils.device.isMobile()) {\n                this.createRayTarget(event.detail.el);\n            }\n        }, false);\n\n    },\n\n    play: function() {\n\n    },\n\n    pause: function() {\n\n    },\n\n    tick: function() {\n\n    },\n\n    // required to redo hit states\n    addRaycaster: function() {\n        this.createRayTargets();\n\n        let avatar = document.querySelector('#avatar');\n\n        // deletes old rays and adds new one\n        if(avatar.hasAttribute('raycaster')) { avatar.removeAttribute(\"raycaster\"); }\n        avatar.setAttribute(\"raycaster\", \"objects: .selectable; near:0; far:10; recursive: true; interval:200\");\n\n        /*\n         * the raycaster intersection updates continuously on interval,\n         * returning an array ofhit objects\n         * Intersection clear not required\n        */\n\n        // null state is gaze-floor\n        let currentEntity = document.querySelector('#gaze-floor');\n\n        avatar.addEventListener('raycaster-intersection', (event) => {\n            if(event.detail.intersections.length === 0 ) { return; }\n\n            let entity = event.detail.intersections[0].object.el;\n            let isBall = entity.classList.contains('ball');\n\n            if(isBall && (currentEntity.id !== entity.id) ){\n                currentEntity.emit(\"unHighlight\");\n                currentEntity = entity;\n                this.hoveredEntity = entity;\n                this.hoveredEntity.emit(\"highlight\");\n\n                // teleport related\n                event.wasSphereHit = true;\n                document.dispatchEvent(new Event(\"ON_SPHERE_IN\"));\n                event.stopPropagation();\n\n            } else if(isBall && (currentEntity.id === entity.id) ){\n\n            } else if (this.hoveredEntity) {\n\n                currentEntity = document.querySelector('#gaze-floor');\n                this.hoveredEntity.emit(\"unHighlight\");\n                this.hoveredEntity = null;\n                // teleport related\n                document.dispatchEvent(new Event(\"ON_SPHERE_OUT\"));\n                event.stopPropagation();\n            }\n        });\n        avatar.addEventListener('raycaster-intersection-cleared', (event) => {\n            if(this.hoveredEntity){\n                currentEntity = document.querySelector('#gaze-floor');\n                this.hoveredEntity.emit(\"unHighlight\");\n                this.hoveredEntity = null;\n            }\n        });\n        document.addEventListener(\"mouseup\", (event) => {\n            if(this.hoveredEntity) {\n                this.hoveredEntity.emit(\"controllerhit\", {velocity:1.0});\n                event.wasSphereHit = true;\n            }\n        });\n    },\n\n    /*\n     * create ray targets, remove shadows for performance\n    */\n    createRayTargets: function() {\n        let tree = document.querySelector(\"#tree\");\n        let balls = tree.getElementsByClassName(\"selectable\");\n        for(let i=0; i<balls.length; i++){\n            this.createRayTarget(balls[i]);\n        }\n    },\n\n    removeShadows: function() {\n        let tree = document.querySelector(\"#tree\");\n        let balls = tree.getElementsByClassName(\"selectable\");\n        for(let i=0; i<balls.length; i++){\n            balls[i].components.ball.note.removeShadow();\n        }\n    },\n\n    createRayTarget: function(ball) {\n        ball.removeAttribute(\"geometry\");\n        ball.removeAttribute(\"material\");\n        let hitScalar = (this.getShape(ball) === \"sphere\") ? 1 : 1.5;\n        let ballRadius = this.getScale(ball) * BALL_RADIUS * hitScalar;\n        setComponentProperty(ball, \"geometry\", \"primitive: sphere; radius: \"+ballRadius+\"; segments-height:6; segments-width:6;\");\n        setComponentProperty(ball, \"material\", \"shader:flat; transparent:true; opacity:0.0; color:#ff0000;\");\n    },\n    doesControllerExists: function() {\n        this.el.sceneEl.systems.controllers.doesControllerExists();\n    },\n    getScale(entity) {\n\t\treturn entity.components.ball.note.head.scale;\n\t},\n    getShape(entity) {\n\t\treturn entity.components.ball.note.head.shape;\n\t},\n});\n"
  },
  {
    "path": "js/components/grab-move.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\n/**\n * Based on aframe/examples/showcase/tracked-controls.\n *\n * Handles events coming from the hand-controls.\n * Determines if the entity is grabbed or released.\n * Updates its position to move along the controller.\n */\nimport { getViewerType, getParameterByName } from '../util';\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nconst BALL_RADIUS = 0.05;\nconst CONTROLLER_BALL_RADIUS = 0.04;\nconst DELETE_VELOCITY = 0.2;\nconst VELOCITY_SAMPLES = 10;\n\nAFRAME.registerComponent('grab-move', {\n\tcontrollerBall: null,\n\tselectedSphere: -1,\n\tprevControllerPos: null,\n\tvelocityHistory : [],\n\tlastUpdate: Date.now(),\n\thasJustPlayed:false,\n\tpreviousClosestSphere:null,\n\tgrabbedSphere: null,\n\tschema: {\n\t\tgrabbing: {default: false, type: 'boolean'},\n\t\tisNearSphere: {default: false, type: 'boolean'},\n\t\tclosestSphere: { default:null }\n\t},\n\n\tinit: function () {\n\t\tthis.data.grabbing = false;\n\t\tdocument.addEventListener(\"BALL_CREATED\", (event) => {\n\n\t\t\t// when ball is created, makes sure hand is passed to correct ball\n\t\t\tif(this.data.grabbing && event.detail.grab && event.detail.hand===this.el.id) {\n\t\t\t\tlet closestSphere = event.detail.el;\n\t\t\t\tthis.selectBall(closestSphere);\n\t\t\t\tthis.grabbedSphere = closestSphere;\n\t\t\t\tclosestSphere.emit(\"controllerhit\", {velocity:0.5});\n\t\t\t\tthis.hasJustPlayed = true;\n\n\t\t\t}\n\t\t}, false);\n\n\t},\n\n\tselectBall: function(entity){\n\t\tif(this.selectedSphere && this.selectedSphere !== -1) {\n\t\t\tthis.selectedSphere.setAttribute('ball', 'grabbed', false);\n\t\t}\n\n\t\tif( entity === -1 ){\n\t\t\tthis.selectedSphere = -1;\n\t\t} else {\n\t\t\tthis.selectedSphere = entity;\n\t\t\tthis.selectedSphere.setAttribute('ball', 'grabbed', true);\n\t\t}\n\t},\n\n\t// 57, 48\n\tonKeyUp: function (event) {\n\t\tswitch(event.keyCode){\n\t\t\t// close bracket\n\t\t\tcase 219:\n\t\t\t\tthis.onTriggerPress();\n\t\t\t\tbreak;\n\t\t\t// open bracket\n\t\t\tcase 221:\n\t\t\t\tthis.onTriggerRelease();\n\t\t\t\tbreak;\n\t\t\t// close parenthisis\n\t\t\tcase 48:\n\t\t\t\tlet controllerPos = this.controllerBall.getWorldPosition();\n\t\t\t\tlet [ closestSphere, distanceSquared ] = this.getClosestEntity(tree, controllerPos);\n\t\t\t\tthis.grabbedSphere = closestSphere;\n\t\t\t\tif(this.grabbedSphere){\n\t\t\t\t\tthis.delete();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\n\t},\n\n\tcreatControllerBall: function() {\n\t\tlet material, geometry, ball;\n\t\tgeometry = new THREE.IcosahedronGeometry( 0.04, 1);\n\t\tmaterial = new THREE.MeshBasicMaterial( {\n\t\t\tcolor: 0xffffff,\n\t\t\ttransparent:true,\n\t\t\topacity: 0.25 });\n\t\tthis.controllerBall = new THREE.Mesh(geometry,material);\n\t\t// ball = new THREE.Object3D();\n\n\t\tthis.controllerBall.name = \"ball\";\n\t\tthis.el.object3D.add(this.controllerBall);\n\n\t\tif(this.el.id === \"avatar\") {\n\t\t\tthis.controllerBall.position.z = -1.0;\n\t\t} else {\n\t\t\tthis.controllerBall.position.z = -0.03;\n\t\t}\n\n\t\tthis.el.object3D.add(this.controllerBall);\n\n\t\t// delete from avatar\n\t\tdocument.addEventListener(\"CONTROLLER_CREATED\", (event) => {\n\t\t\tif(this.el.id !== \"avatar\") { return; }\n\t\t\tif(!this.controllerBall) { return; }\n\t\t\tthis.el.object3D.remove(this.controllerBall);\n        }, false);\n\n\t},\n\tplay: function () {\n\t\tthis.creatControllerBall();\n\t\tthis.enableInteractions();\n\n\t},\n\n\tenableInteractions: function() {\n\t\tif (this.el.id === \"avatar\" && getParameterByName('reticle')==='true') {\n\t\t\tdocument.addEventListener('keyup', this.onKeyUp.bind(this));\n\t\t}\n\t\tthis.el.addEventListener('triggerdown', this.onTriggerPress.bind(this));\n\t\tthis.el.addEventListener('triggerup', this.onTriggerRelease.bind(this));\n\n\t},\n\tpause: function () {\n\t\tif (this.el.id === \"avatar\" && getParameterByName('reticle')==='true') {\n\t\t\tdocument.removeEventListener('keyup', this.onKeyUp);\n\t\t}\n\n\t\tthis.el.removeEventListener('triggerdown', this.onTriggerPress);\n\t\tthis.el.removeEventListener('triggerup', this.onTriggerRelease);\n\t},\n\n\tgetClosestEntity: function(tree, pos, ignored) {\n\t\tlet minValue = 100000;\n\t\tlet closestId = -1;\n\t\tfor(let i=0; i<tree.children.length; i++ ){\n\t\t\tlet entity = tree.children[i];\n\t\t\tif(entity.id === ignored) { break; }\n\t\t\tlet distance = entity.object3D.getWorldPosition().distanceToSquared (pos);\n\t\t\tif(distance<minValue && entity.classList.contains('selectable')) {\n\t\t\t\tclosestId = i;\n\t\t\t\tminValue = distance;\n\t\t\t}\n\t\t}\n\t\treturn [tree.children[closestId], minValue];\n\t},\n\n\tonTriggerPress: function (evt) {\n\t\tlet avatar = document.querySelector('#avatar');\n\t\tlet controller = this.el;\n\n\t\tlet avatarPosition = getComponentProperty(avatar,\"position\");\n\t\tlet controllerPosition = getComponentProperty(controller,\"position\");\n\n\t\tthis.el.setAttribute('grab-move', 'grabbing', true);\n    \tlet tree = document.querySelector(\"#tree\");\n\n\t\tlet controllerPos = this.controllerBall.getWorldPosition();\n\t\tlet [ closestSphere, distanceSquared ] = this.getClosestEntity(tree, controllerPos);\n\n\t\tif(closestSphere) {\n\t\t\tlet hitScalar = (this.getShape(closestSphere) === \"sphere\") ? 1 : 1.5;\n\t\t\tlet thresholdSquared = this.getScale(closestSphere) * BALL_RADIUS*hitScalar + CONTROLLER_BALL_RADIUS;\n\t\t\tthresholdSquared = thresholdSquared*thresholdSquared;\n\t\t\tthis.data.isNearSphere = distanceSquared < thresholdSquared;\n\t\t} else {\n\t\t\tthis.data.isNearSphere = false;\n\t\t}\n\n\t\tthis.data.closestSphere = closestSphere;\n\n\t\tif(this.data.isNearSphere) {\n\t\t\tthis.selectBall(closestSphere);\n\t\t\tthis.grabbedSphere = closestSphere;\n            closestSphere.emit(\"grab\");\n\t\t} else {\n\t\t\tthis.selectBall(-1);\n\t\t\tthis.grabbedSphere = null;\n\t\t}\n\n\t\t// if trigger is down where no balls are near\n\t\tif(!this.data.isNearSphere) {\n\t\t\tlet treePosition = tree.object3D.getWorldPosition();\n\t\t\tcontrollerPos.sub(treePosition);\n\t\t\tlet lastTone = this.el.components['touch-color'].lastTone;\n\t\t\tlet triggerEvent = new CustomEvent(\"ON_TRIGGER_EMPTY_SPACE\",{ \"detail\": {\n\t\t\t\t\"controllerPos\":controllerPos,\n\t\t\t\t\"hand\": this.el.id,\n\t\t\t\t\"lastTone\" : lastTone\n\t\t\t}});\n\t\t\tdocument.dispatchEvent(triggerEvent);\n\t\t}\n\t},\n\n\tonTriggerRelease: function (evt) {\n\t\tthis.el.setAttribute('grab-move', 'grabbing', false);\n\t\tthis.selectBall(-1);\n\n\t\t// deletion check based on shaking gesture's velocity\n\t\tlet controllerPos = this.controllerBall.getWorldPosition();\n\t\tlet velocity = this.getVelocity(controllerPos);\n\t\tif(velocity > DELETE_VELOCITY) {\n\t\t\tthis.delete();\n\t\t}\n\t},\n\n\tdelete: function () {\n\t\tif(!this.grabbedSphere) { return; }\n\t\tlet controllerPos = this.controllerBall.getWorldPosition();\n\t\tlet triggerEvent = new CustomEvent(\"ON_DELETE\",{ \"detail\": {\n\t\t\t\"closestEntity\": this.grabbedSphere,\n\t\t}});\n\t\tdocument.dispatchEvent(triggerEvent);\n\t},\n\n\tgetVelocity: function(controllerPos) {\n\t\tlet velocity;\n\t\t// compute velocity\n\t\tif (this.prevControllerPos){\n\t\t\tlet distance = this.prevControllerPos.distanceTo(controllerPos);\n\t\t\tlet elapsedTime = Date.now() - this.lastUpdate;\n\t\t\tlet instantVelocity = distance / elapsedTime;\n\t\t\tthis.lastUpdate = Date.now();\n\t\t\tthis.prevControllerPos.copy(controllerPos);\n\t\t\tthis.velocityHistory.push(distance);\n\t\t} else {\n\t\t\tthis.prevControllerPos = controllerPos.clone();\n\t\t\tthis.velocityHistory.push(0);\n\t\t}\n\n\t\t//keep no more than 10\n\t\tif (this.velocityHistory.length > VELOCITY_SAMPLES){\n\t\t\tthis.velocityHistory.shift();\n\t\t}\n\n\t\t//compute the velocity as an average over all of the velocity history\n\t\tvelocity = this.velocityHistory.reduce(function(a, b) { return a + b; }) / this.velocityHistory.length;\n\t\tvelocity = Math.min(velocity / 0.02, 1); // normalize\n\t\treturn velocity;\n\t},\n\n\ttick: function(){\n\n\t\tlet tree = document.querySelector(\"#tree\");\n\t\tif(!this.controllerBall || tree.children.length === 0) { return; }\n\n\t\tlet controllerPos = this.controllerBall.getWorldPosition();\n\t\tlet [ closestSphere , distanceSquared ] = this.getClosestEntity(tree, controllerPos);\n\t\tif(!closestSphere) { return; }\n\t\tif(closestSphere.id === \"TEMP_NEW_SPHERE_ID\") { return; }\n\n\t\tlet hitScalar = (this.getShape(closestSphere) === \"sphere\") ? 1 : 1.5;\n\n\t\tlet thresholdSquared = this.getScale(closestSphere) * BALL_RADIUS*hitScalar + CONTROLLER_BALL_RADIUS;\n\t\tthresholdSquared = thresholdSquared*thresholdSquared;\n\t\tthis.data.isNearSphere = distanceSquared < thresholdSquared;\n\t\tlet velocity = this.getVelocity(controllerPos);\n\n\t\t// hightlight controller if near a ball\n\t\tif(this.data.isNearSphere) {\n\t\t\tthis.previousClosestSphere = this.data.closestSphere;\n\t\t\tthis.data.closestSphere = closestSphere;\n\t\t\tthis.controllerBall.material.opacity = 0.75;\n\t\t} else {\n\t\t\tthis.controllerBall.material.opacity = 0.25;\n\t\t\tthis.hasJustPlayed = false;\n\t\t}\n\n\t\t// repositions ball when grabbing so ball rests just above controller.\n\t\tif(this.data.grabbing && (this.selectedSphere !== -1)) {\n\t\t\tlet treePosition = tree.object3D.getWorldPosition();\n\t\t\t// controllerPos = this.controllerBall.getWorldPosition();\n\n\t\t\tlet matrix = new THREE.Matrix4();\n\t\t\tmatrix.extractRotation( this.el.object3D.matrix );\n\n\t\t\t// let tone = this.getTone(this.selectedSphere);\n\t\t\tlet tone = this.getTone(this.selectedSphere) % this.getTonesInScale();\n\t\t\tlet hitScalar = (this.getShape(this.selectedSphere) === \"sphere\") ? 1 : 1.5;\n\t\t\tlet scale = 1 - tone / (this.getTotalTones() - 1);\n\t\t\tlet ballSize = 0.1;\n\n\t\t\tballSize *= scale*hitScalar;\n\t\t\tballSize += 0.05;\n\n\t\t\tlet direction = new THREE.Vector3( 0, 0, 1 );\n\t\t\tdirection.applyMatrix4( matrix );\n\t\t\tdirection.normalize();\n\t\t\tdirection.multiplyScalar(ballSize-0.05);\n\t\t\tcontrollerPos.sub(treePosition);\n\t\t\tcontrollerPos.sub(direction);\n\n\t\t\t// if ball position is too low\n\t\t\tif (controllerPos.y < 0.25) {\n\t\t\t\tcontrollerPos.y = 0.25;\n\t\t\t}\n\n\t\t\tsetComponentProperty(this.selectedSphere, \"position\", AFRAME.utils.coordinates.stringify(controllerPos));\n\t\t}\n\n\t\t// touch hits\n\t\tif(this.data.grabbing) { return; }\n\t\tif(!this.data.isNearSphere) { return; }\n\t\tif(!this.hasJustPlayed || this.previousClosestSphere !== closestSphere) {\n\t\t\tclosestSphere.emit(\"controllerhit\", {velocity:velocity, controllerPosition: this.controllerBall.getWorldPosition()});\n\t\t\tthis.hasJustPlayed = true;\n      \t\tthis.el.emit('vibrate');\n\t\t}\n\t},\n\n\tgetTone(el){\n\t\treturn el.getAttribute('tone');\n\t},\n\tgetTotalTones(el){\n\t\treturn this.el.sceneEl.components.palette.totalNotes();\n\t},\n\tgetTonesInScale() {\n        return this.el.sceneEl.components.palette.noteCount();\n    },\n\tgetScale(entity) {\n\t\treturn entity.components.ball.note.head.scale;\n\t},\n\tgetShape(entity) {\n\t\treturn entity.components.ball.note.head.shape;\n\t},\n});\n"
  },
  {
    "path": "js/components/haptics.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nAFRAME.registerComponent('haptics', {\n\n    schema: {\n        hand: {type:'string'}\n    },\n\n    init: function(){\n        this.el.addEventListener('vibrate', ()=>{\n            try {\n                let gamepad = navigator.getGamepads()[this.el.getAttribute(\"tracked-controls\").controller];\n                if ('hapticActuators' in gamepad && gamepad.hapticActuators.length > 0) {\n                    gamepad.hapticActuators[0].pulse(0.7, 10);\n                }\n            } catch (e){}\n        });\n    }\n});\n"
  },
  {
    "path": "js/components/headset-material.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { HeadsetColor, HeadsetShadow } from '../core/colors';\n\nAFRAME.registerComponent( 'headset-material', {\n\tisLoaded: false,\n\tschema: {\n\t\topacity: {\n\t\t\tdefault: 1.0\n\t\t},\n\t},\n\n\tinit: function() {\n\t\tthis.el.addEventListener( 'model-loaded', this.onLoaded.bind( this ) );\n\t\tthis.el.addEventListener(\"componentchanged\", (event) => {\n\t\t\tthis.onUpdated();\n\t\t}, false);\n\t},\n\n\tonLoaded: function() {\n\t\tlet mesh = this.el.components[\"collada-model\"].model.children[ 0 ].children[ 0 ];\n\t\tmesh.material = new THREE.MultiMaterial([\n\t\t\tnew THREE.MeshBasicMaterial( { color: HeadsetColor, transparent:true} ),\n\t\t\tnew THREE.MeshBasicMaterial( { color: HeadsetShadow, transparent:true} )\n\t\t]);\n\t\tthis.isLoaded = true;\n\t\tthis.onUpdated();\n\t},\n\n\tonUpdated: function() {\n\t\tif(!this.isLoaded) { return; }\n\t\tlet mesh = this.el.components[\"collada-model\"].model.children[ 0 ].children[ 0 ];\n\t\tlet transparent = (this.data.opacity<1) ? true : false;\n\n\t\tmesh.material.materials[0].transparent = transparent;\n\t\tmesh.material.materials[1].transparent = transparent;\n\n\t\tmesh.material.materials[0].opacity = this.data.opacity;\n\t\tmesh.material.materials[1].opacity = this.data.opacity;\n\n\t},\n\n});\n"
  },
  {
    "path": "js/components/listener.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport Tone from 'tone';\n\nAFRAME.registerComponent('listener', {\n    init() {},\n    remove() {},\n    tick() {\n        // const pos = this.el.object3D.position\n        const object3d = this.el.object3D;\n        object3d.updateMatrixWorld();\n        const matrixWorld = object3d.matrixWorld;\n        const position = new THREE.Vector3().setFromMatrixPosition(matrixWorld);\n        Tone.Listener.setPosition(position.x, position.y, position.z);\n        const mOrientation = matrixWorld.clone();\n        mOrientation.setPosition({\n            x: 0,\n            y: 0,\n            z: 0\n        });\n        const vFront = new THREE.Vector3(0, 0, 1);\n        vFront.applyMatrix4(mOrientation);\n        vFront.normalize();\n        const vUp = new THREE.Vector3(0, -1, 0);\n        vUp.applyMatrix4(mOrientation);\n        vUp.normalize();\n        Tone.Listener.setOrientation(vFront.x, vFront.y, vFront.z, vUp.x, vUp.y, vUp.z);\n    }\n});\n"
  },
  {
    "path": "js/components/palette.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { Instruments, NoteCount, InstrumentCount, TotalNotes} from '../core/instruments';\nimport { BallColors, ControllerColors } from '../core/colors';\nimport { Shapes } from '../core/shapes';\n\nconst SPHERE_BASE_URL = './static/img/ball_';\nconst SPHERE_BASE_EXT = '.png';\n\nAFRAME.registerComponent('palette', {\n    schema: {\n        type: 'int',\n        default: 0\n    },\n\n    update() {\n        // Dispose of the old instrument\n        if (this._currentInstrument) {\n            this._currentInstrument.dispose();\n        }\n\n        // Set the new instrument\n        this._currentInstrument = Instruments[this.data % Instruments.length];\n    },\n\n    trigger(time, tone, velocity, x, y, z) {\n        this._currentInstrument.trigger(time, tone, velocity, x, y, z);\n    },\n\n    totalNotes() {\n        return TotalNotes;\n    },\n\n    noteCount() {\n        return NoteCount;\n    },\n\n    colorPalette() {\n        return BallColors[ this._currentInstrument.color ];\n    },\n\n    controllerColors() {\n        return ControllerColors[ this._currentInstrument.color ];\n    },\n\n    shapePalette(id) {\n        return Shapes[ id ];\n    },\n\n    loadSphereTextures() {\n\n        let image134Circles = document.getElementById( 'ball_circles_134' );\n        let image567Circles = document.getElementById( 'ball_circles_567' );\n        let image134Triangles = document.getElementById( 'ball_triangles_134' );\n        let image567Triangles = document.getElementById( 'ball_triangles_567' );\n        let image134Squares = document.getElementById( 'ball_squares_134' );\n        let image567Squares = document.getElementById( 'ball_squares_567' );\n\n        this._textureSprite134Circles = new THREE.Texture( image134Circles );\n        this._textureSprite567Circles = new THREE.Texture( image567Circles );\n        this._textureSprite134Triangles = new THREE.Texture( image134Triangles );\n        this._textureSprite567Triangles = new THREE.Texture( image567Triangles );\n        this._textureSprite134Squares = new THREE.Texture( image134Squares );\n        this._textureSprite567Squares = new THREE.Texture( image567Squares );\n\n        this._textureSprite134Circles.wrapS = this._textureSprite134Circles.wrapT = THREE.RepeatWrapping;\n        this._textureSprite567Circles.wrapS = this._textureSprite567Circles.wrapT = THREE.RepeatWrapping;\n        this._textureSprite134Triangles.wrapS = this._textureSprite134Triangles.wrapT = THREE.RepeatWrapping;\n        this._textureSprite567Triangles.wrapS = this._textureSprite567Triangles.wrapT = THREE.RepeatWrapping;\n        this._textureSprite134Squares.wrapS = this._textureSprite134Squares.wrapT = THREE.RepeatWrapping;\n        this._textureSprite567Squares.wrapS = this._textureSprite567Squares.wrapT = THREE.RepeatWrapping;\n\n        setTimeout( () => {\n            document.dispatchEvent(new Event(\"SPHERE_TEXTURE_LOADED\"));\n        },0);\n\n    },\n\n    textureSprite134(id) {\n        return [this._textureSprite134Circles, this._textureSprite134Squares, this._textureSprite134Triangles][id];\n    },\n\n    textureSprite567(id) {\n        return [this._textureSprite567Circles, this._textureSprite567Squares, this._textureSprite567Triangles][id];\n    },\n\n});\n"
  },
  {
    "path": "js/components/proximity-check.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nimport { getParameterByName } from '../util';\n\nAFRAME.registerComponent('proximity-check', {\n    schema: {\n        objects: {\n            default: [],\n            type: 'array'\n        },\n    },\n\n    init: function() {\n\n    },\n\n    play: function() {\n        document.addEventListener('PLAYER_ADDED', (event) => {\n            // adds to list\n            this.data.objects.push(event.detail.entity);\n            // when remote heads move check distances\n            let head = document.querySelector(\"#head_\"+event.detail.entity.id);\n            let person = head.parentNode;\n            person.addEventListener('componentchanged', (event) => {\n                this.checkDistances();\n            });\n            this.checkDistances();\n        });\n\n        if(getParameterByName(\"proximity\")!==\"false\"){\n            // when player moves check distances\n            let player = document.querySelector(\"#player\");\n            player.addEventListener('componentchanged', (event) => {\n                this.checkDistances();\n            });\n            this.checkDistances();\n        }\n    },\n\n    pause: function() {\n\n    },\n\n    checkDistances:function() {\n        let myPosition = this.el.object3D.getWorldPosition();\n\n        let entity;\n        for(let i=0; i<this.data.objects.length; i++){\n            entity = this.data.objects[i];\n            let head = document.querySelector(\"#head_\"+entity.id);\n            if(!head){ return; }\n\n            let distanceSquared = myPosition.distanceToSquared(head.object3D.getWorldPosition());\n            const max = 1.5;\n            const min = 0.5;\n            let opacity = (distanceSquared<max) ? (distanceSquared-min)/(max-min) : 1;\n            opacity = (opacity<0) ? 0 : opacity;\n            setComponentProperty(head,\"headset-material\", \"opacity:\"+opacity);\n\n            let left = document.querySelector(\"#left_\"+entity.id);\n            if(left){\n                setComponentProperty(left,\"controller-material\", \"opacity:\"+opacity);\n            }\n\n            let right = document.querySelector(\"#right_\"+entity.id);\n            if(right){\n                setComponentProperty(right,\"controller-material\", \"opacity:\"+opacity);\n                if(right.childNodes.length > 0){\n                    let ray = right.childNodes[0];\n                    ray.setAttribute(\"opacity\",opacity);\n                    let cone = right.childNodes[0].childNodes[0];\n                    cone.setAttribute(\"opacity\",opacity);\n                }\n            }\n        }\n    },\n});\n"
  },
  {
    "path": "js/components/quaternion.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\n/**\n * Quaternion.\n *\n * Represents orientation of object in three dimensions. Similar to `rotation`\n * component, but avoids problems of gimbal lock.\n *\n * See: https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation\n */\nAFRAME.registerComponent('quaternion', {\n  schema: {type: 'vec4'},\n\n  update: function () {\n    var data = this.data;\n    this.el.object3D.quaternion.set(data.x, data.y, data.z, data.w);\n  }\n});\n"
  },
  {
    "path": "js/components/smooth-motion.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nvar degToRad = require('three').Math.degToRad;\n\n\nfunction LowpassFilter(Fc) {\n    var Q = 0.707;\n    var K = Math.tan(Math.PI * Fc);\n    var norm = 1 / (1 + K / Q + K * K);\n\n    this.a0 = K * K * norm;\n    this.a1 = 2 * this.a0;\n    this.a2 = this.a0;\n    this.b1 = 2 * (K * K - 1) * norm;\n    this.b2 = (1 - K / Q + K * K) * norm;\n    this.z1 = this.z2 = 0;\n    this.value = 0;\n\n\n    this.tick = function(value) {\n        var out = value * this.a0 + this.z1;\n        this.z1 = value * this.a1 + this.z2 - this.b1 * out;\n        this.z2 = value * this.a2 - this.b2 * out;\n        return out;\n    };\n\n\n}\n\n\n/**\n * Interpolate component for A-Frame.\n */\nAFRAME.registerComponent('smooth-motion', {\n    schema: {\n        amount: {\n            default: 1\n        }\n    },\n\n    /**\n     * Called once when component is attached. Generally for initial setup.\n     */\n    init: function() {\n        this.quaternion = new THREE.Quaternion();\n    },\n\n    /**\n     * Called when component is attached and when component data changes.\n     * Generally modifies the entity based on the data.\n     */\n    update: function(oldData) {\n        if (!this.filterPosX) {\n            var amount = parseFloat(this.data.amount);\n            if (amount > 0) {\n                var freq = 0.1 / amount;\n\n                this.filterPosX = new LowpassFilter(freq, this);\n                this.filterPosY = new LowpassFilter(freq, this);\n                this.filterPosZ = new LowpassFilter(freq, this);\n            }\n        }\n    },\n\n    /**\n     * Called when a component is removed (e.g., via removeAttribute).\n     * Generally undoes all modifications to the entity.\n     */\n    remove: function() {},\n\n    /**\n     * Called on each scene tick.\n     */\n    tick: function(t) {\n        if (!this.filterPosX) { return; }\n\n        var p = AFRAME.utils.entity.getComponentProperty(this.el, 'position');\n        this.el.object3D.position.setX(this.filterPosX.tick(p.x));\n        this.el.object3D.position.setY(this.filterPosY.tick(p.y));\n        this.el.object3D.position.setZ(this.filterPosZ.tick(p.z));\n\n        var quaternion;\n        if (AFRAME.utils.entity.getComponentProperty(this.el, 'quaternion')) {\n            var q = AFRAME.utils.entity.getComponentProperty(this.el, 'quaternion');\n            quaternion = new THREE.Quaternion(q._x, q._y, q._z, q._w);\n        } else {\n            var r = AFRAME.utils.entity.getComponentProperty(this.el, 'rotation');\n            quaternion = new THREE.Quaternion();\n            quaternion.setFromEuler(new THREE.Euler(degToRad(r.x), degToRad(r.y), degToRad(r.z), 'YXZ'));\n        }\n\n        this.quaternion.slerp(quaternion, 1 / (3 * parseFloat(this.data.amount)));\n        this.el.object3D.quaternion.copy(this.quaternion);\n    }\n});\n"
  },
  {
    "path": "js/components/teleport.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nimport { getParameterByName } from '../util'\nimport TWEEN from 'tween.js';\n\nAFRAME.registerComponent('teleport', {\n    isHitByRayCast: false,\n    isAnimating: false,\n    isOverSphere: false,\n    floorTarget: null,\n    doesControllerExist: false,\n    schema: {\n\n    },\n\n    init: function() {\n        this.floorTarget = document.querySelector('#gaze-hit');\n        this.floorTarget.setAttribute(\"visible\", \"false\");\n    },\n\n    play: function() {\n\n        let onUp = (event) => {\n            // flag passed from clicker js\n            if (event.wasSphereHit) { return; }\n            if(this.isHitByRayCast){\n                this.teleport();\n            }\n        };\n\n        document.addEventListener('INTRO_COMPLETED', (event) => {\n\n            setTimeout(() => {\n                document.addEventListener(\"CONTROLLER_CREATED\", (event) => {\n                    this.doesControllerExist = true;\n                    this.hideTeleport();\n\n                    // disables teleport on click screen tap\n                    if (event.detail.id === \"Daydream Controller\") {\n                        document.removeEventListener('touchend', onUp);\n                    }\n\n                    // enables teleport on controller\n                    document.addEventListener('trackpadup', onUp);\n                    document.addEventListener('thumbstickup', onUp);\n                }, false);\n\n\n                let avatar = document.querySelector('#avatar');\n                if(getParameterByName(\"teleport\")===\"false\"){\n                    this.hideTeleport();\n                    return;\n                }\n\n                document.addEventListener(\"touchstart\", (event) => {});\n                document.addEventListener(\"touchend\", (event) => {});\n\n                let isTablet  = this.isTabletLikeDimensions() && this.isTouchDevice();\n                if (AFRAME.utils.device.isMobile() || isTablet) {\n                    document.addEventListener('mouseup', onUp);\n                } else {\n                    this.hideTeleport();\n                    return;\n                }\n\n                this.el.addEventListener('raycaster-intersected', (event) => {\n                    if(this.isOverSphere) { return; }\n                    if(this.isAnimating) { return; }\n                    if(this.isHitByRayCast) { return; }\n                    this.showTeleport();\n                });\n                this.el.addEventListener('raycaster-intersected-cleared', (event) => {\n                    if(this.isOverSphere) { return; }\n                    if(this.isAnimating) { return; }\n                    if(!this.isHitByRayCast) { return; }\n                    this.hideTeleport();\n                });\n\n                document.addEventListener('keyup', (event) => {\n                    switch (event.keyCode) {\n                        case 32: // spacebar\n                            this.onUp(event);\n                            break;\n                    }\n                });\n\n                document.addEventListener('ON_SPHERE_IN', (event) => {\n                    this.isOverSphere = true;\n                    this.hideTeleport();\n\n                });\n                document.addEventListener('ON_SPHERE_OUT', (event) => {\n                    this.isOverSphere = false;\n                });\n\n            });\n\n        });\n    },\n\n    hideTeleport: function() {\n        this.floorTarget.setAttribute(\"visible\", \"false\");\n        this.isHitByRayCast = false;\n    },\n\n    showTeleport: function() {\n        this.floorTarget.setAttribute(\"visible\", \"true\");\n        this.isHitByRayCast = true;\n    },\n\n    teleport: function() {\n        if (!this.getTreeCreated()) { return; }\n        if (this.isNearSphere()) { return; }\n\n        if (!this.isHitByRayCast) { return; }\n\n        this.isAnimating = true;\n        const target = document.querySelector('#gaze-hit');\n        const speed = 100;\n        const player = document.querySelector(\"#player\");\n        const avatar = document.querySelector('#avatar');\n        const daydream = document.querySelector('#daydream');\n        let targetPosition = target.object3D.position;\n        let position = getComponentProperty(player,\"position\");\n        let moveTo = (data) => {\n            setComponentProperty(player, \"position\", position);\n            if (!daydream) { return; }\n            setComponentProperty(daydream, \"position\", position);\n        };\n        if (this.flyTween) {\n            this.flyTween.stop();\n        }\n        this.flyTween = new TWEEN.Tween(position)\n            .to(targetPosition.clone(), speed)\n            .easing(TWEEN.Easing.Exponential.InOut)\n            .onUpdate(moveTo)\n            .onComplete(()=>{\n                this.isAnimating = false;\n                this.hideTeleport();\n            })\n            .start();\n    },\n\n    pause: function() {\n\n    },\n\n    tick: function() {\n        if(this.isAnimating) { return; }\n\n        let cam = document.querySelector(\"[raycaster]\").object3D;\n        let orientation = cam.getWorldDirection();\n        orientation.y *= -1;\n        orientation.z *= -1;\n        orientation.x *= -1;\n        let ray = new THREE.Ray(cam.getWorldPosition(), orientation);\n\n        let point = ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0,1,0)));\n        if(point) {\n            point.y = 0.02;\n            this.floorTarget.setAttribute('position', point);\n        }\n\n    },\n\n    getTreeCreated: function() {\n        return this.el.sceneEl.systems.tree.getTreeCreated();\n    },\n\n    isNearSphere: function() {\n        let nearSphereCount = 0;\n        let elements = document.querySelectorAll('[grab-move]');\n\n        let entity;\n        for(let i=0; i<elements.length; i++){\n            entity = elements[i];\n            if(getComponentProperty(entity, \"grab-move\").isNearSphere) {\n                nearSphereCount++;\n            }\n        }\n\n        return (nearSphereCount===0) ? false : true;\n    },\n    isTabletLikeDimensions: function() {\n        let w = screen.availWidth;\n        let h = screen.availHeight;\n        return ( ( (w > h) ? w : h) >= 960);\n    },\n    isTouchDevice: function() {\n        return 'ontouchstart' in window || navigator.maxTouchPoints;      // works on most browsers ||  works on IE10/11 and Surface\n    },\n});\n"
  },
  {
    "path": "js/components/tone.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport Tone from 'tone';\nimport {scale} from '../util';\nimport { Instruments, NoteCount, InstrumentCount, TotalNotes} from '../core/instruments';\n\n//create the background track\nconst filetype = Tone.Buffer.supportsType('mp3') ? 'mp3' : 'ogg';\nconst ambient = new Tone.Player(`static/audio/bg.${filetype}`, () => {\n    ambient.start();\n    ambient.volume.rampTo(-14, 4);\n}).toMaster();\nambient.volume.value = -Infinity;\nambient.loop = true;\n\nwindow.addEventListener('blur', () => {\n    Tone.Master.mute = true;\n});\n\nwindow.addEventListener('focus', () => {\n    Tone.Master.mute = false;\n});\n\ndocument.addEventListener(\"ENTERED_FOREST\", () => {\n    Tone.Master.mute = false;\n});\n\ndocument.addEventListener(\"EXITED_FOREST\", () => {\n    Tone.Master.mute = true;\n});\n\nAFRAME.registerComponent('tone', {\n    schema: {\n        type: 'int',\n        default: 0\n    },\n    init() {\n\n\n        // listen for events\n        this.el.addEventListener('controllerhit', this.controllerHit.bind(this));\n\n        this._palette = this.el.sceneEl.components.palette;\n\n        this._lastTrigger = 0;\n\n    },\n    remove() {\n\n    },\n    update(oldData) {\n        if (this.data !== oldData && typeof oldData !== \"undefined\") {\n            this.trigger(Tone.now() + 0.1, 0.4);\n        }\n    },\n    getPosition() {\n        const object3d = this.el.object3D;\n        object3d.updateMatrixWorld();\n        const matrixWorld = object3d.matrixWorld;\n        const position = new THREE.Vector3().setFromMatrixPosition(matrixWorld);\n        return position;\n    },\n    trigger(time = Tone.now(), velocity = 1) {\n        // this.env.triggerAttack(time, velocity)\n        const pos = this.getPosition();\n        Instruments[this.getShape()].trigger(time, this.getNote(), velocity, pos.x, pos.y, pos.z);\n    },\n    hit(time, velocity, controllerPosition = null, sourceId = null) {\n        // hits.push('#' + this.el.id);\n        this.trigger(time, velocity);\n        this.el.emit('hit', {\n            from: sourceId,\n            velocity: velocity,\n            delay: time - Tone.now(),\n            controllerPosition: controllerPosition\n        });\n    },\n    updatePosition() {\n        const object3d = this.el.object3D;\n        object3d.updateMatrixWorld();\n        const matrixWorld = object3d.matrixWorld;\n        const position = new THREE.Vector3().setFromMatrixPosition(matrixWorld);\n        this.panner.setPosition(position.x, position.y, position.z);\n\n    },\n    controllerHit(data) {\n        if( !this.el.getAttribute(\"visible\")) {return;}\n\n        const time = Tone.Time('+0.01').toSeconds();\n        if ((time - this._lastTrigger) > 0.1) {\n            this._lastTrigger = time;\n            this.hit(time, data.detail.velocity, data.detail.controllerPosition);\n        }\n    },\n    //return the shape number of the tone 0-2\n    getShape(){\n        return Math.floor(this.data / NoteCount);\n    },\n    //return the note number of the tone 0-5\n    getNote(){\n        return this.data % NoteCount;\n    },\n    //return the total number of tones\n    //all note/shape\n    getTotalTones(){\n        return TotalNotes;\n    },\n});\n"
  },
  {
    "path": "js/components/tool-tips.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nAFRAME.registerComponent('tool-tips', {\n    tooltip: null,\n    sprite: null,\n    clock: null,\n    tilesIncX : 6,\n\ttilesIncY : 8,\n\ttotalTiles : 48,\n\ttileTime: 48*4,\n\tcurrentTime:0,\n\tcurrentTile:0,\n\n    schema: {\n        hand: {\n            default: \"right\",\n            type: \"string\"\n        },\n        timeout: {\n            default: 30000,\n            type: \"int\"\n        }\n    },\n\n    init: function() {\n        this.clock = new THREE.Clock();\n    },\n\n    play: function() {\n\n        // adds timeout for tool tip removal\n        let addToolTipTimeout = () => {\n            document.removeEventListener(\"TREE_CREATED\", addToolTipTimeout);\n            setTimeout(() => {\n                removeToolTipsBind();\n            }, this.data.timeout);\n        };\n\n        let removeToolTipsBind = () => {\n            document.removeEventListener(\"TREE_CREATED\", addToolTipTimeout);\n            this.el.removeEventListener('triggerup', removeToolTipsBind);\n            this.removeToolTips();\n        };\n\n        let addToolTips = () => {\n            let loader = new THREE.TextureLoader();\n            loader.load(\"./static/img/toot_tip_\"+this.data.hand+\".png\", (texture) => {\n                this.sprite = texture;\n                this.sprite.wrapS = THREE.RepeatWrapping;\n                this.sprite.wrapT = THREE.RepeatWrapping;\n                this.sprite.repeat.set( 1/this.tilesIncX, 1/this.tilesIncY );\n                let material = new THREE.MeshBasicMaterial({\n                    map: this.sprite,\n                    side: THREE.DoubleSide\n                });\n                let geometry = new THREE.PlaneGeometry( 0.1, 0.05, 1 );\n                this.tooltip = new THREE.Mesh(geometry, material);\n                this.tooltip.rotation.x = THREE.Math.degToRad(-90);\n                this.tooltip.position.x = (this.data.hand===\"right\") ? -0.085 : 0.085;\n                this.tooltip.position.z = 0.075;\n                this.el.object3D.add(this.tooltip);\n            });\n\n            // timeout required due to triggerup firing prematurely\n            setTimeout(() => {\n                this.el.addEventListener('triggerup', removeToolTipsBind);\n                if(this.isTreeCreated()){\n                    addToolTipTimeout();\n                } else {\n                    document.addEventListener(\"TREE_CREATED\", addToolTipTimeout);\n                }\n            });\n\n        };\n\n        // adds tooltips once object 3d is ready\n        let checkForToolTips = () => {\n            if(this.el.object3D) {\n                addToolTips();\n                return;\n            }\n            requestAnimationFrame(checkForToolTips);\n        };\n        checkForToolTips();\n    },\n\n    removeToolTips: function() {\n        if(!this.tooltip) { return; }\n        this.el.object3D.remove(this.tooltip);\n        this.el.removeAttribute(\"tool-tips\");\n    },\n\n    pause: function() {\n\n    },\n\n    tick: function() {\n        if(!this.tooltip) { return; }\n\n        this.currentTime += (1000 * this.clock.getDelta());\n        while (this.currentTime > this.tileTime) {\n            this.currentTime -= this.tileTime;\n            this.currentTile++;\n            if (this.currentTile == this.totalTiles){\n                this.currentTile = 0;\n            }\n\n            this.sprite.offset.x = Math.floor( this.currentTile / this.tilesIncX ) / this.tilesIncX;\n            this.sprite.offset.y = ( this.currentTile % this.tilesIncX ) / this.tilesIncY;\n        }\n    },\n\n    isTreeCreated: function() {\n        return this.el.sceneEl.systems.tree.isTreeCreated;\n    }\n});\n"
  },
  {
    "path": "js/components/touch-color.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { getViewerType, getParameterByName } from '../util'\nimport { ControllerColors } from '../core/colors';\nimport TWEEN from 'tween.js';\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nAFRAME.registerComponent('touch-color', {\n    dependencies: ['tracked-controls', 'brush'],\n    tickTween: null,\n    trackPosStart: null,\n    trackPos: null,\n    prevTrackPos: null,\n    disc: null,\n    rotationChange: 0,\n    totalTicks: 20,\n    ticks: null,\n    lastTone : 4,\n    schema: {\n        isClockwise: {\n            default: true\n        },\n        isTracking: {\n            default: false\n        },\n        trackPos: {\n            default: new THREE.Vector2()\n        },\n        isTrackPadTouched: {\n            default: false\n        },\n        degrees: {\n            default: 0\n        },\n        color: { type: 'string', default: 'red' },\n    },\n\n    init: function() {\n        this.prevTrackPos = new THREE.Vector2(1, 1);\n        setTimeout(() => {\n            let material, geometry, ball;\n\n            ball = this.el.object3D;\n            if (this.el.id === \"avatar\") {\n\n            } else {\n                geometry = new THREE.CylinderGeometry(0.02, 0.02, 0.01, 32);\n                material = new THREE.MeshBasicMaterial({\n                    color: this.getColor()\n                });\n                this.disc = new THREE.Mesh(geometry, material);\n                this.disc.name = \"disc\";\n\n                // this.disc.rotation.x = THREE.Math.degToRad( 6 );\n                this.disc.position.x = 0;\n                this.disc.position.y = 0.001;\n                this.disc.position.z = 0.0728 - 0.025;\n\n                let tick, theta;\n                this.ticks = [];\n                for (let i = 0; i < this.totalTicks; i++) {\n                    geometry = new THREE.CylinderGeometry(0.001, 0.001, 0.011, 12);\n                    material = new THREE.MeshBasicMaterial({\n                        color: 0xFFFFFF\n                    });\n                    tick = new THREE.Mesh(geometry, material);\n                    theta = i / this.totalTicks * Math.PI * 2;\n                    tick.position.x = Math.cos(theta) * 0.017;\n                    tick.position.z = Math.sin(theta) * 0.017;\n                    this.ticks.push(tick);\n                    this.disc.add(tick);\n\n                }\n\n                ball.add(this.disc);\n            }\n\n\n        }, 1);\n\n        this.el.addEventListener(\"componentchanged\", function(e) {\n            if (e.detail.name === \"grab-move\") {\n                //if the attribute changed\n                if (e.detail.oldData.grabbing !== e.detail.newData.grabbing) {\n                    // reset the rotation detail\n                    this.rotationChange = 0;\n                }\n            }\n        });\n        document.addEventListener(\"SPHERE_TEXTURE_LOADED\", (event) => {\n            this.update();\n        });\n    },\n\n    play: function() {\n        let el = this.el;\n\n        if (this.el.id === \"avatar\" && getParameterByName('reticle')==='true') {\n            document.addEventListener('keyup', this.onKeyUp.bind(this));\n            document.addEventListener('keydown', this.onKeyDown.bind(this));\n        }\n\n        el.addEventListener('trackpaddown', this.onTrackPadTouchStart.bind(this));\n        el.addEventListener('trackpadup', this.onTrackPadTouchEnd.bind(this));\n\n        el.addEventListener('touchstart', this.onTrackPadTouchStart.bind(this));\n        el.addEventListener('touchend', this.onTrackPadTouchEnd.bind(this));\n        el.addEventListener('axismove', this.onTrackPadMove.bind(this));\n\n    },\n\n    pause: function() {\n        let el = this.el;\n        if (this.el.id === \"avatar\" && getParameterByName('reticle')==='true') {\n            document.removeEventListener('keyup', this.onKeyUp);\n            document.removeEventListener('keydown', this.onKeyDown);\n        }\n        el.removeEventListener('trackpaddown', this.onTrackPadTouchStart);\n        el.removeEventListener('trackpadup', this.onTrackPadTouchEnd);\n        el.removeEventListener('touchstart', this.onTrackPadTouchStart);\n        el.removeEventListener('touchend', this.onTrackPadTouchEnd);\n        el.removeEventListener('axismove', this.onTrackPadMove);\n    },\n\n    // 57, 48\n    onKeyUp: function(event) {\n        switch (event.keyCode) {\n            // close bracket\n            case 32:\n                // document.dispatchEvent(new Event(\"TRACKPAD_UP\"));\n                break;\n        }\n    },\n\n    onKeyDown: function(event) {\n        switch (event.keyCode) {\n            // <\n            case 188:\n                this.selectSound(-0.03);\n                break;\n                // >\n            case 190:\n                // this.rotateSound(1);\n                this.selectSound(0.03);\n                break;\n        }\n\n    },\n\n    selectSound: function(increment) {\n        let grabComponent = getComponentProperty(this.el, \"grab-move\");\n        if (!grabComponent.isNearSphere) {\n            return;\n        }\n        this.rotationChange += increment;\n        const incrementAmount = 0.02;\n        if (Math.abs(this.rotationChange) > incrementAmount) {\n            this.rotationChange = this.rotationChange % incrementAmount;\n\n            grabComponent.closestSphere.components.ball.note.setRotationIncrement(this.rotationChange > 0);\n\n            // move the tone value\n            let totalNotes = this.el.sceneEl.components.palette.totalNotes();\n            let tone = grabComponent.closestSphere.getAttribute(\"tone\");\n            tone = this.rotationChange > 0 ? tone + 1 : tone - 1;\n            tone = tone % (totalNotes);\n            tone = (tone<0) ? (totalNotes - 1) : tone;\n            this.lastTone = tone;\n            grabComponent.closestSphere.setAttribute(\"tone\", tone);\n        }\n    },\n\n    onTrackPadTouchStart: function(event) {\n        this.trackPosStart = null;\n        this.data.isTrackPadTouched = true;\n    },\n\n    onTrackPadTouchEnd: function(event) {\n        this.trackPosStart = null;\n        this.data.isTrackPadTouched = false;\n    },\n\n    onTrackPadMove: function(event) {\n        // HACK - trackpad move is firing on trigger.x = 0 fixes this\n        if (this.data.isTrackPadTouched === false || event.detail.axis[0] === 0) {\n            return;\n        }\n        this.data.trackPos.fromArray(event.detail.axis, 0);\n        if (this.el.hasAttribute(\"vive-controls\")) {\n            this.data.trackPos.y *= -1;\n        }\n        this.data.trackPos.normalize();\n\n        this.data.isTracking = (this.data.trackPos.distanceToSquared(this.prevTrackPos) > 0.001);\n\n        if (!this.trackPosStart) {\n            this.trackPosStart = this.data.trackPos.clone();\n        }\n\n        let radiansA = Math.atan2(this.data.trackPos.y, this.data.trackPos.x);\n        let radiansB = Math.atan2(this.trackPosStart.y, this.data.trackPos.x);\n        this.data.degrees = Math.abs((radiansA - radiansB) * 180 / Math.PI);\n\n        let cross = (this.prevTrackPos.x * this.data.trackPos.y) - (this.prevTrackPos.y * this.data.trackPos.x);\n\n        if (cross !== 0) {\n            this.data.isClockwise = (cross > 0);\n        }\n\n        this.prevTrackPos.copy(this.data.trackPos);\n\n        if (!this.data.isTracking ||\n            !this.data.isTrackPadTouched\n        ) {\n            return;\n        }\n\n        this.selectSound(0.003 * (this.data.isClockwise ? 1 : -1));\n\n        this.data.trackPos.multiplyScalar(0.1);\n\n        if (this.data.trackPos.lengthSq() > 0.005) {\n\n            let angle = this.data.trackPos.angle() * (180 / Math.PI);\n            let tickSeed = Math.floor((angle / 360) * this.totalTicks);\n            let tick = this.ticks[tickSeed];\n\n            let scalar = {\n                scale: 2\n            };\n            this.tickTween = new TWEEN.Tween({\n                    scale: 0\n                })\n                .to({\n                    scale: 1\n                }, 250)\n                .onUpdate((percentage) => {\n                    tick.scale.x = 1 - percentage + 1;\n                    tick.scale.z = 1 - percentage + 1;\n                });\n            this.tickTween.start();\n        }\n\n    },\n\n    tick: function() {\n\n    },\n\n    update: function( oldData ) {\n        if ( !this.disc ) { return; }\n        this.disc.material.color = this.getColor();\n\t},\n\n    getColor: function() {\n        return new THREE.Color( this.el.sceneEl.components.palette.controllerColors()[1] );\n    }\n});\n\n\nfunction tweenUpdate() {\n    requestAnimationFrame(tweenUpdate);\n    TWEEN.update();\n}\ntweenUpdate();\n"
  },
  {
    "path": "js/components/tree.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { ShadowColor } from '../core/colors';\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nconst stringify = AFRAME.utils.coordinates.stringify;\n\nAFRAME.registerSystem('tree', {\n    tree: null,\n    treeTimeOut:null,\n    isTreeCreated:false,\n\n    schema: {\n\n    },\n\n    init: function() {\n\n        document.addEventListener(\"TREE_CREATED\", (event) => {\n            setTimeout(() => {\n                let balls = tree.getElementsByClassName(\"selectable\");\n            }, 100);\n        });\n\n        document.addEventListener(\"ON_TRIGGER_EMPTY_SPACE\", (event) => {\n            let newBallData = {\n                id: \"TEMP_NEW_SPHERE_ID\",\n                position: stringify(event.detail.controllerPos),\n                hand: event.detail.hand,\n                tone: event.detail.lastTone\n            };\n            this.createSphere(newBallData);\n            ga('send', 'event', \"interaction\", \"create\");\n        }, false);\n\n        document.addEventListener(\"ON_DELETE\", (event) => {\n            this.deleteSphere(event.detail.closestEntity);\n            ga('send', 'event', \"interaction\", \"delete\");\n        }, false);\n\n        this.createTree();\n    },\n\n    createTree: function() {\n        this.tree = document.createElement('a-entity');\n        this.tree.id = \"tree\";\n        setComponentProperty(this.tree, \"position\",\"0 0 0\");\n        this.sceneEl.appendChild(this.tree);\n    },\n\n    createSphere: function(ballData) {\n        if (!ballData) throw new Error(\"No ball data\");\n\n        let entity = document.createElement('a-entity');\n        entity.id = ballData.id;\n        entity.className = \"ball\";\n        entity.className += \" selectable\";\n        this.tree.appendChild(entity);\n        setComponentProperty(entity, \"ball\", {hand:ballData.hand});\n        setComponentProperty(entity, \"tone\", ballData.tone);\n        setComponentProperty(entity, \"position\", ballData.position);\n        setComponentProperty(entity, \"visible\", ballData.visible);\n        setComponentProperty(entity, 'smooth-motion', 'amount:3');\n        setComponentProperty(entity, 'ga', true);\n\n        // setComponentProperty(entity, \"mixin\", \"obj-ball\");\n        setComponentProperty(entity, \"copresence\", {uuid:ballData.uuid});\n\n        if(!this.isTreeCreated){\n            clearTimeout(this.treeTimeOut);\n            this.treeTimeOut = setTimeout(() => {\n                this.isTreeCreated = true;\n                document.dispatchEvent(new Event(\"TREE_CREATED\"));\n            },0);\n        }\n    },\n\n    deleteSphere: function(ballData) {\n        let entity = document.querySelector(\"#\" + ballData.id);\n        if (!entity) {\n            return;\n        }\n\n        let component = getComponentProperty(entity, \"ball\");\n\n        // remove ball\n        setTimeout(() => {\n            this.tree.removeChild(entity);\n        }, 0);\n    },\n\n    truncateDecimals: function(number, digits) {\n        let multiplier = Math.pow(10, digits),\n            adjustedNum = number * multiplier,\n            truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);\n        return truncatedNum / multiplier;\n    },\n\n    switchRoom:function(){\n        this.sceneEl.systems[\"copresence-server\"].switchRoom();\n    },\n    clear: function(){\n\n        let tree = document.querySelector(\"#tree\");\n        let balls = tree.getElementsByClassName(\"selectable\");\n\n        // delete balls on timer\n        let setDeleteTimer = (ball, timer) => {\n            setTimeout((event) => {\n                this.deleteSphere(ball);\n            },timer);\n        };\n        for(let i=0; i<balls.length; i++){\n            let ball = balls[i];\n            setDeleteTimer(ball, (balls.length-i)*1 + 0);\n        }\n        this.isTreeCreated = false;\n\n    },\n    getTreeCreated: function(){\n        return this.isTreeCreated;\n    }\n});\n"
  },
  {
    "path": "js/components/wasd-boundaries.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\n\nAFRAME.registerComponent( 'wasd-boundaries', {\n\n    schema: {\n        maxRadius: {default: 5.0, type: 'float'},\n    },\n\n    init: function() {\n        document.addEventListener(\"INTRO_COMPLETED\", () => {\n            let radius = this.data.maxRadius;\n            this.el.addEventListener(\"componentchanged\", event => {\n                if (event.detail.name === \"position\") {\n                    let position = event.detail.newData;\n                    if (position.z > radius) {\n                        position.z = radius;\n                    }\n                    if (position.z < -radius) {\n                        position.z = -radius;\n                    }\n\n                    if (position.x > radius) {\n                        position.x = radius;\n                    }\n                    if (position.x < -radius) {\n                        position.x = -radius;\n                    }\n\n                    setComponentProperty(this.el, \"position\", position);\n                }\n            });\n        });\n    },\n\n    play: function() {\n\n    },\n\n    pause: function() {\n\n    },\n});\n"
  },
  {
    "path": "js/core/color-set.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nexport class ColorSet {\n\n\tconstructor( background, idle, active, stem ) {\n\t\tthis.background = new THREE.Color( background );\n\t\tthis.idle = new THREE.Color( idle );\n\t\tthis.active = new THREE.Color( active );\n\t\tthis.stem = new THREE.Color( stem );\n\t}\n\n\tsetUniforms( material ) {\n\t\tmaterial.uniforms.bgColor.value = this.background;\n        material.uniforms.idleColor.value = this.idle;\n        material.uniforms.activeColor.value = this.active;\n\t}\n} "
  },
  {
    "path": "js/core/colors.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { ColorSet } from './color-set';\n\nconst BallColors = {\n\n    blue: [\n        //              bg       idle     active    stem\n        //spheres -  yellow to orange\n        new ColorSet( 0xFFC362, 0xFFA544, 0xFFE180, 0xFFA544 ),\n        new ColorSet( 0xFFB564, 0xFF9746, 0xFFD382, 0xFF9746 ),\n        new ColorSet( 0xFFA765, 0xFF8947, 0xFFC583, 0xFF8947 ),\n        new ColorSet( 0xFF9A67, 0xFF7C49, 0xFFB885, 0xFF7C49 ),\n        new ColorSet( 0xFF8C68, 0xFF6E4A, 0xFFAA86, 0xFF6E4A ),\n        new ColorSet( 0xFF7E6A, 0xFF604C, 0xFF9C88, 0xFF604C ),\n\n        //squares - blue to green\n        new ColorSet( 0x49A6E5, 0x308DCC, 0x6CC9FF, 0x308DCC ),\n        new ColorSet( 0x3AAED3, 0x2195BA, 0x5DD1F6, 0x2195BA ),\n        new ColorSet( 0x2CB7C1, 0x139EA8, 0x4FDAE4, 0x048F99 ),\n        new ColorSet( 0x1DBFAE, 0x04A695, 0x40E2D1, 0x04A695 ),\n        new ColorSet( 0x0FC89C, 0x00AF83, 0x32EBBF, 0x00AF83 ),\n        new ColorSet( 0x00D08A, 0x00B771, 0x23F3AD, 0x00B771 ),\n\n        //triangles - purple to light blue\n        new ColorSet( 0xAC44C8, 0x932AAF, 0xDE76FA, 0x982FB4 ),\n        new ColorSet( 0xA258D3, 0x893FBA, 0xD48AFF, 0x8E44BF ),\n        new ColorSet( 0x986DDE, 0x7F54C5, 0xCA9FFF, 0x8459CA ),\n        new ColorSet( 0x8D81E8, 0x7468CF, 0xBFB3FF, 0x796DD4 ),\n        new ColorSet( 0x8396F3, 0x6A7DDA, 0xB5C8FF, 0x6F82DF ),\n        new ColorSet( 0x79AAFE, 0x6091E5, 0xABDCFF, 0x6596EA ),\n    ],\n    red: [\n        //              bg       idle     active    stem\n        //spheres -  yellow to orange\n        new ColorSet( 0xFFC362, 0xFFA544, 0xFFE180, 0xFFA544 ),\n        new ColorSet( 0xFFB564, 0xFF9746, 0xFFD382, 0xFF9746 ),\n        new ColorSet( 0xFFA765, 0xFF8947, 0xFFC583, 0xFF8947 ),\n        new ColorSet( 0xFF9A67, 0xFF7C49, 0xFFB885, 0xFF7C49 ),\n        new ColorSet( 0xFF8C68, 0xFF6E4A, 0xFFAA86, 0xFF6E4A ),\n        new ColorSet( 0xFF7E6A, 0xFF604C, 0xFF9C88, 0xFF604C ),\n\n        //squares - blue to green\n        new ColorSet( 0x49A6E5, 0x308DCC, 0x6CC9FF, 0x308DCC ),\n        new ColorSet( 0x3AAED3, 0x2195BA, 0x5DD1F6, 0x2195BA ),\n        new ColorSet( 0x2CB7C1, 0x139EA8, 0x4FDAE4, 0x048F99 ),\n        new ColorSet( 0x1DBFAE, 0x04A695, 0x40E2D1, 0x04A695 ),\n        new ColorSet( 0x0FC89C, 0x00AF83, 0x32EBBF, 0x00AF83 ),\n        new ColorSet( 0x00D08A, 0x00B771, 0x23F3AD, 0x00B771 ),\n\n        //triangles - purple to light blue\n        new ColorSet( 0xAC44C8, 0x932AAF, 0xDE76FA, 0x982FB4 ),\n        new ColorSet( 0xA258D3, 0x893FBA, 0xD48AFF, 0x8E44BF ),\n        new ColorSet( 0x986DDE, 0x7F54C5, 0xCA9FFF, 0x8459CA ),\n        new ColorSet( 0x8D81E8, 0x7468CF, 0xBFB3FF, 0x796DD4 ),\n        new ColorSet( 0x8396F3, 0x6A7DDA, 0xB5C8FF, 0x6F82DF ),\n        new ColorSet( 0x79AAFE, 0x6091E5, 0xABDCFF, 0x6596EA ),\n\n    ],\n\n    green: [\n        //              bg       idle     active    stem\n        //spheres -  yellow to orange\n        new ColorSet( 0xFFC362, 0xFFA544, 0xFFE180, 0xFFA544 ),\n        new ColorSet( 0xFFB564, 0xFF9746, 0xFFD382, 0xFF9746 ),\n        new ColorSet( 0xFFA765, 0xFF8947, 0xFFC583, 0xFF8947 ),\n        new ColorSet( 0xFF9A67, 0xFF7C49, 0xFFB885, 0xFF7C49 ),\n        new ColorSet( 0xFF8C68, 0xFF6E4A, 0xFFAA86, 0xFF6E4A ),\n        new ColorSet( 0xFF7E6A, 0xFF604C, 0xFF9C88, 0xFF604C ),\n\n        //squares - blue to green\n        new ColorSet( 0x49A6E5, 0x308DCC, 0x6CC9FF, 0x308DCC ),\n        new ColorSet( 0x3AAED3, 0x2195BA, 0x5DD1F6, 0x2195BA ),\n        new ColorSet( 0x2CB7C1, 0x139EA8, 0x4FDAE4, 0x048F99 ),\n        new ColorSet( 0x1DBFAE, 0x04A695, 0x40E2D1, 0x04A695 ),\n        new ColorSet( 0x0FC89C, 0x00AF83, 0x32EBBF, 0x00AF83 ),\n        new ColorSet( 0x00D08A, 0x00B771, 0x23F3AD, 0x00B771 ),\n\n        //triangles - purple to light blue\n        new ColorSet( 0xAC44C8, 0x932AAF, 0xDE76FA, 0x982FB4 ),\n        new ColorSet( 0xA258D3, 0x893FBA, 0xD48AFF, 0x8E44BF ),\n        new ColorSet( 0x986DDE, 0x7F54C5, 0xCA9FFF, 0x8459CA ),\n        new ColorSet( 0x8D81E8, 0x7468CF, 0xBFB3FF, 0x796DD4 ),\n        new ColorSet( 0x8396F3, 0x6A7DDA, 0xB5C8FF, 0x6F82DF ),\n        new ColorSet( 0x79AAFE, 0x6091E5, 0xABDCFF, 0x6596EA ),\n\n    ],\n\n};\n\nconst ControllerColors = {\n    //          bg       grain\n    blue:  [ 0x5396f2, 0x5261f2 ],\n    red:   [ 0xec4c4e, 0xc14040 ],\n    green: [ 0x93cbc4, 0x58a868 ]\n};\n\nconst BgTreeColors = [ 0x827193, 0xa08293, 0xc59498 ];\n\nconst EnvColors = {\n    floor: 0xF79F99,\n    fog: 0xe9a69a\n};\n\nconst ShadowColor = 0xdf978b;\n\nconst HeadsetColor = 0xE8E8E8;\nconst HeadsetShadow = 0xAFAFAF;\n\nexport { BallColors, ControllerColors, BgTreeColors, EnvColors, ShadowColor, HeadsetColor, HeadsetShadow };\n"
  },
  {
    "path": "js/core/instrument.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport Tone from 'tone';\n\n\nexport class Instrument {\n\n\tconstructor( data ) {\n\t\tconst folder = data.name;\n\t\tconst urls = {};\n\t\tconst filetype = Tone.Buffer.supportsType('mp3') ? 'mp3' : 'ogg';\n\n\t\tfor (let i = 0; i < data.noteCount; i++){\n\t\t\turls[i] = `static/audio/${folder}/${i}.${filetype}`;\n\t\t}\n\n\t\tthis._buffers = new Tone.Buffers(urls);\n\n\t\tthis.color = data.color;\n\t\tthis.shape = data.shape;\n\t\tthis.output = new Tone.Gain().toMaster();\n\t\tthis.output.gain.value = 2;\n\t}\n\n\ttrigger(time, tone, velocity, x, y, z){\n\t\tif (this._buffers.loaded){\n\t\t\tconst source = this._createSource(time, tone, velocity);\n\t\t\tconst panner = this._createPanner(x, y, z);\n\t\t\tsource.connect(panner);\n\t\t}\n\t}\n\n\t_createPanner(x, y, z){\n\t\tconst panner = Tone.context.createPanner();\n\t\tpanner.rolloffFactor = 2;\n\t\tpanner.setPosition(x, y, z);\n\t\tpanner.connect(this.output);\n\t\treturn panner;\n\t}\n\n\t_createSource(time, note, velocity){\n\t\tconst buffer = this._buffers.get(note);\n\t\tconst source = new Tone.BufferSource(buffer);\n\t\tsource.start(time, 0, undefined, velocity * 0.5, 0.01);\n\t\treturn source;\n\t}\n\n\n\tdispose(){\n\t\tthis.output.dispose();\n\t}\n}\n"
  },
  {
    "path": "js/core/instruments.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { Instrument } from './instrument';\n\nexport const NoteCount = 6;\n\nexport const Instruments = [\n\n    new Instrument({\n        name: 'percussion',\n        color: 'blue',\n        shape: 'circles',\n        noteCount : NoteCount\n    }),\n\n    new Instrument({\n        name: 'marimba',\n        color: 'red',\n        shape: 'squares',\n        noteCount : NoteCount\n    }),\n\n    new Instrument({\n        name: 'voice',\n        color: 'green',\n        shape: 'triangles',\n        noteCount : NoteCount\n    }),\n];\n\nexport const InstrumentCount = Instruments.length;\n\nexport const TotalNotes = NoteCount * InstrumentCount;\n"
  },
  {
    "path": "js/core/shape-data.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst TONE_CHANNEL_MAP = [\n\tnew THREE.Vector3( 1, 0, 0 ), \n\tnew THREE.Vector3( 1, 0, 0 ),\n\tnew THREE.Vector3( 0, 1, 0 ),\n\tnew THREE.Vector3( 0, 0, 1 ),\n\tnew THREE.Vector3( 1, 0, 0 ),\n\tnew THREE.Vector3( 0, 1, 0 ),\n\tnew THREE.Vector3( 0, 0, 1 )\n];\n\nexport class ShapeData {\n\n\tconstructor( layout, repeats, tone ) {\n\t\tthis.layout = layout;\n\t\tthis.repeats = repeats;\n\t\tthis.tone = tone;\n\t}\n\n\tsetUniforms( material ) {\n\t\tmaterial.uniforms.spriteLayout.value = this.layout;\n\t\tmaterial.uniforms.spriteRepeat.value = this.repeats;\n\t\tmaterial.uniforms.spriteChannel.value = TONE_CHANNEL_MAP[ this.tone ];\n\t\tmaterial.uniforms.use567.value = this.tone > 3 ? 1.0 : 0.0;\n\t}\n}"
  },
  {
    "path": "js/core/shapes.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { ShapeData } from './shape-data';\n\nexport const Shapes = {\n\n\t'circles': [\n\t\tnew ShapeData( new THREE.Vector2( 8, 4 ), new THREE.Vector2( 4, 1 ), 0 ),\n\t\tnew ShapeData( new THREE.Vector2( 8, 4 ), new THREE.Vector2( 4, 1 ), 1 ),\n\t\tnew ShapeData( new THREE.Vector2( 8, 4 ), new THREE.Vector2( 4, 1 ), 2 ),\n\t\tnew ShapeData( new THREE.Vector2( 8, 4 ), new THREE.Vector2( 3, 1 ), 3 ),\n\t\tnew ShapeData( new THREE.Vector2( 8, 4 ), new THREE.Vector2( 3, 1 ), 4 ),\n\t\tnew ShapeData( new THREE.Vector2( 6, 6 ), new THREE.Vector2( 2, 1 ), 5 ),\n\t\tnew ShapeData( new THREE.Vector2( 6, 6 ), new THREE.Vector2( 2, 1 ), 6 ) \n\t],\n\n\t'triangles': [\n\t\tnew ShapeData( new THREE.Vector2( 9, 4 ), new THREE.Vector2( 4, 1 ), 0 ),\n\t\tnew ShapeData( new THREE.Vector2( 9, 4 ), new THREE.Vector2( 4, 1 ), 1 ),\n\t\tnew ShapeData( new THREE.Vector2( 9, 4 ), new THREE.Vector2( 4, 1 ), 2 ),\n\t\tnew ShapeData( new THREE.Vector2( 6, 6 ), new THREE.Vector2( 3, 1 ), 3 ),\n\t\tnew ShapeData( new THREE.Vector2( 7, 5 ), new THREE.Vector2( 3, 1 ), 4 ),\n\t\tnew ShapeData( new THREE.Vector2( 7, 5 ), new THREE.Vector2( 3, 1 ), 5 ),\n\t\tnew ShapeData( new THREE.Vector2( 6, 6 ), new THREE.Vector2( 3, 1 ), 6 ) \n\t],\n\n\t'squares': [\n\t\tnew ShapeData( new THREE.Vector2( 2, 17 ), new THREE.Vector2( 1, 4 ), 0 ),\n\t\tnew ShapeData( new THREE.Vector2( 2, 17 ), new THREE.Vector2( 1, 4 ), 1 ),\n\t\tnew ShapeData( new THREE.Vector2( 3, 11 ), new THREE.Vector2( 1, 3 ), 2 ),\n\t\tnew ShapeData( new THREE.Vector2( 3, 11 ), new THREE.Vector2( 1, 3 ), 3 ),\n\t\tnew ShapeData( new THREE.Vector2( 3, 11 ), new THREE.Vector2( 1, 3 ), 4 ),\n\t\tnew ShapeData( new THREE.Vector2( 3, 11 ), new THREE.Vector2( 1, 2 ), 5 ),\n\t\tnew ShapeData( new THREE.Vector2( 3, 11 ), new THREE.Vector2( 1, 2 ), 6 ) \n\t]\n};"
  },
  {
    "path": "js/index.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\n// debug\nrequire('./util/trace.js');\n\nrequire('./util/browserCheck.js');\n\n// core\nrequire('aframe');\nrequire('./ascene.js');\n\nrequire('webvr-ui');\nrequire('./components/copresence-server.js');\n\nrequire('./components/headset-material.js');\nrequire('./components/bg-tree-ring-material.js');\nrequire('../third_party/aframe-daydream-controller-component/daydream-controller.js');\nrequire('./components/daydream-manager.js');\nrequire('./components/daydream-pointer.js');\nrequire('./components/fake-light.js');\nrequire('./components/tool-tips.js');\nrequire('./components/controller-material.js');\n\n// utility\nrequire('./components/quaternion.js');\nrequire('./components/smooth-motion.js');\nrequire('./components/controllers.js');\nrequire('./components/haptics.js');\nrequire('./components/ga.js');\n\n// shaders\nrequire( './shaders/bg-tree-shader' );\nrequire( './shaders/circle-shader' );\nrequire( './shaders/ball-shader' );\n\n// tree\nrequire('./components/tree.js');\nrequire('./components/ball.js');\nrequire('./components/background-objects.js');\n\n// interaction\nrequire('./components/gaze.js');\nrequire('./components/grab-move.js');\nrequire('./components/touch-color.js');\nrequire('./components/teleport.js');\nrequire('./components/proximity-check.js');\nrequire('./components/clicker.js');\nrequire('./components/wasd-boundaries.js');\n\n//sound\nrequire('./components/tone.js');\nrequire('./components/palette.js');\nrequire('./components/listener.js');\n\n//the splash screen\nrequire('./splash');\n"
  },
  {
    "path": "js/notes/note-head-cube.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { NoteHead } from './note-head';\nimport { RandomRange3D } from '../util/random-range-3d';\n\nconst CUBE_GEOMETRY = new THREE.BoxGeometry( 0.1, 0.1, 0.1 );\nconst RANDOM_ROTATION = new RandomRange3D(\n\tnew THREE.Euler( -3.14, -3.14, -3.14 ),\n\tnew THREE.Euler( +3.14, +3.14, +3.14 )\n);\n\nexport class NoteHeadCube extends NoteHead {\n\n\tget geometry() {\n\t\treturn CUBE_GEOMETRY;\n\t}\n\n\tget rotation() {\n\t\treturn RANDOM_ROTATION.value;\n\t}\n}"
  },
  {
    "path": "js/notes/note-head-sphere.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { NoteHead } from './note-head';\nimport { RandomRange3D } from '../util/random-range-3d';\n\nconst CUBE_GEOMETRY = new THREE.IcosahedronGeometry( 0.05, 2 );\nconst RANDOM_ROTATION = new RandomRange3D(\n\tnew THREE.Euler( -3.14, -3.14, -3.14 ),\n\tnew THREE.Euler( +3.14, +3.14, +3.14 )\n);\n\nexport class NoteHeadSphere extends NoteHead {\n\n\tget geometry() {\n\t\treturn CUBE_GEOMETRY;\n\t}\n\n\tget rotation() {\n\t\treturn RANDOM_ROTATION.value;\n\t}\n}\n"
  },
  {
    "path": "js/notes/note-head-tetra.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { NoteHead } from './note-head';\nimport { RandomRange3D } from '../util/random-range-3d';\nimport { Tetrahedron } from '../util/tetrahedron';\n\nconst TETRA_GEOMETRY = new Tetrahedron().geometry;\nconst RANDOM_ROTATION = new RandomRange3D(\n\tnew THREE.Euler( -0.3, -0.3, -0.3 ),\n\tnew THREE.Euler( +0.3, +0.3, +0.3 )\n);\n\nexport class NoteHeadTetra extends NoteHead {\n\n\tconstructor( palette, shape ) {\n\t\tsuper( palette, shape );\n\n\t\t// Enable flat shading\n\t\tthis.material.extensions = { derivatives: true };\n\t    this.material.defines = { FLAT_SHADED: true };\n\t}\n\n\tget geometry() {\n\t\treturn TETRA_GEOMETRY;\n\t}\n\n\tget rotation() {\n\t\treturn RANDOM_ROTATION.value;\n\t}\n}\n"
  },
  {
    "path": "js/notes/note-head.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst stringify = AFRAME.utils.coordinates.stringify;\nimport TWEEN from 'tween.js';\n\nexport class NoteHead {\n\n\tconstructor( palette, shape ) {\n\t\tthis.palette = palette;\n\t\tthis.shape = shape;\n\n        this.shader = THREE.BallShader;\n        this.uniforms = THREE.UniformsUtils.clone( this.shader.uniforms );\n        this.material = new THREE.ShaderMaterial({\n        \tuniforms: this.uniforms,\n        \tvertexShader: this.shader.vertexShader,\n        \tfragmentShader: this.shader.fragmentShader\n        });\n\n        this.mesh = new THREE.Mesh( this.geometry, this.material );\n\t}\n\n\tsetTone( value ) {\n\n\t\tthis.tone = value;\n        this.scale = ( 1 - this.tone / ( this.palette.totalNotes() - 1 ) ) * 2 + 1;\n        this.mesh.scale.set( this.scale, this.scale, this.scale );\n\n\t\tsetTimeout( () => {\n\t\t\tlet textureId = Math.floor( this.tone / this.palette.noteCount() );\n\t\t\tlet textureOrder = this.tone % this.palette.noteCount();\n\t\t\tlet textureData = [\"circles\",\"squares\",\"triangles\"][textureId];\n\t\t\tthis.palette.shapePalette( textureData )[ textureOrder ].setUniforms( this.material );\n\t\t\tthis.palette.colorPalette()[ this.tone ].setUniforms( this.material );\n\t        let texture134 = this.palette.textureSprite134( textureId );\n\t        let texture567 = this.palette.textureSprite567( textureId );\n\t        texture134.needsUpdate = true;\n\t        texture567.needsUpdate = true;\n\n\t        this.material.uniforms.map134.value = texture134;\n\t        this.material.uniforms.map567.value = texture567;\n\t        this.material.uniforms.lightPosition.value.copy( this.lightPosition );\n\t\t},0);\n\t}\n\thighlight( event ) {\n\t\tthis.material.uniforms.brightnessAmount.value = 0.2;\n\t}\n\tunHighlight( event ) {\n\t\tthis.material.uniforms.brightnessAmount.value = 0.0;\n\t}\n\thit( event, timeMs ) {\n\t\t// Cancel tween if it already exists\n        if ( this.hitTween ) {\n            this.hitTween.stop();\n        }\n        this.hitPosition = event.detail.controllerPosition;\n        this.offsetPosition = this.mesh.getWorldPosition();\n\n        if(this.hitPosition) {\n            this.offsetPosition.sub(this.hitPosition);\n        }\n\n        this.hitVelocity = (event.detail.velocity===0) ? 1 : event.detail.velocity;\n\n        this.hitTween = new TWEEN.Tween( { t: this.hitVelocity } )\n            .to( { t: 0 }, timeMs )\n\t\t\t.easing( TWEEN.Easing.Elastic.Out )\n            .delay( event.detail.delay * 1000 )\n\t\t\t.onUpdate( (t) => {\n\t\t\t\tthis.updateHitTween( t );\n            })\n\t\t\t.onComplete( () => {\n                this.updateHitTween( 1 );\n            });\n        this.hitTween.start();\n\n\t\tthis.hitFrameTween = new TWEEN.Tween( { t: 0 } )\n            .to( { t: 1 }, timeMs*1.25 )\n\t\t\t.easing( TWEEN.Easing.Linear.None )\n            .delay( event.detail.delay * 1000 )\n\t\t\t.onUpdate( (t) => {\n\t\t\t\tthis.material.uniforms.spriteIndex.value = Math.floor( t * 32 );\n            })\n\t\t\t.onComplete( () => {\n\t\t\t\tthis.material.uniforms.spriteIndex.value = Math.floor( 1 * 32 );\n            });\n        this.hitFrameTween.start();\n\n\t}\n\n\tupdateHitTween( t ) {\n\t\tlet s = ( 1 - t ) * this.hitVelocity + this.scale;\n        this.mesh.scale.set( s, s, s );\n\t\tthis.material.uniforms.activeAmount.value = 1 - t;\n        if(this.hitPosition) {\n            let p = ( 1 - t );\n            this.mesh.position.x = this.offsetPosition.x*p*this.hitVelocity;\n            this.mesh.position.y = this.offsetPosition.y*p*this.hitVelocity;\n        }\n\t}\n\n\tget geometry() { }\n\tget rotation() { }\n}\n"
  },
  {
    "path": "js/notes/note-shadow-cube.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { NoteShadow } from './note-shadow';\nimport { ShadowColor } from '../core/colors';\n\nconst SHADOW_MATERIAL = new THREE.MeshBasicMaterial({\n\tcolor: ShadowColor\n});\n\nexport class NoteShadowCube extends NoteShadow {\n\n\tupdate() {\n\t\tsuper.update();\n\t\tthis.mesh.scale.setY( Number.EPSILON );\n\t}\n\n\tget geometry() {\n\t\tvar cubeGeometry = new THREE.BoxGeometry( 0.1, 0.1, 0.1 );\n\t\tcubeGeometry.applyMatrix( this.headMesh.matrix );\n\t\tcubeGeometry.scale( 0.55, 0.55, 0.55 );\n        return cubeGeometry;\n\t}\n\n\tget material() {\n\t\treturn SHADOW_MATERIAL;\n\t}\n}\n"
  },
  {
    "path": "js/notes/note-shadow-sphere.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { NoteShadow } from './note-shadow';\n\nconst SHADOW_GEOMETRY = new THREE.PlaneGeometry( 0.1, 0.1 );\nconst SHADOW_SHADER = THREE.CircleShader;\nconst SHADOW_UNIFORMS = THREE.UniformsUtils.clone( SHADOW_SHADER.uniforms );\nconst SHADOW_MATERIAL = new THREE.ShaderMaterial({\n\tuniforms: SHADOW_UNIFORMS,\n\tvertexShader: SHADOW_SHADER.vertexShader,\n\tfragmentShader: SHADOW_SHADER.fragmentShader\n});\n\nexport class NoteShadowSphere extends NoteShadow {\n\n\tconstructor( headMesh ) {\n\t\tsuper( headMesh );\n\n\t\tthis.mesh.rotation.x -= Math.PI / 2;\n\t}\n\n\tget geometry() {\n\t\treturn SHADOW_GEOMETRY;\n\t}\n\n\tget material() {\n\t\treturn SHADOW_MATERIAL;\n\t}\n}"
  },
  {
    "path": "js/notes/note-shadow-tetra.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { NoteShadow } from './note-shadow';\nimport { ShadowColor } from '../core/colors';\nimport { Tetrahedron } from '../util/tetrahedron';\n\nconst TETRA_GEOMETRY = new Tetrahedron().geometry;\nconst SHADOW_MATERIAL = new THREE.MeshBasicMaterial({\n\tcolor: ShadowColor\n});\n\nexport class NoteShadowTetra extends NoteShadow {\n\n\tupdate() {\n\t\tsuper.update();\n\t\tthis.mesh.scale.setY( Number.EPSILON );\n\t}\n\n\tget geometry() {\n\t\tvar tetraGeometry = TETRA_GEOMETRY.clone();\n        tetraGeometry.applyMatrix( this.headMesh.matrix );\n\t\treturn tetraGeometry;\n\t}\n\n\tget material() {\n\t\treturn SHADOW_MATERIAL;\n\t}\n}\n"
  },
  {
    "path": "js/notes/note-shadow.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst SHADOW_SIZE = 1.05;\nconst SHADOW_GROUND_CLEARANCE = 0.01;\n\nexport class NoteShadow {\n\n\tconstructor( headMesh, rotation ) {\n\t\tthis.headMesh = headMesh;\n\t\tthis.headMesh.updateMatrix();\n\t\tthis.headMesh.updateMatrixWorld( true );\n\t\tthis.rotation = rotation;\n\t\tthis.mesh = new THREE.Mesh( this.geometry, this.material );\n\t}\n\n\tupdate() {\n\t\tvar worldZeroVector = new THREE.Vector3( 0, 0, 0 );\n        this.mesh.parent.worldToLocal( worldZeroVector );\n        this.mesh.position.setY( worldZeroVector.y + SHADOW_GROUND_CLEARANCE );\n        this.mesh.scale.copy( this.headMesh.scale );\n        this.mesh.scale.multiplyScalar( SHADOW_SIZE );\n\t}\n\n\tget geometry() { }\n\tget material() { }\n}\n"
  },
  {
    "path": "js/notes/note-stem.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst STEM_GEOMETRY = new THREE.CylinderGeometry( 0.005, 0.01, 1, 5 );\n\nexport class NoteStem {\n\n\tconstructor( palette ) {\n\t\tthis.palette = palette;\n\t\tthis.material = new THREE.MeshBasicMaterial();\n\n\t\tvar stemMesh = new THREE.Mesh( STEM_GEOMETRY, this.material );\n\t\tstemMesh.position.y = -0.5;\n\n\t\tthis.mesh = new THREE.Group();\n\t\tthis.mesh.add( stemMesh );\n\t}\n\n\tsetTone( value ) {\n\t\tthis.tone = value;\n\t\tthis.material.color = this.palette.colorPalette()[ this.tone ].idle;\n\t\tthis.material.update();\n\t}\n\n\tupdate() {\n\t\tvar worldZeroVector = new THREE.Vector3( 0, 0, 0 );\n        this.mesh.parent.worldToLocal( worldZeroVector );\n        this.mesh.position.setY( worldZeroVector.y );\n        this.mesh.scale.setY( worldZeroVector.y );\n\t}\n\n\thit() {\n\n\t}\n}\n"
  },
  {
    "path": "js/notes/note.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { NoteHeadSphere } from './note-head-sphere';\nimport { NoteHeadTetra } from './note-head-tetra';\nimport { NoteHeadCube } from './note-head-cube';\nimport { NoteShadowSphere } from './note-shadow-sphere';\nimport { NoteShadowTetra } from './note-shadow-tetra';\nimport { NoteShadowCube } from './note-shadow-cube';\n\nconst HIT_ANIM_MS = 750;\n\nconst SHAPE_NAMES = [ 'sphere', 'cube', 'tetra' ];\n\nconst HEAD_CONSTRUCTORS = {\n    sphere: NoteHeadSphere,\n    tetra: NoteHeadTetra,\n    cube: NoteHeadCube\n};\n\nconst SHADOW_CONSTRUCTORS = {\n    sphere: NoteShadowSphere,\n    tetra: NoteShadowTetra,\n    cube: NoteShadowCube\n};\n\nexport class Note {\n\n    constructor( palette ) {\n        this.group = new THREE.Group();\n        this.palette = palette;\n    }\n\n    setTone( value ) {\n        this.tone = value;\n\n        // Remove old note objects\n        if ( this.head ) this.group.remove( this.head.mesh );\n        if ( this.shadow ) this.group.remove( this.shadow.mesh );\n\n        // Create new head object\n        this.head = new ( HEAD_CONSTRUCTORS[ this.shape ] )( this.palette, this.shape );\n        this.head.name = \"head\";\n        this.head.lightPosition = this.lightPosition;\n        this.head.setTone( this.tone );\n\n        // Store first random rotation value\n        if ( !this.rotation ) {\n            this.rotation = new THREE.Euler();\n            this.rotation.copy( this.head.rotation );\n        }\n\n        // Reset head mesh rotation\n        this.head.mesh.rotation.copy( this.rotation );\n\n        // Create new shadow object\n        this.shadow = new ( SHADOW_CONSTRUCTORS[ this.shape ] )( this.head.mesh, this.rotation );\n\n        // Add 'em up\n        this.group.add( this.head.mesh );\n        this.group.add( this.shadow.mesh );\n    }\n\n    setRotationIncrement(isClockwise=false) {\n        let rotationAxis = new THREE.Vector3(0,1,0);\n        let rotationAmount = Math.PI * 2 * (isClockwise ? 0.03 : -0.03);\n        let deltaMatrix = new THREE.Matrix4();\n        deltaMatrix.makeRotationAxis(rotationAxis, rotationAmount );\n\n        let rotationMatrix = new THREE.Matrix4().makeRotationFromEuler(this.rotation);\n        rotationMatrix.multiplyMatrices(deltaMatrix,rotationMatrix);\n        deltaMatrix.extractRotation(rotationMatrix);\n        this.rotation.setFromRotationMatrix(deltaMatrix);\n\t}\n\n    removeShadow(){\n        if ( this.shadow ) {\n            this.group.remove( this.shadow.mesh );\n            delete this.shadow;\n            this.shadow = null;\n        }\n    }\n    tick() {\n        if(this.shadow) {\n            this.shadow.update();\n        }\n    }\n\n    highlight( event ) {\n        this.head.highlight( event );\n    }\n    unHighlight( event ) {\n        this.head.unHighlight( event );\n    }\n\n    hit( event ) {\n        this.head.hit( event, HIT_ANIM_MS );\n    }\n\n    get shape() {\n        return SHAPE_NAMES[ Math.floor( this.tone / this.palette.noteCount() ) ];\n    }\n}\n"
  },
  {
    "path": "js/orientation-arm-model.js",
    "content": "/*\n * Copyright 2016 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst HEAD_ELBOW_OFFSET = new THREE.Vector3(0.155, -0.465, -0.15);\nconst ELBOW_WRIST_OFFSET = new THREE.Vector3(0, 0, -0.25);\nconst WRIST_CONTROLLER_OFFSET = new THREE.Vector3(0, 0, 0.05);\nconst ARM_EXTENSION_OFFSET = new THREE.Vector3(-0.08, 0.14, 0.08);\n\nconst ELBOW_BEND_RATIO = 0.4; // 40% elbow, 60% wrist.\nconst EXTENSION_RATIO_WEIGHT = 0.4;\n\nconst MIN_ANGULAR_SPEED = 0.61; // 35 degrees per second (in radians).\n\n/**\n * Represents the arm model for the Daydream controller. Feed it a camera and\n * the controller. Update it on a RAF.\n *\n * Get the model's pose using getPose().\n */\nexport default class OrientationArmModel {\n    constructor() {\n        this.isLeftHanded = false;\n\n        // Current and previous controller orientations.\n        this.controllerQ = new THREE.Quaternion();\n        this.lastControllerQ = new THREE.Quaternion();\n\n        // Current and previous head orientations.\n        this.headQ = new THREE.Quaternion();\n\n        // Current head position.\n        this.headPos = new THREE.Vector3();\n\n        // Positions of other joints (mostly for debugging).\n        this.elbowPos = new THREE.Vector3();\n        this.wristPos = new THREE.Vector3();\n\n        // Current and previous times the model was updated.\n        this.time = null;\n        this.lastTime = null;\n\n        // Root rotation.\n        this.rootQ = new THREE.Quaternion();\n\n        // Current pose that this arm model calculates.\n        this.pose = {\n            orientation: new THREE.Quaternion(),\n            position: new THREE.Vector3()\n        };\n    }\n\n    /**\n     * Methods to set controller and head pose (in world coordinates).\n     */\n    setControllerOrientation(quaternion) {\n        this.lastControllerQ.copy(this.controllerQ);\n        this.controllerQ.copy(quaternion);\n    }\n\n    setHeadOrientation(quaternion) {\n\n        this.headQ.copy(quaternion);\n    }\n\n    setHeadPosition(position) {\n        this.headPos.copy(position);\n    }\n\n    setLeftHanded(isLeftHanded) {\n        // TODO(smus): Implement me!\n        this.isLeftHanded = isLeftHanded;\n    }\n\n    /**\n     * Called on a RAF.\n     */\n    update() {\n        this.time = performance.now();\n\n        // If the controller's angular velocity is above a certain amount, we can\n        // assume torso rotation and move the elbow joint relative to the\n        // camera orientation.\n        let headYawQ = this.getHeadYawOrientation_();\n        let timeDelta = (this.time - this.lastTime) / 1000;\n        let angleDelta = this.quatAngle_(this.lastControllerQ, this.controllerQ);\n        let controllerAngularSpeed = angleDelta / timeDelta;\n        if (controllerAngularSpeed > MIN_ANGULAR_SPEED) {\n            // Attenuate the Root rotation slightly.\n            this.rootQ.slerp(headYawQ, angleDelta / 10);\n        } else {\n            this.rootQ.copy(headYawQ);\n        }\n\n        // We want to move the elbow up and to the center as the user points the\n        // controller upwards, so that they can easily see the controller and its\n        // tool tips.\n        let controllerEuler = new THREE.Euler().setFromQuaternion(this.controllerQ, 'YXZ');\n        let controllerXDeg = THREE.Math.radToDeg(controllerEuler.x);\n        let extensionRatio = this.clamp_((controllerXDeg - 11) / (50 - 11), 0, 1);\n\n        // Controller orientation in camera space.\n        let controllerCameraQ = this.rootQ.clone().inverse();\n        controllerCameraQ.multiply(this.controllerQ);\n\n        // Calculate elbow position.\n        let elbowPos = this.elbowPos;\n        elbowPos.copy(this.headPos).add(HEAD_ELBOW_OFFSET);\n        let elbowOffset = new THREE.Vector3().copy(ARM_EXTENSION_OFFSET);\n        elbowOffset.multiplyScalar(extensionRatio);\n        elbowPos.add(elbowOffset);\n\n        // Calculate joint angles. Generally 40% of rotation applied to elbow, 60%\n        // to wrist, but if controller is raised higher, more rotation comes from\n        // the wrist.\n        let totalAngle = this.quatAngle_(controllerCameraQ, new THREE.Quaternion());\n        let totalAngleDeg = THREE.Math.radToDeg(totalAngle);\n        let lerpSuppression = 1 - Math.pow(totalAngleDeg / 180, 4); // TODO(smus): ???\n\n        let elbowRatio = ELBOW_BEND_RATIO;\n        let wristRatio = 1 - ELBOW_BEND_RATIO;\n        let lerpValue = lerpSuppression *\n            (elbowRatio + wristRatio * extensionRatio * EXTENSION_RATIO_WEIGHT);\n\n        let wristQ = new THREE.Quaternion().slerp(controllerCameraQ, lerpValue);\n        let invWristQ = wristQ.inverse();\n        let elbowQ = controllerCameraQ.clone().multiply(invWristQ);\n\n        // Calculate our final controller position based on all our joint rotations\n        // and lengths.\n        /*\n         position_ =\n         root_rot_ * (\n         controller_root_offset_ +\n         2:      (arm_extension_ * amt_extension) +\n         1:      elbow_rot * (kControllerForearm + (wrist_rot * kControllerPosition))\n         );\n         */\n        let wristPos = this.wristPos;\n        wristPos.copy(WRIST_CONTROLLER_OFFSET);\n        wristPos.applyQuaternion(wristQ);\n        wristPos.add(ELBOW_WRIST_OFFSET);\n        wristPos.applyQuaternion(elbowQ);\n        wristPos.add(this.elbowPos);\n\n        let offset = new THREE.Vector3().copy(ARM_EXTENSION_OFFSET);\n        offset.multiplyScalar(extensionRatio);\n\n        let position = new THREE.Vector3().copy(this.wristPos);\n        position.add(offset);\n        position.applyQuaternion(this.rootQ);\n\n        let orientation = new THREE.Quaternion().copy(this.controllerQ);\n\n        // Set the resulting pose orientation and position.\n        this.pose.orientation.copy(orientation);\n        this.pose.position.copy(position);\n\n        this.lastTime = this.time;\n    }\n\n    /**\n     * Returns the pose calculated by the model.\n     */\n    getPose() {\n        return this.pose;\n    }\n\n    /**\n     * Debug methods for rendering the arm model.\n     */\n    getForearmLength() {\n        return ELBOW_WRIST_OFFSET.length();\n    }\n\n    getElbowPosition() {\n        let out = this.elbowPos.clone();\n        return out.applyQuaternion(this.rootQ);\n    }\n\n    getWristPosition() {\n        let out = this.wristPos.clone();\n        return out.applyQuaternion(this.rootQ);\n    }\n\n    getHeadYawOrientation_() {\n        let headEuler = new THREE.Euler().setFromQuaternion(this.headQ, 'YXZ');\n        headEuler.x = 0;\n        headEuler.z = 0;\n        let destinationQ = new THREE.Quaternion().setFromEuler(headEuler);\n        return destinationQ;\n    }\n\n    clamp_(value, min, max) {\n        return Math.min(Math.max(value, min), max);\n    }\n\n    quatAngle_(q1, q2) {\n        let vec1 = new THREE.Vector3(0, 0, -1);\n        let vec2 = new THREE.Vector3(0, 0, -1);\n        vec1.applyQuaternion(q1);\n        vec2.applyQuaternion(q2);\n        return vec1.angleTo(vec2);\n    }\n}\n"
  },
  {
    "path": "js/shaders/ball-shader.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { CommonUniforms, ToonVertex, ToonFragUniforms, ToonFragCommon, ToonFragLighting } from './shader-chunks';\n\nTHREE.BallShader = {\n\n\tuniforms: THREE.UniformsUtils.merge([ CommonUniforms,\n\t\t{\n\t\t\tmap134: { type: 't' },\n\t\t\tmap567: { type: 't' },\n\t\t\tuse567: { value: 0.0 },\n\n\t\t\tspriteLayout:  { value: new THREE.Vector2( 8, 4 ) },\n\t\t\tspriteRepeat:  { value: new THREE.Vector2( 5, 1 ) },\n\t\t\tspriteChannel: { value: new THREE.Vector3( 1, 0, 0 ) },\n\t\t\tspriteIndex:   { value: 0 },\n\n\t\t\tbgColor: \t   { value: new THREE.Color( 'rgb( 53, 101, 190 )' ) },\n\t\t\tidleColor: \t   { value: new THREE.Color( 'rgb( 0, 55, 182 )' ) },\n\t\t\tactiveColor:   { value: new THREE.Color( 'rgb( 0, 130, 237 )' ) },\n\t\t\tactiveAmount:  { value: 0 },\n\t\t\tbrightnessAmount:  { value: 0.0 }\n\t\t}\n\t]),\n\n\tvertexShader: ToonVertex,\n\n\tfragmentShader: [\n\n\t\tToonFragUniforms,\n\n\t\t'uniform sampler2D map134;',\n\t\t'uniform sampler2D map567;',\n\t\t'uniform float use567;',\n\n\t\t'uniform vec2 spriteLayout;',\n\t\t'uniform vec2 spriteRepeat;',\n\t\t'uniform vec3 spriteChannel;',\n\t\t'uniform float spriteIndex;',\n\n\t\t'uniform vec3 bgColor;',\n\t\t'uniform vec3 idleColor;',\n\t\t'uniform vec3 activeColor;',\n\t\t'uniform float activeAmount;',\n\t\t'uniform float brightnessAmount;',\n\n\t\t'void main() {',\n\n\t\t\tToonFragLighting,\n\n\t\t\t'vec2 uvSpriteOffset = vec2( 0.0 );',\n\t\t\t'uvSpriteOffset.x =  floor( mod( spriteIndex, spriteLayout.x ) );',\n\t\t\t'uvSpriteOffset.y = -floor( spriteIndex / spriteLayout.x ) - 1.0;',\n\t\t\t'uvSpriteOffset /= spriteLayout;',\n\n\t\t\t'vec2 uvSize = 1.0 / spriteLayout;',\n\t\t\t'vec2 vUvSprite = vUv;',\n\t\t\t'vUvSprite.y = vUvSprite.y + spriteLayout.y - 1.0;',\n\t\t\t'vUvSprite = mod( vUvSprite * uvSize * spriteRepeat, uvSize ) + uvSpriteOffset;',\n\n\t\t\t'vec3 tex134 = texture2D( map134, vUvSprite ).rgb;',\n\t\t\t'vec3 tex567 = texture2D( map567, vUvSprite ).rgb;',\n\t\t\t'vec3 a = mix( tex134, tex567, use567 );',\n\t\t\t'vec3 color = mix( idleColor, activeColor, activeAmount );',\n\t\t\t'diffuseColor = vec4( mix( bgColor, color, length( a * spriteChannel ) ) + brightnessAmount, 1.0 );',\n\n\t\t\tToonFragCommon,\n\n\t\t'}'\n\t].join( '\\n' )\n};\n"
  },
  {
    "path": "js/shaders/bg-tree-shader.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { BgTreeColors, ShadowColor } from '../core/colors';\n\nTHREE.BGTreeShader = {\n\t\n\tuniforms: {\n\t\tmap:    \t { type: 't' },\n\t\tfogColor: \t { type: 'c' },\n\t\tfogDensity:  { value: 0 },\n\t\tcolor:  \t { type: 'c', value: new THREE.Color( BgTreeColors[ 0 ] ) },\n\t\tshadowColor: { type: 'c', value: new THREE.Color( ShadowColor ) }\n\t},\n\n\tvertexShader: [\n\n\t\t'varying vec2 vUV;',\n\t\t'varying float fogDepth;',\n\n\t\t'void main() {',\n\t\t\t'vUV = uv;',\n\t\t\t'vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );',\n\t\t\t'fogDepth = -mvPosition.z;',\n\t\t\t'gl_Position = projectionMatrix * mvPosition;',\n\t\t'}'\n\n\t].join( '\\n' ),\n\n\tfragmentShader: [\n\n\t\t'#define LOG2 1.442695',\n\n\t\t'uniform vec3 color;',\n\t\t'uniform vec3 shadowColor;',\n\t\t'uniform sampler2D map;',\n\t\t'uniform float fogDensity;',\n\t\t'uniform vec3 fogColor;',\n\n\t\t'varying float fogDepth;',\n\t\t'varying vec2 vUV;',\n\n\t\t'void main() {',\n\n\t\t\t'vec3 c = vUV.y > 0.5 ? color : shadowColor;', \n\t\t\t'vec3 t = texture2D( map, vUV ).rgb;',\n\t\t\t'if ( t.r < 0.5 ) discard;',\n\t\t\t\n\t\t\t'float fogFactor = 1.0 - saturate( ( exp2( -fogDensity * fogDensity * fogDepth * fogDepth * LOG2 ) ) );',\n\n\t\t\t'gl_FragColor = vec4( mix( t * c, fogColor, fogFactor ), 1.0 );',\n\n\t\t'}'\n\n\t].join( '\\n' )\n};"
  },
  {
    "path": "js/shaders/circle-shader.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { ShadowColor } from '../core/colors';\nimport { BasicVertex } from './shader-chunks';\n\nTHREE.CircleShader = {\n\n\tuniforms: {\n\t\tradius: { type: 'f', value: 0.5 },\n\t\tcolor:  { type: 'c', value: new THREE.Color( ShadowColor ) }\n\t},\n\n\tvertexShader: BasicVertex,\n\n\tfragmentShader: [\n\n\t\t'uniform float radius;',\n\t\t'uniform vec3 color;',\n\n\t\t'varying vec2 vUV;',\n\n\t\t'void main() {',\n\n\t\t\t'if ( length( vUV - 0.5 ) > radius ) discard;',\n\t\t\t'gl_FragColor = vec4( color, 1.0 );',\n\n\t\t'}'\n\n\t].join( '\\n' )\n};\n"
  },
  {
    "path": "js/shaders/shader-chunks.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst CommonUniforms = {\n\tlightPosition: { value: new THREE.Vector3( 3, 10, 1 ) },\n\tlightIntensity: { value: 1.15 },\n\tshadeAmount: { value: 0.55 }\n};\n\nconst BasicVertex = [\n\n\t'varying vec2 vUV;',\n\n\t'void main() {',\n\t\t'vUV = uv;',\n\t\t'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',\n\t'}'\n\n].join( '\\n' );\n\n\nconst ToonVertex = [\n\n\t'varying vec4 vWorldPosition;',\n\t'varying vec3 vNormal;',\n\t'varying vec2 vUv;',\n\n\t'void main() {',\n\n\t\t'vUv = uv;',\n\n\t\t// World-space normal\n\t\t'vNormal = normalize ( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );',\n\n\t\t'vec3 transformed = vec3( position );',\n\t\t'vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );',\n\t\t'gl_Position = projectionMatrix * mvPosition;',\n\n\t\t'vWorldPosition = modelMatrix * vec4( position, 1.0 );',\n\n\t'}'\n\n].join( '\\n' );\n\n\nconst ToonFragUniforms = [\n\n\t'uniform float lightIntensity;',\n\t'uniform float shadeAmount;',\n\t'uniform vec3 lightPosition;',\n\t'uniform sampler2D gradientMap;',\n\n\t'varying vec4 vWorldPosition;',\n\t'varying vec3 vNormal;',\n\t'varying vec2 vUv;',\n\n].join( '\\n' );\n\n\nconst ToonFragLighting = [\n\n\t'#ifdef FLAT_SHADED',\n\n\t\t// TODO: do this in the vertex shader for speeeeeed\n\t\t'vec3 fdx = vec3( dFdx( vWorldPosition.x ), dFdx( vWorldPosition.y ), dFdx( vWorldPosition.z ) );',\n\t\t'vec3 fdy = vec3( dFdy( vWorldPosition.x ), dFdy( vWorldPosition.y ), dFdy( vWorldPosition.z ) );',\n\t\t'vec3 normal = normalize( cross( fdx, fdy ) );',\n\n\t'#else',\n\n\t\t'vec3 normal = normalize( vNormal );',\n\n\t'#endif',\n\n\t// The usual Lambertian stuff\n\t'vec3 lightDirection = normalize( lightPosition - vWorldPosition.xyz );',\n\t'float dotNL = max( dot( normal, lightDirection ), 0.0 ) * 0.5 + 0.5;',\n\n\t// Clamp for a shaded toon look\n\t'float toonIrradience = ( dotNL < shadeAmount ) ? 0.7 * lightIntensity : 1.0;',\n\t'vec4 diffuseColor = vec4( 0.0 );',\n\n].join( '\\n' );\n\n\nconst ToonFragCommon = [\n\n\t'gl_FragColor = vec4( diffuseColor.rgb * toonIrradience, diffuseColor.a );',\n\n].join( '\\n' );\n\n\nexport { CommonUniforms, BasicVertex, ToonVertex, ToonFragUniforms, ToonFragCommon, ToonFragLighting };\n"
  },
  {
    "path": "js/splash.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the 'License');\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an 'AS IS' BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport TWEEN from 'tween.js';\nimport StartAudioContext from 'startaudiocontext';\nimport Tone from 'tone';\nimport { getParameterByName, getViewerType } from './util';\n\nconst getComponentProperty = AFRAME.utils.entity.getComponentProperty;\nconst setComponentProperty = AFRAME.utils.entity.setComponentProperty;\nconst stringify = AFRAME.utils.coordinates.stringify;\n\nAFRAME.registerComponent('splash', {\n    flyTweenPosition:undefined,\n    flyTweenRotation:undefined,\n    isFirstTime:true,\n    viewer : false,\n    mode:undefined,\n    camPos:{ x:0, y:0, z:0 },\n\n    init(){\n        if(WebVRConfig){\n            WebVRConfig.CARDBOARD_UI_DISABLED = false;\n            WebVRConfig.ENABLE_DEPRECATED_API = true;\n            WebVRConfig.ROTATE_INSTRUCTIONS_DISABLED = false;\n        }\n    },\n    play(){\n        this._splashEl = document.querySelector('#splash');\n        this._splashEl.querySelector('#enterButton').appendChild(document.querySelector('.webvr-ui-button'));\n\n        // listen for enter vr\n        this.el.addEventListener('enter-vr', () => this._entered('vr'));\n        this.el.addEventListener('exit-vr', () => this._exited());\n\n        // start audio context\n        StartAudioContext(Tone.context, ['.webvr-ui-button', '#enter-360']);\n\n        // Connect to server on loaded\n        if( getParameterByName('autojoin') ){\n            setTimeout(() => this._entered('360'), 1000);\n        }\n\n        // add's editing capbaility on desktop\n        if( getParameterByName('reticle')==='true' ){\n            let avatar = document.querySelector('#avatar');\n            setComponentProperty(avatar,'grab-move', '');\n        }\n\n        let displayId;\n        //send analytics events\n        let enterVRButton = this.el.components['webvr-ui'].enterVR;\n        enterVRButton.on('error', (e)=>{\n            ga('send', 'event', 'device', 'error', e);\n        }).on('ready', ()=>{\n            enterVRButton.getVRDisplay().then((disp)=>{\n                displayId = disp.displayId;\n                ga('send', 'event', 'device', 'ready', disp.displayName);\n            });\n        });\n        window.addEventListener('gamepadconnected', (e)=>{\n            let gamepads = navigator.getGamepads();\n            for (let i = 0; i < gamepads.length; ++i) {\n                if(gamepads[i]){\n                // if(gamepads[i] && gamepads[i].displayId == displayId){\n                    ga('send', 'event', 'gamepad', 'ready', gamepads[i].id);\n                }\n            }\n        });\n        this._progressBar();\n    },\n\n    _progressBar(){\n        const promises = [];\n        let asset;\n\n        for(let i=0; i<document.querySelectorAll('a-asset-item').length; i++){\n            asset = document.querySelectorAll('a-asset-item')[i];\n            if (!asset.data){\n                promises.push(new Promise(done => {\n                    asset.addEventListener('loaded', done);\n                }));\n            }\n        }\n        promises.push(new Promise(done => {\n            Tone.Buffer.on('load', done);\n        }));\n        this._allLoaded();\n    },\n\n    _allLoaded(){\n        // let avatar = document.querySelector('#avatar');\n        let camera = document.querySelector('a-entity[camera]');\n        let concater = function(obj) {\n            Object.keys(obj).forEach( key => {\n                obj[key] = ((obj[key]*100) | 0) / 100;\n            });\n            return JSON.stringify(obj);\n        };\n\n        /*\n         * HACK: position is stored to counteract height issues\n         * associated with Oculus's relative position data\n         * using componentchanged instead of EXIT VR\n        */\n        camera.addEventListener('componentchanged', (event) => {\n            if ( event.detail.name === 'position' || event.detail.name === 'rotation' ) {\n                let pos = getComponentProperty(camera,'position');\n                if (pos.y !== 1.6 && pos.y !== 0){\n                    this.camPos.x = pos.x;\n                    this.camPos.y = pos.y;\n                    this.camPos.z = pos.z;\n                }\n            }\n        });\n\n        this._splashEl.querySelector('#enter-container').classList.add('loaded');\n        this._splashEl.querySelector('#enter-360').addEventListener('click', () => this._entered('360'));\n\n        let treeCreatedEvent = (event) => {\n            document.removeEventListener('TREE_CREATED', treeCreatedEvent);\n            this._animateToTree();\n        };\n        document.addEventListener('TREE_CREATED', treeCreatedEvent);\n    },\n\n    _animateToTree(){\n        const player = document.querySelector('#player');\n        const avatar = document.querySelector('#avatar');\n        const camera = document.querySelector('a-entity[camera]');\n        const delay = 500;\n        const speed = 1000;\n\n        let position = getComponentProperty(player,'position');\n        let rotation = getComponentProperty(player,'rotation');\n\n        let isTabletWindow  = this.isTabletLikeDimensions() && this.isTouchDevice() && this.mode === '360';\n        let isTabletVR      = this.isTabletLikeDimensions() && this.isTouchDevice() && this.mode === 'vr';\n        let isDesktop       = this.mode === '360' && !AFRAME.utils.device.isMobile();\n\n        let y = (isDesktop) ? 1.6: 0;\n        y = (isTabletWindow) ? 0: y;\n        const z = this.viewer ? 1.5 : 0;\n\n        this.flyTweenPosition = new TWEEN.Tween(position)\n            .to({ x:0, y:y, z:z }, speed)\n            .delay(delay)\n            .easing(TWEEN.Easing.Quadratic.InOut)\n            .onUpdate(() => player.setAttribute('position', position));\n\n        this.flyTweenRotation = new TWEEN.Tween(rotation)\n            .to({ x:0, y: 0, z: 0 }, speed)\n            .delay(delay)\n            .easing(TWEEN.Easing.Quadratic.InOut)\n            .onUpdate(() => player.setAttribute('rotation', rotation))\n            .onComplete(() => {\n                this.isFirstTime = false;\n                document.dispatchEvent(new Event('INTRO_COMPLETED'));\n            });\n\n        if (this.flyTweenPosition) {\n            this.flyTweenPosition.stop();\n        }\n        if (this.flyTweenRotation) {\n            this.flyTweenRotation.stop();\n        }\n        this.flyTweenPosition.start();\n        this.flyTweenRotation.start();\n    },\n\n    _exited(){\n        this._splashEl.classList.remove('invisible');\n        document.dispatchEvent(new Event('EXITED_FOREST'));\n        this.removeCardBoardInstructions();\n        this.mode = undefined;\n    },\n\n    _entered(mode){\n        this.mode = mode;\n        this.addCardBoardInstructions();\n\n        if(this.mode === '360') {\n            this.viewer = true;\n            ga('send', 'event', 'clickable_link', 'splash_page', 'enter_360');\n        } else {\n            ga('send', 'event', 'clickable_link', 'splash_page', 'enter_VR');\n        }\n\n        let player = document.querySelector('#player');\n        let avatar = document.querySelector('#avatar');\n        let camera = document.querySelector('a-entity[camera]');\n\n        if(!this.isFirstTime && !AFRAME.utils.device.isMobile()){\n            setComponentProperty(player,'position', '0 0 0');\n            setComponentProperty(avatar,'position', this.camPos);\n            setComponentProperty(camera,'position', this.camPos);\n\n        } else if (!this.isFirstTime){\n            setComponentProperty(player,'position', '0 0 0');\n            setComponentProperty(avatar,'position', '0 1.6 0');\n            setComponentProperty(camera,'position', '0 1.6 0');\n\n        }\n        this.el.removeEventListener('enter-vr', () => this._entered());\n        this._splashEl.querySelector('#enter-360').removeEventListener('click', () => this._entered());\n\n        this._splashEl.classList.add('invisible');\n        this.el.sceneEl.systems['copresence-server'].connectToServer();\n\n        // add ux based on device\n        getViewerType((clientType) => {\n            this._setSceneParameters({\n                clientType: clientType,\n                mode:       this.mode,\n                isMobile:   AFRAME.utils.device.isMobile(),\n            });\n            document.dispatchEvent(new Event('ENTERED_FOREST'));\n        });\n    },\n    _setSceneParameters(params){\n        let avatar          = document.querySelector('#avatar');\n        let player          = document.querySelector('#player');\n        let camera          = document.querySelector('a-entity[camera]');\n        let floor           = document.querySelector('#gaze-floor');\n\n        let floorStatic     = document.querySelector('#floorStatic');\n        let skyStatic       = document.querySelector('#skyStatic');\n\n\n        let isTabletWindow  = this.isTabletLikeDimensions() && this.isTouchDevice() && this.mode === '360';\n        let isTabletVR      = this.isTabletLikeDimensions() && this.isTouchDevice() && this.mode === 'vr';\n        let is6DOF          = params.clientType === '6dof';\n        let isDesktop       = params.clientType === 'viewer' && !params.isMobile;\n        let is3DOF          = params.clientType === '3dof';\n        let isMagicWindow   = params.isMobile && this.mode === '360';\n        let isCardBoard     = params.isMobile && this.mode === 'vr';\n\n        if(isTabletVR){\n            setComponentProperty(avatar, 'position', '0 1.6 0');\n            setComponentProperty(floor,'teleport', '');\n            setComponentProperty(avatar,'look-controls', '');\n            setComponentProperty(avatar,'clicker', '');\n\n        } else if(isTabletWindow){\n            setComponentProperty(avatar, 'position', '0 1.6 0');\n            setComponentProperty(avatar, 'rotation', '-35 0 0');\n            setComponentProperty(floor,'teleport', '');\n            setComponentProperty(avatar,'look-controls', '');\n            setComponentProperty(avatar,'clicker', '');\n\n        } else if(is6DOF){\n            setComponentProperty(avatar,'look-controls', '');\n            setComponentProperty(avatar,'clicker', '');\n            setComponentProperty(floorStatic,'material', 'shader: flat; color: #e9a69a;');\n\n        } else if (isDesktop){\n            setComponentProperty(avatar, 'camera', 'userHeight:0.0');\n            setComponentProperty(player,'look-controls', '');\n            setComponentProperty(player,'wasd-controls', 'acceleration:35; easing:25;');\n            setComponentProperty(player,'wasd-boundaries', 'maxRadius:3.125;');\n            setComponentProperty(avatar,'clicker', '');\n            setComponentProperty(floorStatic,'material', 'shader: flat; color: #e9a69a;');\n\n        } else if (is3DOF) {\n            setComponentProperty(floor,'teleport', '');\n            setComponentProperty(avatar,'look-controls', '');\n            setComponentProperty(avatar,'clicker', '');\n\n        } else if (isMagicWindow){\n            this.removeCardBoardInstructions();\n            setComponentProperty(floor, 'teleport', '');\n            setComponentProperty(avatar,'look-controls', '');\n            setComponentProperty(avatar,'clicker', '');\n\n        } else if (isCardBoard){\n            setComponentProperty(floor, 'teleport', '');\n            setComponentProperty(avatar,'look-controls', '');\n\n        } else {\n\n        }\n    },\n    removeCardBoardInstructions(){\n        let cardboard = document.querySelector(\"#cardboard\");\n        if(cardboard) {\n            cardboard.remove();\n        }\n    },\n\n    addCardBoardInstructions(){\n        if(AFRAME.utils.device.isMobile() && this.mode === 'vr'){\n            let cardboard = document.createElement('div');\n            cardboard.id = 'cardboard';\n            document.body.appendChild(cardboard);\n\n            // createElement\n            let container = document.createElement('div');\n            container.id = 'cardboardContainer';\n            cardboard.appendChild(container);\n\n            let img = document.createElement(\"img\");\n            img.id = \"img\";\n            img.src = `/static/img/cardboardInstructions.gif`;\n            container.appendChild(img);\n\n            let p = document.createElement(\"p\");\n            p.innerHTML = 'Place your phone into your default viewer<br><br>';\n            container.appendChild(p);\n\n            let p2 = document.createElement(\"p\");\n            p2.innerHTML = 'No Cardboard viewer?';\n            container.appendChild(p2);\n\n            let b = document.createElement(\"button\");\n            b.innerHTML = `GET ONE`;\n            container.appendChild(b);\n\n            b.addEventListener(\"click\", () => {\n                window.open(\"https://vr.google.com/cardboard/get-cardboard/\", '_blank');\n            });\n        }\n    },\n    isTabletLikeDimensions() {\n        let w = screen.availWidth;\n        let h = screen.availHeight;\n        return ( ( (w > h) ? w : h ) >= 960 );\n    },\n    isTouchDevice() {\n        return 'ontouchstart' in window || navigator.maxTouchPoints;      // works on most browsers ||  works on IE10/11 and Surface\n    },\n});\n\nfunction tweenUpdate() {\n    requestAnimationFrame(tweenUpdate);\n    TWEEN.update();\n}\ntweenUpdate();\n\n\n(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\n  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');\n\nga('create', 'UA-90331006-2', 'auto');\nga('send', 'pageview');\n\nlet aboutBTN = document.getElementById('about-button');\nlet about = document.getElementById('about');\n\naboutBTN.addEventListener('click', () => {\n    about.classList.add('visible');\n    aboutBTN.classList.remove('visible');\n    ga('send', 'event', 'clickable_link', 'splash_page', 'show_info');\n});\n\nabout.addEventListener('click', () => {\n    about.classList.remove('visible');\n    aboutBTN.classList.add('visible');\n});\n"
  },
  {
    "path": "js/util/browserCheck.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { showErrorMessage } from '../util';\n\n\nif(navigator.userAgent.indexOf('MSIE')!==-1 || navigator.appVersion.indexOf('Trident/') > 0) {\n    showErrorMessage(\"error_shake.gif\", \"Your browser is not supported\",\"Download Chrome\",\"http://www.google.com/chrome\");\n}\n"
  },
  {
    "path": "js/util/random-range-1d.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nexport class RandomRange {\n\n\tconstructor( min, max ) {\n\t\tthis.min = min;\n\t\tthis.max = max;\n\t}\n\n\tget [ 'value' ]() { \n\t\treturn Math.random() * ( this.max - this.min ) + this.min;\n\t}\n}"
  },
  {
    "path": "js/util/random-range-3d.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { RandomRange } from './random-range-1d';\n\nexport class RandomRange3D {\n\n\tconstructor( min, max ) {\n\t\tthis.type = min.constructor;\n\t\tthis.range3D = {\n\t\t\tx: new RandomRange( min.x, max.x ),\n\t\t\ty: new RandomRange( min.y, max.y ),\n\t\t\tz: new RandomRange( min.z, max.z )\n\t\t};\n\t}\n\n\tget x() { return this.range3D.x.value; }\n\tget y() { return this.range3D.y.value; }\n\tget z() { return this.range3D.z.value; }\n\t\n\tget [ 'value' ]() { \n\t\treturn new this.type( this.x, this.y, this.z );\n\t}\n}"
  },
  {
    "path": "js/util/tetrahedron.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nconst X = Math.sqrt( 2 / 3 );\nconst Y = 1 / 3;\nconst Z = 1 / Math.sqrt( 2 ) * ( 2 / 3);\n\nconst v1 = new THREE.Vector3( -X, -Y, Z );\nconst v2 = new THREE.Vector3( +X, -Y, Z );\nconst v3 = new THREE.Vector3( 0, -Y, -Z * 2 );\nconst v4 = new THREE.Vector3( 0, 1, 0 );\n\nconst f1 = new THREE.Face3( 2, 1, 0 );\nconst f2 = new THREE.Face3( 1, 3, 0 );\nconst f3 = new THREE.Face3( 2, 3, 1 );\nconst f4 = new THREE.Face3( 0, 3, 2 );\n\nconst t1 = new THREE.Vector2( 1, 0 );\nconst t2 = new THREE.Vector2( 0.5, 1 );\nconst t3 = new THREE.Vector2( 0, 0 );\n\nconst uvs = [\n\t[ t1, t2, t3 ],\n\t[ t1, t2, t3 ],\n\t[ t1, t2, t3 ],\n\t[ t1, t2, t3 ]\n];\n\nexport class Tetrahedron {\n\n\tconstructor() {\n\t\tthis.geometry = new THREE.Geometry();\n\t\tthis.geometry.vertices.push( v1, v2, v3, v4 );\n\t\tthis.geometry.faces.push( f1, f2, f3, f4 );\n\t\tthis.geometry.faceVertexUvs = [ uvs ];\n\t\tthis.geometry.scale( 0.32, 0.32, 0.32 );\n\n\t\tthis.geometry.uvsNeedUpdate = true;\n\n\t\t// Compute normals automatically\n\t\tthis.geometry.computeFaceNormals();\n\t\tthis.geometry.computeVertexNormals();\n\t}\n}\n"
  },
  {
    "path": "js/util/trace.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nif (typeof console._commandLineAPI !== 'undefined') {\n    console.API = console._commandLineAPI; //chrome\n} else if (typeof console._inspectorCommandLineAPI !== 'undefined') {\n    console.API = console._inspectorCommandLineAPI; //Safari\n} else if (typeof console.clear !== 'undefined') {\n    console.API = console;\n}\nwindow.clear = console.API.clear;\nwindow.trace = console.log;\n"
  },
  {
    "path": "js/util.js",
    "content": "// Copyright 2017 Google Inc.\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n// limitations under the License.\n\nexport function getParameterByName(name, url) {\n    if (!url) {\n        url = window.location.href;\n    }\n    name = name.replace(/[\\[\\]]/g, \"\\\\$&\");\n    let regex = new RegExp(\"[?&]\" + name + \"(=([^&#]*)|&|#|$)\"),\n        results = regex.exec(url);\n    if (!results) return null;\n    if (!results[2]) return '';\n    return decodeURIComponent(results[2].replace(/\\+/g, \" \"));\n}\n\nexport function scale(value, inMin, inMax, outMin, outMax) {\n    return ((value - inMin) / (inMax - inMin)) * (outMax - outMin) + outMin;\n}\n\nexport function getViewerType(callBack){\n\n    navigator.getVRDisplays()\n    .then( function( displays ) {\n        if(displays.length>0 && displays[0].isPresenting) {\n            if (displays[0].stageParameters === null) {\n                callBack('3dof');\n            } else {\n                callBack('6dof');\n            }\n        } else {\n            callBack('viewer');\n        }\n    })\n    .catch( function() {\n        callBack('viewer');\n    });\n\n}\n\nexport function showErrorMessage(imgURL, errorCode, cta, ctaUrl){\n\n    console.error(errorCode);\n    let error = document.querySelector(\"#error\");\n\n    // delete error if it already exists\n    if(error) {\n        error.remove();\n    }\n\n    // createElement\n    error = document.createElement('div');\n    error.id = 'error';\n    document.body.appendChild(error);\n\n    let container = document.createElement('div');\n    container.id = 'errorContainer';\n    error.appendChild(container);\n\n    let img = document.createElement(\"img\");\n    img.id = \"img\";\n    img.src = `/static/img/${imgURL}`;\n    container.appendChild(img);\n\n    let p = document.createElement(\"p\");\n    let notification = `${errorCode}`;\n    p.appendChild(document.createTextNode(notification));\n    container.appendChild(p);\n\n    let b = document.createElement(\"button\");\n    b.innerHTML = `${cta}`;\n    container.appendChild(b);\n\n    b.addEventListener(\"click\", () => {\n        if(ctaUrl){\n            window.open(ctaUrl, '_blank');\n        } else {\n            window.location.href = window.location.href.replace(/#.*/,'#');\n            error.remove();\n            let splash = document.querySelector('#splash');\n            splash.classList.remove('invisible');\n        }\n    });\n}\n\nwindow.showErrorMessage = showErrorMessage;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"webvr-musical-forest\",\n  \"version\": \"0.0.1\",\n  \"description\": \"\",\n  \"authors\": [],\n  \"scripts\": {\n    \"start\": \"npm run build-css && npm run budo\",\n    \"build\": \"browserify js/index.js | derequire > build/main.js\",\n    \"buildmin\": \"browserify  -g uglifyify js/index.js | derequire > build/main.js\",\n    \"build-css\": \"node-sass --include-path scss style/splash.scss build/style.css\",\n    \"budo\": \"budo js/index.js:build/main.js --live --verbose --port 3000\",\n    \"appengine\": \"~/bin/google_appengine/dev_appserver.py app.yaml\",\n    \"appengine_watch\": \"parallelshell 'npm run appengine' 'npm run watch'\",\n    \"watch\": \"watchify js/index.js -v -d -o build/main.js\",\n    \"discify\": \"browserify --full-paths js/index.js | derequire | discify --open\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/googlecreativelab/webvr-musicalforest\"\n  },\n  \"dependencies\": {\n    \"aframe\": \"^0.5.0\",\n    \"bufferutil\": \"^1.2.1\",\n    \"domready\": \"^1.0.8\",\n    \"envify\": \"^4.0.0\",\n    \"image-promise\": \"^4.0.1\",\n    \"startaudiocontext\": \"^1.2.0\",\n    \"tone\": \"^0.9.0\",\n    \"uglifyify\": \"^3.0.4\",\n    \"underscore\": \"^1.8.3\",\n    \"url-parse\": \"^1.1.6\",\n    \"webvr-polyfill\": \"aframevr/webvr-polyfill\",\n    \"webvr-ui\": \"^0.9.4\"\n  },\n  \"devDependencies\": {\n    \"babel-preset-es2015\": \"^6.16.0\",\n    \"babel-preset-stage-2\": \"^6.22.0\",\n    \"babelify\": \"^7.3.0\",\n    \"browserify\": \"^14.0.0\",\n    \"budo\": \"^9.4.7\",\n    \"derequire\": \"^2.0.3\",\n    \"discify\": \"^1.6.0\",\n    \"node-sass\": \"^4.5.2\",\n    \"parallelshell\": \"^2.0.0\",\n    \"utf-8-validate\": \"^1.2.1\",\n    \"watchify\": \"^3.7.0\"\n  },\n  \"keywords\": [\n    \"vr\",\n    \"webvr\"\n  ],\n  \"license\": \"Apache-2.0\",\n  \"browserify\": {\n    \"transform\": [\n      [\n        \"babelify\",\n        {\n          \"presets\": [\n            \"es2015\"\n          ]\n        }\n      ]\n    ]\n  }\n}\n"
  },
  {
    "path": "python/__init__.py",
    "content": ""
  },
  {
    "path": "python/base/__init__.py",
    "content": ""
  },
  {
    "path": "python/base/api_fixer.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"Fixes up various popular APIs to ensure they use secure defaults.\"\"\"\n\nimport __builtin__\nimport constants\nimport cPickle\nimport functools\nimport io\nimport json\nimport logging\nimport pickle\nimport yaml\n\nfrom google.appengine.api import urlfetch\nfrom webapp2_extras import sessions\n\n\nclass ApiSecurityException(Exception):\n  \"\"\"Error when attempting to call an unsafe API.\"\"\"\n  pass\n\n\ndef FindArgumentIndex(function, argument):\n  args = function.func_code.co_varnames[:function.func_code.co_argcount]\n  return args.index(argument)\n\n\ndef GetDefaultArgument(function, argument):\n  argument_index = FindArgumentIndex(function, argument)\n  num_positional_args = (function.func_code.co_argcount -\n                         len(function.func_defaults))\n  default_position = argument_index - num_positional_args\n  if default_position < 0:\n    return None\n  return function.func_defaults[default_position]\n\n\ndef ReplaceDefaultArgument(function, argument, replacement):\n  argument_index = FindArgumentIndex(function, argument)\n  num_positional_args = (function.func_code.co_argcount -\n                         len(function.func_defaults))\n  default_position = argument_index - num_positional_args\n  if default_position < 0:\n    raise ApiSecurityException('Attempt to modify positional default value')\n  new_defaults = list(function.func_defaults)\n  new_defaults[default_position] = replacement\n  function.func_defaults = tuple(new_defaults)\n\n\n# JSON.\n# Does not escape HTML metacharacters by default.\n_JSON_CHARACTER_REPLACEMENT_MAPPING = [\n    ('<', '\\\\u%04x' % ord('<')),\n    ('>', '\\\\u%04x' % ord('>')),\n    ('&', '\\\\u%04x' % ord('&')),\n]\n\n\nclass _JsonEncoderForHtml(json.JSONEncoder):\n\n  def encode(self, o):\n    chunks = self.iterencode(o, _one_shot=True)\n    if not isinstance(chunks, (list, tuple)):\n      chunks = list(chunks)\n    return ''.join(chunks)\n\n  def iterencode(self, o, _one_shot=False):\n    chunks = super(_JsonEncoderForHtml, self).iterencode(o, _one_shot)\n    for chunk in chunks:\n      for (character, replacement) in _JSON_CHARACTER_REPLACEMENT_MAPPING:\n        chunk = chunk.replace(character, replacement)\n      yield chunk\n\n\nReplaceDefaultArgument(json.dump, 'cls', _JsonEncoderForHtml)\nReplaceDefaultArgument(json.dumps, 'cls', _JsonEncoderForHtml)\n\n\n# Pickle.  See http://www.cs.jhu.edu/~s/musings/pickle.html for more info.\n# Whitelist of module name => (module, [list of safe names])\n_PICKLE_CLASS_WHITELIST = { '__builtin__': (__builtin__,\n                                            ['basestring',\n                                             'bool',\n                                             'buffer',\n                                             'bytearray',\n                                             'bytes',\n                                             'complex',\n                                             'dict',\n                                             'enumerate',\n                                             'float',\n                                             'frozenset',\n                                             'int',\n                                             'list',\n                                             'long',\n                                             'reversed',\n                                             'set',\n                                             'slice',\n                                             'str',\n                                             'tuple',\n                                             'unicode',\n                                             'xrange']),\n                           }\n\n# See https://docs.python.org/3/library/pickle.html#restricting-globals.\nclass RestrictedUnpickler(pickle.Unpickler):\n\n  def find_class(self, module_name, name):\n    (module, safe_names) = _PICKLE_CLASS_WHITELIST.get(module_name, (None, []))\n    if name in safe_names:\n      return getattr(module, name)\n    raise ApiSecurityException('%s.%s forbidden in unpickling' % (module, name))\n\ndef _SafePickleLoad(f):\n  return RestrictedUnpickler(f).load()\n\ndef _SafePickleLoads(string):\n  return RestrictedUnpickler(io.BytesIO(string)).load()\n\npickle.load = _SafePickleLoad\npickle.loads = _SafePickleLoads\ncPickle.load = _SafePickleLoad\ncPickle.loads = _SafePickleLoads\n\n\n# YAML.  The Python tag scheme allows arbitrary code execution:\n# yaml.load('!!python/object/apply:os.system [\"ls\"]')\nReplaceDefaultArgument(yaml.compose, 'Loader', yaml.loader.SafeLoader)\nReplaceDefaultArgument(yaml.compose_all, 'Loader', yaml.loader.SafeLoader)\nReplaceDefaultArgument(yaml.load, 'Loader', yaml.loader.SafeLoader)\nReplaceDefaultArgument(yaml.load_all, 'Loader', yaml.loader.SafeLoader)\nReplaceDefaultArgument(yaml.parse, 'Loader', yaml.loader.SafeLoader)\nReplaceDefaultArgument(yaml.scan, 'Loader', yaml.loader.SafeLoader)\n\n\n# AppEngine urlfetch.\n# Does not validate certificates by default.\nReplaceDefaultArgument(urlfetch.fetch, 'validate_certificate', True)\nReplaceDefaultArgument(urlfetch.make_fetch_call, 'validate_certificate', True)\n\n\ndef _HttpUrlLoggingWrapper(func):\n  \"\"\"Decorates func, logging when 'url' params do not start with https://.\"\"\"\n  @functools.wraps(func)\n  def _CheckAndLog(*args, **kwargs):\n    try:\n      arg_index = FindArgumentIndex(func, 'url')\n    except ValueError:\n      return func(*args, **kwargs)\n\n    if arg_index < len(args):\n      arg_value = args[arg_index]\n    elif 'url' in kwargs:\n      arg_value = kwargs['url']\n    elif 'url' not in kwargs:\n      arg_value = GetDefaultArgument(func, 'url')\n\n    if arg_value and not arg_value.startswith('https://'):\n      logging.warn('SECURITY : fetching non-HTTPS url %s' % (arg_value))\n    return func(*args, **kwargs)\n  return _CheckAndLog\n\nurlfetch.fetch = _HttpUrlLoggingWrapper(urlfetch.fetch)\nurlfetch.make_fetch_call = _HttpUrlLoggingWrapper(urlfetch.make_fetch_call)\n\n# webapp2_extras session does not set HttpOnly/Secure by default.\nsessions.default_config['cookie_args']['secure'] = (not\n                                                    constants.IS_DEV_APPSERVER)\nsessions.default_config['cookie_args']['httponly'] = True\n\n"
  },
  {
    "path": "python/base/api_fixer_test.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"Tests for base.api_fixer.\"\"\"\n\nimport json\nimport pickle\nimport unittest2\nimport yaml\n\nimport api_fixer\n\n\nclass BadPickle(object):\n  \"\"\"Dummy object.\"\"\"\n  def __reduce__(self):\n    return tuple([eval, tuple(['[1][2]'])])\n\n\nclass ApiFixerTest(unittest2.TestCase):\n  \"\"\"Test cases for base.api_fixer.\"\"\"\n\n  def testJsonEscaping(self):\n    o = {'foo': '<script>alert(1);</script>'}\n    self.assertFalse('<' in json.dumps(o))\n\n  def testYamlLoading(self):\n    unsafe = '!!python/object/apply:os.system [\"ls\"]'\n    try:\n      yaml.load(unsafe)\n      self.fail('loading unsafe YAML object succeeded')\n    except yaml.constructor.ConstructorError:\n      pass\n\n  def testPickle(self):\n    b = { 'foo': BadPickle() }\n    s = pickle.dumps(b)\n    try:\n      b = pickle.loads(s)\n      self.fail('BadPickle() loaded successfully')\n    except IndexError:\n      self.fail('pickled code execution')\n    except api_fixer.ApiSecurityException:\n      pass\n\n    foo = { 'bar': [1, 2, 3] }\n    s = pickle.dumps(foo)\n    try:\n      foo = pickle.loads(s)\n      self.assertEqual(foo['bar'][0], 1)\n    except Exception:\n      self.fail('safe unpickling failed')\n\n\nif __name__ == '__main__':\n  unittest2.main()\n"
  },
  {
    "path": "python/base/constants.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"Public constants for use in application configuration.\"\"\"\n\nimport os\n\n\ndef _IsDevAppServer():\n  return os.environ.get('SERVER_SOFTWARE', '').startswith('Development')\n\n# CSP Nonce length\nNONCE_LENGTH = 10\n\n# webapp2 application configuration constants.\n# template\n(CLOSURE, DJANGO, JINJA2) = range(0, 3)\n\n# using_angular\nDEFAULT_ANGULAR = False\n\n# framing_policy\n(DENY, SAMEORIGIN, PERMIT) = range(0, 3)\nX_FRAME_OPTIONS_VALUES = {DENY: 'DENY', SAMEORIGIN: 'SAMEORIGIN'}\n\n# hsts_policy\nDEFAULT_HSTS_POLICY = {'max_age': 2592000, 'includeSubdomains': True}\n\n# placeholder for the CSP nonce. 'nonce_value' is replaced for every response\n# in base/handers.py with a random nonce value.\nCSP_NONCE_PLACEHOLDER_FORMAT = '\\'nonce-%(nonce_value)s\\' '\n\n# IS_DEV_APPSERVER is primarily used for decisions that rely on whether or\n# not the application is currently serving over HTTPS (dev_appserver does\n# not support HTTPS).\nIS_DEV_APPSERVER = _IsDevAppServer()\n\nDEBUG = IS_DEV_APPSERVER\n\nTEMPLATE_DIR = os.path.sep.join([os.path.dirname(__file__), '..', '..'])\n\n# csp_policy\nDEFAULT_CSP_POLICY = {\n    # Disallow Flash, etc.\n    'object-src': '\\'none\\'',\n    # Strict CSP with fallbacks for browsers not supporting CSP v3.\n    'script-src': CSP_NONCE_PLACEHOLDER_FORMAT +\n                  # Propagate trust to dynamically created scripts.\n                  '\\'strict-dynamic\\' '\n                  # Fallback. Ignored in presence of a nonce\n                  '\\'unsafe-inline\\' '\n                  # Fallback. Ignored in presence of strict-dynamic.\n                  'https: http:',\n    'report-uri': '/csp',\n    'reportOnly': DEBUG,\n}\n"
  },
  {
    "path": "python/base/handlers.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"A collection of secure base handlers for webapp2-based applications.\"\"\"\n\nimport abc\nimport base64\nimport django.conf\nimport django.template\nimport django.template.loader\nimport functools\n\nimport json\nimport webapp2\nfrom webapp2_extras import jinja2\n\nimport api_fixer\nimport constants\nimport models\nimport os\nimport xsrf\n\nfrom google.appengine.api import memcache\nfrom google.appengine.api import users\n\n\n# Django initialization.\ndjango.conf.settings.configure(DEBUG=constants.DEBUG,\n                               TEMPLATE_DEBUG=constants.DEBUG,\n                               TEMPLATE_DIRS=[constants.TEMPLATE_DIR])\n\n\n# Assorted decorators that can be used inside a webapp2.RequestHandler object\n# to assert certain preconditions before entering any method.\ndef requires_auth(f):\n  \"\"\"A decorator that requires a currently logged in user.\"\"\"\n  @functools.wraps(f)\n  def wrapper(self, *args, **kwargs):\n    if not users.get_current_user():\n      self.DenyAccess()\n    else:\n      return f(self, *args, **kwargs)\n  return wrapper\n\n\ndef requires_admin(f):\n  \"\"\"A decorator that requires a currently logged in administrator.\"\"\"\n  @functools.wraps(f)\n  def wrapper(self, *args, **kwargs):\n    if not users.is_current_user_admin():\n      self.DenyAccess()\n    else:\n      return f(self, *args, **kwargs)\n  return wrapper\n\n\ndef xsrf_protected(f):\n  \"\"\"Decorator to validate XSRF tokens for any verb but GET, HEAD, OPTIONS.\"\"\"\n  @functools.wraps(f)\n  def wrapper(self, *args, **kwargs):\n    non_xsrf_protected_verbs = ['options', 'head', 'get']\n    if (self.request.method.lower() in non_xsrf_protected_verbs or\n        self._RequestContainsValidXsrfToken()):\n      return f(self, *args, **kwargs)\n    else:\n      self.XsrfFail()\n  return wrapper\n\n\n# Utility functions.\ndef _GetXsrfKey():\n  \"\"\"Returns the current key for generating and verifying XSRF tokens.\"\"\"\n  client = memcache.Client()\n  xsrf_key = client.get('xsrf_key')\n  if not xsrf_key:\n    config = models.GetApplicationConfiguration()\n    xsrf_key = config.xsrf_key\n    client.set('xsrf_key', xsrf_key)\n  return xsrf_key\n\n\ndef _GetCspNonce():\n  \"\"\"Returns a random CSP nonce.\"\"\"\n  nonce_length = constants.NONCE_LENGTH\n  return base64.b64encode(os.urandom(nonce_length * 2))[:nonce_length]\n\n\n# Classes with a __metaclass__ of _HandlerMeta may not contain any methods\n# with these names.  This is checked when the class is instantiated.\n_RESTRICTED_FUNCTION_LIST = [\n    'dispatch',\n    '_RequestContainsValidXsrfToken',\n]\n\n# Classes with these names (and _HandlerMeta as a metaclass) can contain\n# functions in the _RESTRICTED_FUNCTION_LIST.  Note that there is no\n# package/module specified, so it is possible to bypass this check through\n# clever (or malicious) naming.\n_RESTRICTED_FUNCTION_CLASS_WHITELIST = [\n    'BaseHandler',\n    'BaseAjaxHandler',\n    'BaseCronHandler',\n    'BaseTaskHandler',\n    'AuthenticatedHandler',\n    'AuthenticatedAjaxHandler',\n    'AdminHandler',\n    'AdminAjaxHandler',\n]\n\n# This prefix is returned on GET requests to any Ajax-like handler.\n# It is used to prevent JSON-like responses that may contain non-public\n# information from being included in malicious domains, e.g. evil.com\n# inserting a tag like: <script src=\"http://example.com/ajax/foo\"></script>.\n# evil.com cannot strip this prefix before parsing the result, unlike\n# same-origin requests.  See https://google-gruyere.appspot.com/part3\n# for more information.  It is not necessary for POST requests because\n# there is no way to force the browser to make a cross-domain POST request\n# and interpret the response as Javascript without use of other mechanisms\n# like Cross-Origin-Resource-Sharing, which is disabled by default.\n_XSSI_PREFIX = ')]}\\',\\n'\n\n\nclass SecurityError(Exception):\n  pass\n\n\nclass _HandlerMeta(abc.ABCMeta):\n  \"\"\"Metaclass for our secure base handlers.\n\n  When a class with this metaclass is defined, the fields\n  are checked to ensure that certain methods we would like to approximate as\n  'final' are not declared in subclasses. This is because we provide a\n  default implementation which enforces various security related functionality.\n\n  Class names that can bypass this whitelist are listed in\n  _RESTRICTED_FUNCTION_CLASS_WHITELIST.  Restricted methods are listed in\n  _RESTRICTED_FUNCTION_LIST.\n  \"\"\"\n\n  def __new__(mcs, name, bases, dct):\n    if name not in _RESTRICTED_FUNCTION_CLASS_WHITELIST:\n      for func in _RESTRICTED_FUNCTION_LIST:\n        if func in dct:\n          raise SecurityError('%s attempts to override restricted method %s' %\n                              (name, func))\n    return super(_HandlerMeta, mcs).__new__(mcs, name, bases, dct)\n\n\nclass BaseHandler(webapp2.RequestHandler):\n  \"\"\"Base handler for servicing unauthenticated user requests.\"\"\"\n\n  __metaclass__ = _HandlerMeta\n\n  def __init__(self, request, response):\n    self.initialize(request, response)\n    api_fixer.ReplaceDefaultArgument(response.set_cookie.im_func, 'secure',\n                                     not constants.IS_DEV_APPSERVER)\n    api_fixer.ReplaceDefaultArgument(response.set_cookie.im_func, 'httponly',\n                                     True)\n    if self.current_user:\n      self._xsrf_token = xsrf.GenerateToken(_GetXsrfKey(),\n                                            self.current_user.email())\n      if self.app.config.get('using_angular', constants.DEFAULT_ANGULAR):\n        # AngularJS requires a JS readable XSRF-TOKEN cookie and will pass this\n        # back in AJAX requests.\n        self.response.set_cookie('XSRF-TOKEN', self._xsrf_token, httponly=False)\n    else:\n      self._xsrf_token = None\n\n    self.csp_nonce = _GetCspNonce()\n\n    self._RawWrite = self.response.out.write\n    self.response.out.write = self._ReplacementWrite\n\n  # All content should be rendered through a template system to reduce the\n  # risk/likelihood of XSS issues.  Access to the original function\n  # self.response.out.write is available via self._RawWrite for exceptional\n  # circumstances.\n  def _ReplacementWrite(*args, **kwargs):\n    raise SecurityError('All response content must originate via render() or'\n                        'render_json()')\n\n  def _SetCommonResponseHeaders(self):\n    \"\"\"Sets various headers with security implications.\"\"\"\n    frame_policy = self.app.config.get('framing_policy', constants.DENY)\n    frame_header_value = constants.X_FRAME_OPTIONS_VALUES.get(frame_policy, '')\n    if frame_header_value:\n      self.response.headers['X-Frame-Options'] = frame_header_value\n\n    hsts_policy = self.app.config.get('hsts_policy',\n                                      constants.DEFAULT_HSTS_POLICY)\n    if self.request.scheme.lower() == 'https' and hsts_policy:\n      include_subdomains = bool(hsts_policy.get('includeSubdomains', False))\n      subdomain_string = '; includeSubdomains' if include_subdomains else ''\n      hsts_value = 'max-age=%d%s' % (int(hsts_policy.get('max_age')),\n                                     subdomain_string)\n      self.response.headers['Strict-Transport-Security'] = hsts_value\n\n    self.response.headers['X-XSS-Protection'] = '1; mode=block'\n    self.response.headers['X-Content-Type-Options'] = 'nosniff'\n\n    csp_policy = self.app.config.get('csp_policy', constants.DEFAULT_CSP_POLICY)\n    report_only = False\n    if 'reportOnly' in csp_policy:\n      report_only = csp_policy.get('reportOnly')\n      csp_policy = csp_policy.copy()\n      del csp_policy['reportOnly']\n    header_name = ('Content-Security-Policy%s' %\n                   ('-Report-Only' if report_only else ''))\n    directives = []\n    for (k, v) in csp_policy.iteritems():\n      directives.append('%s %s' % (k, v))\n    csp = '; '.join(directives)\n\n    # Set random nonce per response\n    csp = csp % {'nonce_value': self.csp_nonce}\n\n    self.response.headers.add(header_name, csp)\n\n  @webapp2.cached_property\n  def current_user(self):\n    return users.get_current_user()\n\n  def dispatch(self):\n    self._SetCommonResponseHeaders()\n    super(BaseHandler, self).dispatch()\n\n\n  @classmethod\n  def get_jinja2_config(cls):\n    \"\"\"\n      Builds Jinja2 config based on constants.\n\n      Note: this is used in the factory below, but an alternative way of setting\n      up Jinja2 would be to use the WSGIApplication config to set this and not\n      use the factory below. This has the advantage of having different settings\n      for different applications and not set here at the handler level.\n    \"\"\"\n    extensions = ['jinja2.ext.with_']\n    return {\n      'environment_args': {\n        'autoescape': True,\n        'extensions': extensions,\n        'auto_reload': constants.DEBUG,\n      },\n      'template_path': constants.TEMPLATE_DIR\n    }\n\n  @staticmethod\n  def j2_factory(app):\n    \"\"\"\n      The factory function passed to get_jinja2.\n      Args:\n        app: the WSGIApplication\n    \"\"\"\n    return jinja2.Jinja2(app, BaseHandler.get_jinja2_config())\n\n  @webapp2.cached_property\n  def jinja2(self):\n    \"\"\"\n      Get the cached Jinja2 instance from the app registry, if none exists\n      the factory function is used to create one.\n    \"\"\"\n    return jinja2.get_jinja2(self.j2_factory, app=self.app)\n\n  def render_to_string(self, template, template_values=None):\n    \"\"\"Renders template_name with template_values and returns as a string.\"\"\"\n    if not template_values:\n      template_values = {}\n\n    template_values['_xsrf'] = self._xsrf_token\n    template_values['_csp_nonce'] = self.csp_nonce\n    template_strategy = self.app.config.get('template', constants.CLOSURE)\n\n    if template_strategy == constants.DJANGO:\n      t = django.template.loader.get_template(template)\n      template_values = django.template.Context(template_values)\n      return t.render(template_values)\n    elif template_strategy == constants.JINJA2:\n      return self.jinja2.render_template(template, **template_values)\n    else:\n      ijdata = { 'csp_nonce': self.csp_nonce }\n      return template(template_values, ijdata)\n\n  def render(self, template, template_values=None):\n    \"\"\"Renders template with template_values and writes to the response.\"\"\"\n    template_strategy = self.app.config.get('template', constants.CLOSURE)\n    self._RawWrite(self.render_to_string(template, template_values))\n\n\nclass BaseCronHandler(BaseHandler):\n  \"\"\"Base handler for servicing Cron requests.\n\n  This handler enforces that inbound requests contain the X-AppEngine-Cron\n  header, which AppEngine guarantees is only present on actual invocations\n  according to the cron schedule, or crafted requests by an administrator\n  of the application (the header is filtered out from normal user requests).\n  \"\"\"\n\n  __metaclass__ = _HandlerMeta\n\n  def dispatch(self):\n    header = self.request.headers.get('X-AppEngine-Cron', 'false')\n    if header != 'true':\n      raise SecurityError('attempt to access cron handler without '\n                          'X-AppEngine-Cron header')\n    super(BaseCronHandler, self).dispatch()\n\n\nclass BaseTaskHandler(BaseHandler):\n  \"\"\"Base handler for servicing task requests.\n\n  This handler enforces that inbound requests contain the X-AppEngine-QueueName\n  header, which AppEngine guarantees is only present on requests from the\n  Task Queue API, or crafted requests by an administrator of the application\n  (the header is filtered out from normal user requests).\n  \"\"\"\n\n  __metaclass__ = _HandlerMeta\n\n  def dispatch(self):\n    header = self.request.headers.get('X-AppEngine-QueueName', None)\n    if not header:\n      raise SecurityError('attempt to access task handler without '\n                          'X-AppEngine-QueueName header')\n    super(BaseTaskHandler, self).dispatch()\n\n\nclass BaseAjaxHandler(BaseHandler):\n  \"\"\"Base handler for servicing unauthenticated AJAX requests.\n\n  Responses to GET requests will be prefixed by _XSSI_PREFIX.  Requests\n  using other HTTP verbs will not include such a prefix.\n  \"\"\"\n\n  __metaclass__ = _HandlerMeta\n\n  def _SetAjaxResponseHeaders(self):\n    self.response.headers['Content-Disposition'] = 'attachment; filename=json'\n    self.response.headers['Content-Type'] = 'application/json; charset=utf-8'\n\n  def dispatch(self):\n    self._SetAjaxResponseHeaders()\n    if self.request.method.lower() == 'get':\n      self._RawWrite(_XSSI_PREFIX)\n    super(BaseAjaxHandler, self).dispatch()\n\n  def render(self, *args, **kwargs):\n    raise SecurityError('AJAX handlers must use render_json()')\n\n  def render_json(self, obj):\n    self._RawWrite(json.dumps(obj))\n\n\nclass AuthenticatedHandler(BaseHandler):\n  \"\"\"Base handler for servicing authenticated user requests.\n\n  Implementations should provide an implementation of DenyAccess()\n  and XsrfFail() to handle unauthenticated requests or invalid XSRF tokens.\n\n  POST requests will be rejected unless the request contains a\n  parameter named 'xsrf' which is a valid XSRF token for the\n  currently authenticated user.\n  \"\"\"\n\n  __metaclass__ = _HandlerMeta\n\n  @requires_auth\n  @xsrf_protected\n  def dispatch(self):\n    super(AuthenticatedHandler, self).dispatch()\n\n  def _RequestContainsValidXsrfToken(self):\n    token = self.request.get('xsrf') or self.request.headers.get('X-XSRF-TOKEN')\n    # By default, Angular's $http service will add quotes around the\n    # X-XSRF-TOKEN.\n    if (token and\n        self.app.config.get('using_angular', constants.DEFAULT_ANGULAR) and\n        token[0] == '\"' and token[-1] == '\"'):\n      token = token[1:-1]\n\n    if xsrf.ValidateToken(_GetXsrfKey(), self.current_user.email(),\n                          token):\n      return True\n    return False\n\n  @abc.abstractmethod\n  def DenyAccess(self):\n    pass\n\n  @abc.abstractmethod\n  def XsrfFail(self):\n    pass\n\n\nclass AuthenticatedAjaxHandler(BaseAjaxHandler):\n  \"\"\"Base handler for servicing AJAX requests.\n\n  Implementations should provide an implementation of DenyAccess()\n  and XsrfFail() to handle unauthenticated requests or invalid XSRF tokens.\n\n  POST requests will be rejected unless the request contains a\n  parameter named 'xsrf', OR an HTTP header named 'X-XSRF-Token'\n  which is a valid XSRF token for the currently authenticated user.\n\n  Responses to GET requests will be prefixed by _XSSI_PREFIX.  Requests\n  using other HTTP verbs will not include such a prefix.\n  \"\"\"\n\n  __metaclass__ = _HandlerMeta\n\n  @requires_auth\n  @xsrf_protected\n  def dispatch(self):\n    super(AuthenticatedAjaxHandler, self).dispatch()\n\n  def _RequestContainsValidXsrfToken(self):\n    token = self.request.get('xsrf') or self.request.headers.get('X-XSRF-Token')\n    # By default, Angular's $http service will add quotes around the\n    # X-XSRF-TOKEN.\n    if (token and\n        self.app.config.get('using_angular', constants.DEFAULT_ANGULAR) and\n        token[0] == '\"' and token[-1] == '\"'):\n      token = token[1:-1]\n\n    if xsrf.ValidateToken(_GetXsrfKey(), self.current_user.email(),\n                          token):\n      return True\n    return False\n\n  @abc.abstractmethod\n  def DenyAccess(self):\n    pass\n\n  @abc.abstractmethod\n  def XsrfFail(self):\n    pass\n\n\nclass AdminHandler(AuthenticatedHandler):\n  \"\"\"Base handler for servicing administrator requests.\n\n  Implementations should provide an implementation of DenyAccess()\n  and XsrfFail() to handle unauthenticated requests or invalid XSRF tokens.\n\n  Requests will be rejected if the currently logged in user is\n  not an administrator.\n\n  POST requests will be rejected unless the request contains a\n  parameter named 'xsrf' which is a valid XSRF token for the\n  currently authenticated user.\n  \"\"\"\n\n  __metaclass__ = _HandlerMeta\n\n  @requires_admin\n  def dispatch(self):\n    super(AdminHandler, self).dispatch()\n\n\nclass AdminAjaxHandler(AuthenticatedAjaxHandler):\n  \"\"\"Base handler for servicing AJAX administrator requests.\n\n  Implementations should provide an implementation of DenyAccess()\n  and XsrfFail() to handle unauthenticated requests or invalid XSRF tokens.\n\n  Requests will be rejected if the currently logged in user is\n  not an administrator.\n\n  POST requests will be rejected unless the request contains a\n  parameter named 'xsrf', OR an HTTP header named 'X-XSRF-Token'\n  which is a valid XSRF token for the currently authenticated user.\n\n  Responses to GET requests will be prefixed by _XSSI_PREFIX.  Requests\n  using other HTTP verbs will not include such a prefix.\n  \"\"\"\n\n  __metaclass__ = _HandlerMeta\n\n  @requires_admin\n  def dispatch(self):\n    super(AdminAjaxHandler, self).dispatch()\n"
  },
  {
    "path": "python/base/handlers_test.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"Tests for base.handlers.\"\"\"\n\nimport exceptions\nimport unittest2\nimport webapp2\n\nimport handlers\nimport xsrf\n\nfrom google.appengine.ext import testbed\n\n\nclass DummyHandler(handlers.AuthenticatedHandler):\n  \"\"\"Convenience class to verify successful requests.\"\"\"\n\n  def get(self):\n    self._RawWrite('get_succeeded')\n\n  def post(self):\n    self._RawWrite('post_succeeded')\n\n  def DenyAccess(self):\n    self._RawWrite('access_denied')\n\n  def XsrfFail(self):\n    self._RawWrite('xsrf_fail')\n\n\nclass DummyAjaxHandler(handlers.BaseAjaxHandler):\n  \"\"\"Convenience class to verify successful requests.\"\"\"\n\n  def get(self):\n    pass\n\n  def post(self):\n    pass\n\n\nclass DummyCronHandler(handlers.BaseCronHandler):\n  \"\"\"Convenience class to verify successful requests.\"\"\"\n\n  def get(self):\n    self._RawWrite('get_succeeded')\n\n\nclass DummyTaskHandler(handlers.BaseTaskHandler):\n  \"\"\"Convenience class to verify successful requests.\"\"\"\n\n  def get(self):\n    self._RawWrite('get_succeeded')\n\n\nclass HandlersTest(unittest2.TestCase):\n  \"\"\"Test cases for base.handlers.\"\"\"\n\n  def setUp(self):\n    self.testbed = testbed.Testbed()\n    self.testbed.activate()\n    self.testbed.init_datastore_v3_stub()\n    self.testbed.init_memcache_stub()\n    self.app = webapp2.WSGIApplication([('/', DummyHandler),\n                                        ('/ajax', DummyAjaxHandler),\n                                        ('/cron', DummyCronHandler),\n                                        ('/task', DummyTaskHandler)])\n\n  def _FakeLogin(self):\n    \"\"\"Sets up the environment to have a fake user logged in.\"\"\"\n    self.testbed.setup_env(\n        USER_EMAIL='user@example.com',\n        USER_ID='123',\n        overwrite=True)\n\n  def testHandlerCannotOverrideFinalMethods(self):\n\n    try:\n\n      class _(handlers.BaseHandler):\n\n        def dispatch(self):\n          pass\n\n      self.fail('should not be able to override dispatch')\n    except handlers.SecurityError, e:\n      self.assertTrue(e.message.find('override restricted') != -1)\n\n  def testAuthenticatedHandlerRequiresUser(self):\n\n    self.assertEqual('access_denied', self.app.get_response('/').body)\n    self.assertEqual('access_denied', self.app.get_response('/',\n                                                            method='POST').body)\n    self._FakeLogin()\n    self.assertEqual('get_succeeded', self.app.get_response('/').body)\n\n  def testXsrfProtectionFailsWithInvalidToken(self):\n    self._FakeLogin()\n    self.assertEqual('xsrf_fail', self.app.get_response('/',\n                                                        method='POST',\n                                                        POST={}).body)\n\n  def testXsrfProtectionSucceedsWithValidToken(self):\n    self._FakeLogin()\n\n    key = handlers._GetXsrfKey()\n    token = xsrf.GenerateToken(key, 'user@example.com')\n    self.assertEqual('post_succeeded',\n                     self.app.get_response('/',\n                                           method='POST',\n                                           POST={'xsrf': token}).body)\n\n  def testResponseHasStrictCSP(self):\n    \"\"\"Checks that the CSP in the response is set and strict.\n    More information: https://www.w3.org/TR/CSP3/#strict-dynamic-usage\n    \"\"\"\n    fakeNonce = 'rand0m123'\n    strictScriptSrc = ['\\'strict-dynamic\\'', '\\'nonce-%s\\'' % fakeNonce]\n    strictObjectSrc = ['\\'none\\'']\n\n    handlers._GetCspNonce = lambda : fakeNonce\n\n    headers = self.app.get_response('/', method='GET').headers\n    csp_header = headers.get('Content-Security-Policy')\n    self.assertIsNotNone(csp_header)\n    csp = {x.split()[0]: x.split()[1:] for x in csp_header.split(';')}\n\n    # Check that csp contains a nonce and the stict-dynamic keyword.\n    self.assertTrue(set(strictScriptSrc) <= set(csp.get('script-src')))\n    self.assertListEqual(strictObjectSrc, csp.get('object-src'))\n\n  def testAjaxGetResponsesIncludeXssiPrefix(self):\n    self.assertEqual(handlers._XSSI_PREFIX, self.app.get_response('/ajax').body)\n\n  def testAjaxPostResponsesLackXssiPrefix(self):\n    self.assertEqual('', self.app.get_response('/ajax', method='POST').body)\n\n  def testCronFailsWithoutXAppEngineCron(self):\n    try:\n      self.app.get_response('/cron', method='GET')\n      self.fail('Cron succeeded without X-AppEngine-Cron: true header')\n    except exceptions.AssertionError, e:\n      # webapp2 wraps the raised SecurityError during dispatch with an\n      # exceptions.AssertionError.\n      self.assertTrue(e.message.find('X-AppEngine-Cron') != -1)\n\n  def testCronSucceedsWithXAppEngineCron(self):\n    headers = [('X-AppEngine-Cron', 'true')]\n    self.assertEqual('get_succeeded',\n                     self.app.get_response('/cron',\n                                           headers=headers).body)\n\n  def testTaskFailsWithoutXAppEngineQueueName(self):\n    try:\n      self.app.get_response('/task', method='GET')\n      self.fail('Task succeeded without X-AppEngine-QueueName header')\n    except exceptions.AssertionError, e:\n      # webapp2 wraps the raised SecurityError during dispatch with an\n      # exceptions.AssertionError.\n      self.assertTrue(e.message.find('X-AppEngine-QueueName') != -1)\n\n  def testTaskSucceedsWithXAppEngineQueueName(self):\n    headers = [('X-AppEngine-QueueName', 'default')]\n    self.assertEqual('get_succeeded',\n                     self.app.get_response('/task',\n                                           headers=headers).body)\n\nif __name__ == '__main__':\n  unittest2.main()\n"
  },
  {
    "path": "python/base/models.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"Framework wide datastore models.\"\"\"\n\nfrom google.appengine.ext import ndb\n\nimport os\n\n@ndb.transactional\ndef GetApplicationConfiguration():\n  \"\"\"Returns the application configuration, creating it if necessary.\"\"\"\n  key = ndb.Key(Config, 'config')\n  entity = key.get()\n  if not entity:\n    entity = Config(key=key)\n    entity.xsrf_key = os.urandom(16)\n    entity.put()\n  return entity\n\n\nclass Config(ndb.Model):\n  \"\"\"A simple key-value store for application configuration settings.\"\"\"\n\n  xsrf_key = ndb.BlobProperty()\n"
  },
  {
    "path": "python/base/models_test.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"Tests for base.models.\"\"\"\n\nimport unittest2\n\nimport models\n\nfrom google.appengine.ext import testbed\n\n\nclass ModelsTest(unittest2.TestCase):\n  \"\"\"Test cases for base.models.\"\"\"\n\n  def setUp(self):\n    self.testbed = testbed.Testbed()\n    self.testbed.activate()\n    self.testbed.init_datastore_v3_stub()\n\n  def testConfigurationAutomaticallyGenerated(self):\n    config = models.GetApplicationConfiguration()\n    self.assertIsNotNone(config)\n    self.assertIsNotNone(config.xsrf_key)\n\n\nif __name__ == '__main__':\n  unittest2.main()\n"
  },
  {
    "path": "python/base/xsrf.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"Utilities related to Cross-Site Request Forgery protection.\"\"\"\n\nimport hashlib\nimport hmac\nimport time\n\nDELIMITER_ = ':'\nDEFAULT_TIMEOUT_ = 86400\n\n\ndef _Compare(a, b):\n  \"\"\"Compares a and b in constant time and returns True if they are equal.\"\"\"\n  if len(a) != len(b):\n    return False\n  result = 0\n  for x, y in zip(a, b):\n    result |= ord(x) ^ ord(y)\n\n  return result == 0\n\n\ndef GenerateToken(key, user, action='*', now=None):\n  \"\"\"Generates an XSRF token for the provided user and action.\"\"\"\n  token_timestamp = now or int(time.time())\n  message = DELIMITER_.join([user, action, str(token_timestamp)])\n  digest = hmac.new(key, message, hashlib.sha1).hexdigest()\n  return DELIMITER_.join([str(token_timestamp), digest])\n\n\ndef ValidateToken(key, user, token, action='*', max_age=DEFAULT_TIMEOUT_):\n  \"\"\"Validates the provided XSRF token.\"\"\"\n  if not token or not user:\n    return False\n  try:\n    (timestamp, digest) = token.split(DELIMITER_)\n  except ValueError:\n    return False\n  expected = GenerateToken(key, user, action, timestamp)\n  (_, expected_digest) = expected.split(DELIMITER_)\n  now = int(time.time())\n  if _Compare(expected_digest, digest) and now < int(timestamp) + max_age:\n    return True\n  return False\n"
  },
  {
    "path": "python/base/xsrf_test.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"Tests for base.xsrf.\"\"\"\n\nimport os\nimport time\nimport unittest2\n\nimport xsrf\n\n\nclass XsrfTest(unittest2.TestCase):\n  \"\"\"Test cases for base.xsrf.\"\"\"\n\n  def setUp(self):\n    # non-deterministic tests FTW!\n    self.key = os.urandom(16)\n\n  def testCompare(self):\n    self.assertTrue(xsrf._Compare('a', 'a'))\n    self.assertFalse(xsrf._Compare('a', 'b'))\n    self.assertFalse(xsrf._Compare('a', 'ab'))\n\n  def testTokenWithNoActionVerifies(self):\n    token = xsrf.GenerateToken(self.key, 'user')\n    self.assertTrue(xsrf.ValidateToken(self.key, 'user', token))\n\n  def testTokenWithDifferentActionsFail(self):\n    token = xsrf.GenerateToken(self.key, 'user', 'a')\n    self.assertFalse(xsrf.ValidateToken(self.key, 'user', token, 'b'))\n\n  def testTokenWithDifferentUsersFail(self):\n    token = xsrf.GenerateToken(self.key, 'user')\n    self.assertFalse(xsrf.ValidateToken(self.key, 'otheruser', token))\n\n  def testExpiredTokenDoesNotVerify(self):\n    now = int(time.time()) - (xsrf.DEFAULT_TIMEOUT_ + 1)\n    token = xsrf.GenerateToken(self.key, 'user', '*', now)\n    self.assertFalse(xsrf.ValidateToken(self.key, 'user', token))\n    self.assertTrue(xsrf.ValidateToken(self.key, 'user', token, '*',\n                                       xsrf.DEFAULT_TIMEOUT_ * 2))\n\n\nif __name__ == '__main__':\n  unittest2.main()\n"
  },
  {
    "path": "python/country_servers.py",
    "content": "# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport sys\nfrom random import randint\nfrom google.appengine.ext import ndb\nimport logging\n\n# the top level of your domain in which you'll run backend servers, e.g. your-domain.com\ndomain = '<insert-your-domain-without-host-part>'\n\n# regions you might have treehouse servers in.\n# each of these must be running an instance of the app in 'backend'\n# with DNS set up appropriately (e.g. forest-rooms-us.your-domain.com).\nus = 'us'\nasia = 'asia'\neurope = 'europe'\n\n\n# next we get the list of countries mapped to server they should use,\n# if there's a regional server for that region. country list is\n# derived from http://dev.maxmind.com/static/csv/codes/country_continent.csv\n# & mapped to us/asia/europe thus:\n# NA -> us\n# SA -> us\n# EU -> eu\n# AF -> eu\n# AS -> as\n# OC -> as\n# AN -> us\n\ncountry_to_server_map = {\n    'A1': us,\n    'A2': us,\n    'AD': europe,\n    'AE': asia,\n    'AF': asia,\n    'AG': us,\n    'AI': us,\n    'AL': europe,\n    'AM': asia,\n    'AN': us,\n    'AO': europe,\n    'AP': asia,\n    'AQ': us,\n    'AR': us,\n    'AS': asia,\n    'AT': europe,\n    'AU': asia,\n    'AW': us,\n    'AX': europe,\n    'AZ': asia,\n    'BA': europe,\n    'BB': us,\n    'BD': asia,\n    'BE': europe,\n    'BF': europe,\n    'BG': europe,\n    'BH': asia,\n    'BI': europe,\n    'BJ': europe,\n    'BL': us,\n    'BM': us,\n    'BN': asia,\n    'BO': us,\n    'BR': us,\n    'BS': us,\n    'BT': asia,\n    'BV': us,\n    'BW': europe,\n    'BY': europe,\n    'BZ': us,\n    'CA': us,\n    'CC': asia,\n    'CD': europe,\n    'CF': europe,\n    'CG': europe,\n    'CH': europe,\n    'CI': europe,\n    'CK': asia,\n    'CL': us,\n    'CM': europe,\n    'CN': asia,\n    'CO': us,\n    'CR': us,\n    'CU': us,\n    'CV': europe,\n    'CX': asia,\n    'CY': asia,\n    'CZ': europe,\n    'DE': europe,\n    'DJ': europe,\n    'DK': europe,\n    'DM': us,\n    'DO': us,\n    'DZ': europe,\n    'EC': us,\n    'EE': europe,\n    'EG': europe,\n    'EH': europe,\n    'ER': europe,\n    'ES': europe,\n    'ET': europe,\n    'EU': europe,\n    'FI': europe,\n    'FJ': asia,\n    'FK': us,\n    'FM': asia,\n    'FO': europe,\n    'FR': europe,\n    'FX': europe,\n    'GA': europe,\n    'GB': europe,\n    'GD': us,\n    'GE': asia,\n    'GF': us,\n    'GG': europe,\n    'GH': europe,\n    'GI': europe,\n    'GL': us,\n    'GM': europe,\n    'GN': europe,\n    'GP': us,\n    'GQ': europe,\n    'GR': europe,\n    'GS': us,\n    'GT': us,\n    'GU': asia,\n    'GW': europe,\n    'GY': us,\n    'HK': asia,\n    'HM': us,\n    'HN': us,\n    'HR': europe,\n    'HT': us,\n    'HU': europe,\n    'ID': asia,\n    'IE': europe,\n    'IL': asia,\n    'IM': europe,\n    'IN': asia,\n    'IO': asia,\n    'IQ': asia,\n    'IR': asia,\n    'IS': europe,\n    'IT': europe,\n    'JE': europe,\n    'JM': us,\n    'JO': asia,\n    'JP': asia,\n    'KE': europe,\n    'KG': asia,\n    'KH': asia,\n    'KI': asia,\n    'KM': europe,\n    'KN': us,\n    'KP': asia,\n    'KR': asia,\n    'KW': asia,\n    'KY': us,\n    'KZ': asia,\n    'LA': asia,\n    'LB': asia,\n    'LC': us,\n    'LI': europe,\n    'LK': asia,\n    'LR': europe,\n    'LS': europe,\n    'LT': europe,\n    'LU': europe,\n    'LV': europe,\n    'LY': europe,\n    'MA': europe,\n    'MC': europe,\n    'MD': europe,\n    'ME': europe,\n    'MF': us,\n    'MG': europe,\n    'MH': asia,\n    'MK': europe,\n    'ML': europe,\n    'MM': asia,\n    'MN': asia,\n    'MO': asia,\n    'MP': asia,\n    'MQ': us,\n    'MR': europe,\n    'MS': us,\n    'MT': europe,\n    'MU': europe,\n    'MV': asia,\n    'MW': europe,\n    'MX': us,\n    'MY': asia,\n    'MZ': europe,\n    'NA': europe,\n    'NC': asia,\n    'NE': europe,\n    'NF': asia,\n    'NG': europe,\n    'NI': us,\n    'NL': europe,\n    'NO': europe,\n    'NP': asia,\n    'NR': asia,\n    'NU': asia,\n    'NZ': asia,\n    'O1': us,\n    'OM': asia,\n    'PA': us,\n    'PE': us,\n    'PF': asia,\n    'PG': asia,\n    'PH': asia,\n    'PK': asia,\n    'PL': europe,\n    'PM': us,\n    'PN': asia,\n    'PR': us,\n    'PS': asia,\n    'PT': europe,\n    'PW': asia,\n    'PY': us,\n    'QA': asia,\n    'RE': europe,\n    'RO': europe,\n    'RS': europe,\n    'RU': europe,\n    'RW': europe,\n    'SA': asia,\n    'SB': asia,\n    'SC': europe,\n    'SD': europe,\n    'SE': europe,\n    'SG': asia,\n    'SH': europe,\n    'SI': europe,\n    'SJ': europe,\n    'SK': europe,\n    'SL': europe,\n    'SM': europe,\n    'SN': europe,\n    'SO': europe,\n    'SR': us,\n    'ST': europe,\n    'SV': us,\n    'SY': asia,\n    'SZ': europe,\n    'TC': us,\n    'TD': europe,\n    'TF': us,\n    'TG': europe,\n    'TH': asia,\n    'TJ': asia,\n    'TK': asia,\n    'TL': asia,\n    'TM': asia,\n    'TN': europe,\n    'TO': asia,\n    'TR': europe,\n    'TT': us,\n    'TV': asia,\n    'TW': asia,\n    'TZ': europe,\n    'UA': europe,\n    'UG': europe,\n    'UM': asia,\n    'US': us,\n    'UY': us,\n    'UZ': asia,\n    'VA': europe,\n    'VC': us,\n    'VE': us,\n    'VG': us,\n    'VI': us,\n    'VN': asia,\n    'VU': asia,\n    'WF': asia,\n    'WS': asia,\n    'YE': asia,\n    'YT': europe,\n    'ZA': europe,\n    'ZM': europe,\n    'ZW': europe,\n    'ZZ': us\n}\n\n\ndef get_region_for_country( client_country ):\n    region = country_to_server_map[client_country.upper()]\n    if region is None:\n        region = 'us'\n\n    return region\n\ndef get_all_servers():\n    servers = RegionalRoomServer.query().fetch(10)\n\n    if servers is None or len(servers) == 0:\n        servers = []\n        server = RegionalRoomServer()\n        server.name = 'us'\n        # you'll need to modify this appropriately for your deployment setup\n        server.hostname = 'forest-rooms-' + server.name + '.' + domain\n        server.put()\n\n        servers.append(server)\n        logging.info(\"Auto populated datastore\")\n    return servers\n\n\n\nclass RegionalRoomServer(ndb.Model):\n    name = ndb.StringProperty('name', indexed=True)\n    hostname = ndb.StringProperty('hostname', indexed=True)\n\n\nif __name__ == \"__main__\":\n    if len( sys.argv ) == 2:\n        print get_server_for_country( sys.argv[ 1 ] )\n"
  },
  {
    "path": "python/handlers.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\nimport json\nimport logging\nimport country_servers\n\nfrom base import handlers\n\n# Minimal set of handlers to let you display main page with examples\nclass RootHandler(handlers.BaseHandler):\n\n  def get(self):\n\n    self.render('index.html')\n\nclass ConfigHandler(handlers.BaseHandler):\n\n  def get(self):\n    servers = country_servers.get_all_servers()\n    country = self.request.headers.get(\"X-AppEngine-Country\")\n    region = country_servers.get_region_for_country(country)\n\n    self.response.headers['Content-Type'] = 'application/javascript; charset=utf-8'\n    self.render('config.template', { 'default_region': region, 'servers': servers })\n\nclass CspHandler(handlers.BaseAjaxHandler):\n\n  def post(self):\n    try:\n      report = json.loads(self.request.body)\n      logging.warn('CSP Violation: %s' % (json.dumps(report['csp-report'])))\n      self.render_json({})\n    except:\n      self.render_json({'error': 'invalid CSP report'})\n"
  },
  {
    "path": "python/main.py",
    "content": "# Copyright 2014 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"Main application entry point.\"\"\"\n\nimport base.api_fixer\n\nimport webapp2\n\nimport base\nimport base.constants\nimport handlers\n\n# These should all inherit from base.handlers.BaseHandler\n_UNAUTHENTICATED_ROUTES = [\n    ('/', handlers.RootHandler),\n    ('/config.js', handlers.ConfigHandler)\n]\n\n# These should all inherit from base.handlers.BaseAjaxHandler\n_UNAUTHENTICATED_AJAX_ROUTES = [('/csp', handlers.CspHandler)]\n\n# These should all inherit from base.handlers.AuthenticatedHandler\n_USER_ROUTES = []\n\n# These should all inherit from base.handlers.AuthenticatedAjaxHandler\n_AJAX_ROUTES = []\n\n# These should all inherit from base.handlers.AdminHandler\n_ADMIN_ROUTES = []\n\n# These should all inherit from base.handlers.AdminAjaxHandler\n_ADMIN_AJAX_ROUTES = []\n\n# These should all inherit from base.handlers.BaseCronHandler\n_CRON_ROUTES = []\n\n# These should all inherit from base.handlers.BaseTaskHandler\n_TASK_ROUTES = []\n\n# Place global application configuration settings (e.g. settings for\n# 'webapp2_extras.sessions') here.\n#\n# These values will be accessible from handler methods like this:\n# self.app.config.get('foo')\n#\n# Framework level settings:\n#   template: one of base.constants.CLOSURE (default), base.constants.DJANGO,\n#             or base.constants.JINJA.\n#\n#   using_angular: True or False (default).  When True, an XSRF-TOKEN cookie\n#                  will be set for interception/use by Angular's $http service.\n#                  When False, no header will be set (but an XSRF token will\n#                  still be available under the _xsrf key for Django/Jinja\n#                  templates).  If you set this to True, be especially careful\n#                  when mixing Angular and any server side templates:\n#                    https://github.com/angular/angular.js/issues/5601\n#                  See the summary by IgorMinar for details.\n#\n#   framing_policy: one of base.constants.DENY (default),\n#                   base.constants.SAMEORIGIN, or base.constants.PERMIT\n#\n#   hsts_policy:    A dictionary with minimally a 'max_age' key, and optionally\n#                   a 'includeSubdomains' boolean member.\n#                   Default: { 'max_age': 2592000, 'includeSubDomains': True }\n#                   implying 30 days of strict HTTPS for all subdomains.\n#\n#   csp_policy:     A dictionary with keys that correspond to valid CSP\n#                   directives, as defined in the W3C CSP 3 spec.  Each\n#                   key/value pair is transmitted as a distinct\n#                   Content-Security-Policy header.\n#                   Default: {'default-src': '\\'self\\''}\n#                   which is a very restrictive policy.  An optional\n#                   'reportOnly' boolean key substitutes a\n#                   'Content-Security-Policy-Report-Only' header\n#                   name in lieu of 'Content-Security-Policy' (the default\n#                   is base.constants.DEBUG).\n#\n#  Note that the default values are also configured in app.yaml for files\n#  served via the /static/ resources.  You may need to change the settings\n#  there as well.\n\n_CONFIG = {\n    'template': base.constants.JINJA2,\n    # Developers are encouraged to build sites that comply with this CSP policy.\n    # Changing the first two entries (nonce, strict-dynamic) of the script-src\n    # directive may render XSS protection invalid! For more information take a\n    # look here https://www.w3.org/TR/CSP3/#strict-dynamic-usage\n    # With this policy, modern browsers will execute only those scripts whose\n    # nonce attribute matches the value set in the policy header, as well as\n    # scripts dynamically added to the page by scripts with the proper nonce.\n    # Older browsers, which don't support the CSP3 standard, will ignore the\n    # nonce-* and 'strict-dynamic' keywords and fall back to [script-src\n    # 'unsafe-inline' https: http:] which will not provide protection against\n    # XSS vulnerabilities, but will allow the application to function properly.\n    'csp_policy': {\n        # Disallow Flash, etc.\n        'object-src': '\\'none\\'',\n        # Strict CSP with fallbacks for browsers not supporting CSP v3.\n        'script-src': # Propagate trust to dynamically created scripts.\n                      # '\\'strict-dynamic\\' '\n                      # Fallback. Ignored in presence of a nonce\n                      # '\\'unsafe-inline\\' '\n                      '\\'unsafe-eval\\' '\n                      # Fallback. Ignored in presence of strict-dynamic.\n                      'https: http:',\n        'report-uri': '/csp',\n        'reportOnly': base.constants.DEBUG,\n    }\n}\n\n#################################\n# DO NOT MODIFY BELOW THIS LINE #\n#################################\n\napp = webapp2.WSGIApplication(\n    routes=(_UNAUTHENTICATED_ROUTES + _UNAUTHENTICATED_AJAX_ROUTES +\n            _USER_ROUTES + _AJAX_ROUTES + _ADMIN_ROUTES + _ADMIN_AJAX_ROUTES +\n            _CRON_ROUTES + _TASK_ROUTES),\n    debug=base.constants.DEBUG,\n    config=_CONFIG)\n"
  },
  {
    "path": "python/main_test.py",
    "content": "# Copyright 2015 Google Inc. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n#     Unless required by applicable law or agreed to in writing, software\n#     distributed under the License is distributed on an \"AS IS\" BASIS,\n#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n#     See the License for the specific language governing permissions and\n#     limitations under the License.\n\"\"\"Tests for main.\"\"\"\n\nimport unittest2\nimport webapp2\nimport webapp2_extras.routes\n\nfrom base import handlers\nimport main\n\n\nclass MainTest(unittest2.TestCase):\n  \"\"\"Test cases for main.\"\"\"\n\n  def _VerifyInheritance(self, routes_list, base_class):\n    \"\"\"Checks that the handlers of the given routes inherit from base_class.\"\"\"\n    router = webapp2.Router(routes_list)\n    routes = router.match_routes + router.build_routes.values()\n    inheritance_errors = ''\n    for route in routes:\n      if issubclass(route.__class__, webapp2_extras.routes.MultiRoute):\n        self._VerifyInheritance(list(route.get_routes()), base_class)\n        continue\n\n      if issubclass(route.handler, webapp2.RedirectHandler):\n        continue\n\n      if not issubclass(route.handler, base_class):\n        inheritance_errors += '* %s does not inherit from %s.\\n' % (\n            route.handler.__name__, base_class.__name__)\n\n    return inheritance_errors\n\n  def testRoutesInheritance(self):\n    errors = ''\n    errors += self._VerifyInheritance(main._UNAUTHENTICATED_ROUTES,\n                                      handlers.BaseHandler)\n    errors += self._VerifyInheritance(main._UNAUTHENTICATED_AJAX_ROUTES,\n                                      handlers.BaseAjaxHandler)\n    errors += self._VerifyInheritance(main._USER_ROUTES,\n                                      handlers.AuthenticatedHandler)\n    errors += self._VerifyInheritance(main._AJAX_ROUTES,\n                                      handlers.AuthenticatedAjaxHandler)\n    errors += self._VerifyInheritance(main._ADMIN_ROUTES,\n                                      handlers.AdminHandler)\n    errors += self._VerifyInheritance(main._ADMIN_AJAX_ROUTES,\n                                      handlers.AdminAjaxHandler)\n    errors += self._VerifyInheritance(main._CRON_ROUTES,\n                                      handlers.BaseCronHandler)\n    errors += self._VerifyInheritance(main._TASK_ROUTES,\n                                      handlers.BaseTaskHandler)\n    if errors:\n      self.fail('Some handlers do not inherit from the correct classes:\\n' +\n                errors)\n\n  def testStrictHandlerMethodRouting(self):\n    \"\"\"Checks that handler functions properly limit applicable HTTP methods.\"\"\"\n    router = webapp2.Router(main._USER_ROUTES + main._AJAX_ROUTES +\n                            main._ADMIN_ROUTES + main._ADMIN_AJAX_ROUTES)\n    routes = router.match_routes + router.build_routes.values()\n    failed_routes = []\n    while routes:\n      route = routes.pop()\n      if issubclass(route.__class__, webapp2_extras.routes.MultiRoute):\n        routes += list(route.get_routes())\n        continue\n\n      if issubclass(route.handler, webapp2.RedirectHandler):\n        continue\n\n      if route.handler_method and not route.methods:\n        failed_routes.append('%s (%s)' % (route.template,\n                                          route.handler.__name__))\n\n    if failed_routes:\n      self.fail('Some handlers specify a handler_method but are missing a '\n                'methods\" attribute and may be vulnerable to XSRF via GET '\n                'requests:\\n * ' + '\\n * '.join(failed_routes))\n\n\nif __name__ == '__main__':\n  unittest2.main()\n"
  },
  {
    "path": "static/models/bg-tree-1.obj",
    "content": "# WaveFront *.obj file (generated by CINEMA 4D)\n\ng T0\nusemtl default\nv 0.002211 -50319.394575 643.569099\nv -0.001393 50319.394575 643.569099\nv 0.001393 -50319.394575 -643.569099\nv -0.002211 50319.394575 -643.569099\nv 193.430643 34224.063607 -1187.421986\nv 193.430639 34438.054141 -1187.421986\nv 383.49713 34461.72185 -1693.550947\nv 344.742168 34614.596245 -1548.91547\nv 610.230923 36066.252359 -1633.227163\nv 586.296569 36078.629597 -1543.902939\nv 671.454181 37751.454252 -1590.129769\nv 651.708339 37761.665474 -1516.437285\nv -2.54236 34456.199533 -534.75604\nv -2.542356 34205.918206 -534.756041\nv 0.775234 -10454.994775 644.508516\nv 0.775239 -10787.90725 644.508516\nv 408.012358 -8811.710688 1230.435558\nv 119.402512 -10371.22938 1145.224187\nv 154.973641 -10132.832403 1239.839748\nv 138.473894 -10126.859301 1123.71217\nv 403.015706 -8809.901841 1195.268411\nv 119.402511 -10262.968225 990.152164\nv -187.00359 -20452.489717 1025.925562\nv -321.913333 -20266.132184 1354.251253\nv 0.277238 -21002.230843 616.150247\nv 0.277232 -20551.211217 616.150247\nv -509.5859 -19134.36006 1252.95692\nv -118.795314 -20846.756346 838.935504\nv -294.866232 -20268.006517 1232.553183\nv -544.817164 -19139.754641 1245.209942\nv -118.795319 -20517.963039 838.935504\nv -222.259557 -20576.323141 1272.491589\nv -500.389886 -19134.997333 1211.579576\nv -535.62115 -19140.391914 1203.832598\nv -4.748229 -35836.423107 -635.553579\nv -4.748226 -36061.698312 -635.553579\nv -37.70897 -35797.977389 -671.28529\nv -66.070073 -35941.624083 -777.130376\nv -57.67723 -35712.621843 -698.78891\nv -98.170648 -35720.272713 -849.912402\nv -118.930904 -33413.896828 -753.777431\nv -136.487838 -33417.214054 -819.300799\nv -2.397508 10982.775385 624.427512\nv -2.397504 10701.522601 624.427511\nv -89.939652 10938.618696 780.680051\nv -225.4429 10800.184667 1075.97653\nv -89.93965 10745.679286 780.680051\nv -225.442901 10884.11331 1075.97653\n\nvt 0 -1 0\nvt 0 0 0\nvt 1 -1 0\nvt 1 0 0\nvt 0 -1 0\nvt 1 -1 0\nvt 0 0 0\nvt 1 0 0\nvt 1 -1 0\nvt 0 -1 0\nvt 1 0 0\nvt 0 0 0\nvt 0 -1 0\nvt 1 -1 0\nvt 0 0 0\nvt 1 0 0\nvt 1 -1 0\nvt 1 0 0\nvt 0 0 0\nvt 0 -1 0\nvt 0.416667 -0.359412 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.359412 0\nvt 0.416667 -0.359412 0\nvt 0.416667 -0.359412 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.583333 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.583333 -0.529565 0\nvt 1 -0.127661 0\nvt 1 -0.14238 0\nvt 1 -0.127661 0\nvt 1 -0.14238 0\nvt 1 -0.127661 0\nvt 1 -0.14238 0\nvt 1 -0.127661 0\nvt 1 -0.14238 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\n\nf 1/1 2/2 4/4 3/3 \nf 13/19 6/8 5/6 14/20 \nf 8/12 10/16 9/14 7/10 \nf 6/7 8/11 7/9 5/5 \nf 10/15 12/18 11/17 9/13 \nf 20/26 21/27 17/23 19/25 \nf 31/37 23/29 32/38 28/34 \nf 23/29 29/35 24/30 32/38 \nf 29/35 33/39 27/33 24/30 \nf 26/32 31/37 28/34 25/31 \nf 27/33 33/39 34/40 30/36 \nf 15/21 22/28 18/24 16/22 \nf 22/28 20/26 19/25 18/24 \nf 36/42 38/44 37/43 35/41 \nf 40/46 42/48 41/47 39/45 \nf 38/44 40/46 39/45 37/43 \nf 43/49 45/51 47/53 44/50 \nf 45/51 48/54 46/52 47/53 \n\n"
  },
  {
    "path": "static/models/bg-tree-2.obj",
    "content": "# WaveFront *.obj file (generated by CINEMA 4D)\n\ng T1\nusemtl Mat\nv 47.567647 24592.814791 609.948777\nv 47.567647 24311.562006 609.948789\nv 135.109797 24548.65811 766.201315\nv 270.613065 24410.224103 1231.279249\nv 135.109797 24355.7187 766.201323\nv 270.613065 24494.152746 1231.279246\nv 0.001021 -50310.62883 -643.507891\nv -0.001021 50328.160319 -643.503932\nv 0.001021 -50310.62888 643.504854\nv -0.001021 50328.160269 643.508813\nv -233.52329 -23555.630286 -1124.031178\nv -233.52329 -23341.639752 -1124.031188\nv -430.637956 -23317.972071 -1627.457058\nv -389.867337 -23165.097668 -1483.376794\nv -656.507416 -21713.441563 -1563.973519\nv -631.328228 -21701.064319 -1474.99218\nv -717.122996 -20028.239668 -1520.025596\nv -696.350165 -20018.028442 -1446.615992\nv -28.456706 -23573.775651 -474.165076\nv -28.456706 -23323.494324 -474.165088\nv 23.257412 16226.215385 623.971393\nv 23.257412 16559.12786 623.971377\nv -88.367203 16924.682334 1126.294535\nv -375.7592 18484.201026 1215.527159\nv -107.737085 17169.052411 1105.050882\nv -122.613825 17163.079315 1221.397514\nv -90.532353 17032.943482 971.237623\nv -371.254047 18486.009871 1180.293676\nv 31.640006 6011.891792 617.811433\nv 31.640005 6462.911418 617.811411\nv 224.623943 6561.63294 1024.931922\nv 364.104687 6747.990491 1351.341962\nv 335.361048 6746.116152 1230.033392\nv 585.464314 7294.214092 1239.198997\nv 550.344649 7299.608673 1247.437126\nv 153.811525 6167.366301 838.912449\nv 153.811525 6496.159608 838.912434\nv 263.319084 6437.799529 1270.98167\nv 540.571812 7298.971397 1206.192213\nv 575.691477 7293.576817 1197.954083\nv -34.871549 -1407.185261 -624.000499\nv -34.871549 -1688.438045 -624.000509\nv -124.586792 -1451.341942 -779.015526\nv -264.199826 -1589.775958 -1072.391301\nv -124.586792 -1644.281352 -779.015534\nv -264.199826 -1505.847315 -1072.391298\n\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\nvt 0 -1 0\nvt 0 0 0\nvt 1 -1 0\nvt 1 0 0\nvt 0 -1 0\nvt 1 -1 0\nvt 0 0 0\nvt 1 0 0\nvt 1 -1 0\nvt 0 -1 0\nvt 1 0 0\nvt 0 0 0\nvt 0 -1 0\nvt 1 -1 0\nvt 0 0 0\nvt 1 0 0\nvt 1 -1 0\nvt 1 0 0\nvt 0 -1 0\nvt 0 0 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.359412 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.359412 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.359412 0\nvt 0.416667 -0.359412 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.583333 -0.538841 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.583333 -0.529565 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\n\nf 1/1 3/3 5/5 2/2 \nf 3/3 6/6 4/4 5/5 \nf 7/7 8/8 10/10 9/9 \nf 20/26 12/14 11/12 19/25 \nf 14/18 16/22 15/20 13/16 \nf 12/13 14/17 13/15 11/11 \nf 16/21 18/24 17/23 15/19 \nf 25/31 28/34 24/30 26/32 \nf 37/43 31/37 38/44 36/42 \nf 31/37 33/39 32/38 38/44 \nf 33/39 39/45 35/41 32/38 \nf 30/36 37/43 36/42 29/35 \nf 35/41 39/45 40/46 34/40 \nf 22/28 27/33 23/29 21/27 \nf 27/33 25/31 26/32 23/29 \nf 41/47 43/49 45/51 42/48 \nf 43/49 46/52 44/50 45/51 \n\n"
  },
  {
    "path": "static/models/bg-tree-3.obj",
    "content": "# WaveFront *.obj file (generated by CINEMA 4D)\n\ng T2\nusemtl default\nv 0.002266 -50319.395029 643.569333\nv -0.001339 50319.39412 643.569333\nv 0.001448 -50319.395029 -643.568866\nv -0.002157 50319.39412 -643.568866\nv 6.222563 39584.048537 624.427373\nv 6.222567 39302.795752 624.427369\nv -81.319583 39539.891846 780.679911\nv -216.822833 39401.457814 1075.976387\nv -81.31958 39346.952436 780.679908\nv -216.822834 39485.386458 1075.976388\nv 6.222475 45584.048538 624.427429\nv 6.222479 45302.795754 624.427426\nv -216.822919 45401.457817 1075.976444\nv -81.31967 45539.891848 780.679967\nv -216.82292 45485.386461 1075.976445\nv -81.319667 45346.952438 780.679965\nv 153.198441 -22678.489724 -1382.503714\nv 153.198444 -22892.480258 -1382.503735\nv 202.905553 -22501.947584 -1771.222004\nv 241.660514 -22654.821965 -1915.857495\nv 370.874999 -21859.781153 -1785.926446\nv 394.809353 -21872.158383 -1875.250671\nv 426.331935 -20906.4002 -1761.128095\nv 446.077777 -20916.611415 -1834.820581\nv -3.416941 -22660.344413 -538.481203\nv -3.416937 -22910.62574 -538.481228\nv 8.46738 22661.790515 643.707006\nv 8.467387 22210.770889 643.707005\nv -286.676089 22944.995214 1260.109941\nv -536.627012 23493.09315 1272.766674\nv -178.813445 22760.512014 1053.48232\nv -313.723191 22946.869546 1381.808011\nv -110.605172 22695.038692 866.492263\nv -214.069414 22636.67859 1300.048347\nv -501.395748 23498.48773 1280.513653\nv -110.605167 22366.245385 866.492262\nv -190.656988 -42885.162575 1127.18018\nv -492.199733 23497.850457 1239.136309\nv -527.430998 23492.455877 1231.38933\nv -380.723503 -42647.504383 1633.309155\nv -190.656991 -42671.172041 1127.180199\nv -607.457341 -41042.973876 1572.985505\nv -341.96854 -42494.629974 1488.673693\nv -668.680637 -40087.426902 1529.888196\nv -583.522984 -41030.596629 1483.661283\nv 5.316037 -42903.307912 474.51424\nv -648.934792 -40077.215673 1456.195713\nv 5.316033 -42653.026585 474.514262\nv 1.653124 5833.898383 -646.550315\nv 1.65312 6166.810857 -646.550286\nv -116.974164 6532.365349 -1147.265937\nv -405.584055 8091.88404 -1232.477174\nv -136.045553 6776.735425 -1125.7539\nv -152.5453 6770.762333 -1241.881478\nv -116.974166 6640.62649 -992.193906\nv -400.587404 8093.692883 -1197.310027\nv -2.158684 -11789.229054 -618.193576\nv -2.15869 -11338.209428 -618.193537\nv 185.12213 -11239.487887 -1027.968843\nv 320.031868 -11053.130321 -1356.294518\nv 292.984767 -11055.004666 -1234.596448\nv 542.935674 -10506.906721 -1247.253134\nv 507.704409 -10501.512141 -1255.000112\nv 116.913864 -11633.754535 -840.97882\nv 116.913859 -11304.961227 -840.978791\nv 220.378101 -11363.321288 -1274.534881\nv 498.508395 -10502.149418 -1213.622768\nv 533.73966 -10507.543998 -1205.87579\nv 0.515238 12134.667163 -618.40535\nv 0.515243 11853.414378 -618.405373\nv 88.057384 12090.51049 -774.657892\nv 223.560636 11952.07649 -1069.954383\nv 88.057387 11897.57108 -774.657909\nv 223.560635 12036.005133 -1069.954376\n\nvt 0 -1 0\nvt 0 0 0\nvt 1 -1 0\nvt 1 0 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -1 0\nvt 0 -1 0\nvt 1 0 0\nvt 0 0 0\nvt 0 -1 0\nvt 1 -1 0\nvt 0 0 0\nvt 1 0 0\nvt 0 -1 0\nvt 1 -1 0\nvt 0 0 0\nvt 1 0 0\nvt 1 -1 0\nvt 1 0 0\nvt 0 -1 0\nvt 0 0 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.583333 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.538841 0\nvt 0 -1 0\nvt 1 -1 0\nvt 0.5 -0.529565 0\nvt 0.583333 -0.529565 0\nvt 0 -1 0\nvt 1 -1 0\nvt 0 0 0\nvt 1 0 0\nvt 1 -1 0\nvt 0 -1 0\nvt 0 0 0\nvt 1 0 0\nvt 1 -1 0\nvt 1 0 0\nvt 0 0 0\nvt 0 -1 0\nvt 1 0 0\nvt 0 0 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.359412 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.359412 0\nvt 0.416667 -0.36438 0\nvt 0.416667 -0.359412 0\nvt 0.416667 -0.359412 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.583333 -0.538841 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.5 -0.538841 0\nvt 0.5 -0.529565 0\nvt 0.583333 -0.529565 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\nvt 1 -0.449065 0\nvt 1 -0.449065 0\nvt 1 -0.472594 0\n\nf 1/1 2/2 4/4 3/3 \nf 5/5 7/7 9/9 6/6 \nf 7/7 10/10 8/8 9/9 \nf 11/11 14/14 16/16 12/12 \nf 14/14 15/15 13/13 16/16 \nf 18/20 20/24 19/22 17/18 \nf 20/23 22/28 21/26 19/21 \nf 26/32 18/19 17/17 25/31 \nf 22/27 24/30 23/29 21/25 \nf 35/41 38/45 39/46 30/36 \nf 33/39 31/37 34/40 36/42 \nf 31/37 29/35 32/38 34/40 \nf 29/35 38/45 35/41 32/38 \nf 27/33 33/39 36/42 28/34 \nf 48/60 41/50 37/44 46/58 \nf 45/57 47/59 44/55 42/52 \nf 41/49 43/54 40/48 37/43 \nf 43/53 45/56 42/51 40/47 \nf 53/65 56/68 52/64 54/66 \nf 50/62 55/67 51/63 49/61 \nf 55/67 53/65 54/66 51/63 \nf 63/75 67/79 68/80 62/74 \nf 65/77 59/71 66/78 64/76 \nf 59/71 61/73 60/72 66/78 \nf 61/73 67/79 63/75 60/72 \nf 58/70 65/77 64/76 57/69 \nf 69/81 71/83 73/85 70/82 \nf 71/83 74/86 72/84 73/85 \n\n"
  },
  {
    "path": "static/models/bg-tree-ring.obj",
    "content": "# WaveFront *.obj file (generated by CINEMA 4D)\n\ng Cylinder\nusemtl Mat\nv 50 0 0\nv 50 100 0\nv 49.240388 0 -8.682409\nv 49.240388 100 -8.682409\nv 46.984631 0 -17.101007\nv 46.984631 100 -17.101007\nv 43.30127 0 -25\nv 43.30127 100 -25\nv 38.302222 0 -32.13938\nv 38.302222 100 -32.13938\nv 32.13938 0 -38.302222\nv 32.13938 100 -38.302222\nv 25 0 -43.30127\nv 25 100 -43.30127\nv 17.101007 0 -46.984631\nv 17.101007 100 -46.984631\nv 8.682409 0 -49.240388\nv 8.682409 100 -49.240388\nv 0 0 -50\nv 0 100 -50\nv -8.682409 0 -49.240388\nv -8.682409 100 -49.240388\nv -17.101007 0 -46.984631\nv -17.101007 100 -46.984631\nv -25 0 -43.30127\nv -25 100 -43.30127\nv -32.13938 0 -38.302222\nv -32.13938 100 -38.302222\nv -38.302222 0 -32.13938\nv -38.302222 100 -32.13938\nv -43.30127 0 -25\nv -43.30127 100 -25\nv -46.984631 0 -17.101007\nv -46.984631 100 -17.101007\nv -49.240388 0 -8.682409\nv -49.240388 100 -8.682409\nv -50 0 0\nv -50 100 0\nv -49.240388 0 8.682409\nv -49.240388 100 8.682409\nv -46.984631 0 17.101007\nv -46.984631 100 17.101007\nv -43.30127 0 25\nv -43.30127 100 25\nv -38.302222 0 32.13938\nv -38.302222 100 32.13938\nv -32.13938 0 38.302222\nv -32.13938 100 38.302222\nv -25 0 43.30127\nv -25 100 43.30127\nv -17.101007 0 46.984631\nv -17.101007 100 46.984631\nv -8.682409 0 49.240388\nv -8.682409 100 49.240388\nv 0 0 50\nv 0 100 50\nv 8.682409 0 49.240388\nv 8.682409 100 49.240388\nv 17.101007 0 46.984631\nv 17.101007 100 46.984631\nv 25 0 43.30127\nv 25 100 43.30127\nv 32.13938 0 38.302222\nv 32.13938 100 38.302222\nv 38.302222 0 32.13938\nv 38.302222 100 32.13938\nv 43.30127 0 25\nv 43.30127 100 25\nv 46.984631 0 17.101007\nv 46.984631 100 17.101007\nv 49.240388 0 8.682409\nv 49.240388 100 8.682409\nv 102.850059 0 109.0129\nv 109.0129 0 102.850059\nv 89.497024 0 -120.217427\nv 82.357644 0 -125.216475\nv 82.357644 0 125.216475\nv 89.497024 0 120.217427\nv 67.261826 0 -133.932049\nv 59.362833 0 -137.61541\nv 59.362833 0 137.61541\nv 67.261826 0 133.932049\nv 42.982912 0 -143.577214\nv 34.564313 0 -145.83297\nv 34.564313 0 145.83297\nv 42.982912 0 143.577214\nv 17.397983 0 -148.859857\nv 8.715574 0 -149.61947\nv 8.715574 0 149.61947\nv 17.397983 0 148.859857\nv -8.715574 0 -149.61947\nv -17.397983 0 -148.859857\nv -17.397983 0 148.859857\nv -8.715574 0 149.61947\nv -34.564313 0 -145.83297\nv -42.982912 0 -143.577214\nv -42.982912 0 143.577214\nv -34.564313 0 145.83297\nv -59.362833 0 -137.61541\nv -67.261826 0 -133.932049\nv -67.261826 0 133.932049\nv -59.362833 0 137.61541\nv -82.357644 0 -125.216475\nv -89.497024 0 -120.217427\nv -89.497024 0 120.217427\nv -82.357644 0 125.216475\nv -102.850059 0 -109.0129\nv -109.0129 0 -102.850059\nv 149.61947 0 -8.715574\nv -109.0129 0 102.850059\nv -102.850059 0 109.0129\nv 148.859857 0 -17.397983\nv 148.859857 0 17.397983\nv -120.217427 0 -89.497024\nv -125.216475 0 -82.357644\nv 149.61947 0 8.715574\nv 145.83297 0 -34.564313\nv -125.216475 0 82.357644\nv -120.217427 0 89.497024\nv 143.577214 0 -42.982912\nv 143.577214 0 42.982912\nv -133.932049 0 -67.261826\nv -137.61541 0 -59.362833\nv 145.83297 0 34.564313\nv 137.61541 0 -59.362833\nv -137.61541 0 59.362833\nv -133.932049 0 67.261826\nv 133.932049 0 -67.261826\nv 133.932049 0 67.261826\nv -143.577214 0 -42.982912\nv -145.83297 0 -34.564313\nv 137.61541 0 59.362833\nv 125.216475 0 -82.357644\nv -145.83297 0 34.564313\nv -143.577214 0 42.982912\nv 120.217427 0 -89.497024\nv 120.217427 0 89.497024\nv -148.859857 0 -17.397983\nv -149.61947 0 -8.715574\nv 125.216475 0 82.357644\nv 109.0129 0 -102.850059\nv -149.61947 0 8.715574\nv -148.859857 0 17.397983\nv 102.850059 0 -109.0129\n\nvt 1 0.25 0\nvt 0 0.25 0\nvt 1 0.5 0\nvt 0 0.5 0\nvt 1 1 0\nvt 0 1 0\nvt 0.027778 0.25 0\nvt 0.027778 0.5 0\nvt 0.027778 1 0\nvt 0.055556 0.25 0\nvt 0.055556 0.5 0\nvt 0.055556 1 0\nvt 0.083333 0.25 0\nvt 0.083333 0.5 0\nvt 0.083333 1 0\nvt 0.111111 0.25 0\nvt 0.111111 0.5 0\nvt 0.111111 1 0\nvt 0.138889 0.25 0\nvt 0.138889 0.5 0\nvt 0.138889 1 0\nvt 0.166667 0.25 0\nvt 0.166667 0.5 0\nvt 0.166667 1 0\nvt 0.194444 0.25 0\nvt 0.194444 0.5 0\nvt 0.194444 1 0\nvt 0.222222 0.25 0\nvt 0.222222 0.5 0\nvt 0.222222 1 0\nvt 0.25 0.25 0\nvt 0.25 0.5 0\nvt 0.25 1 0\nvt 0.277778 0.25 0\nvt 0.277778 0.5 0\nvt 0.277778 1 0\nvt 0.305556 0.25 0\nvt 0.305556 0.5 0\nvt 0.305556 1 0\nvt 0.333333 0.25 0\nvt 0.333333 0.5 0\nvt 0.333333 1 0\nvt 0.361111 0.25 0\nvt 0.361111 0.5 0\nvt 0.361111 1 0\nvt 0.388889 0.25 0\nvt 0.388889 0.5 0\nvt 0.388889 1 0\nvt 0.416667 0.25 0\nvt 0.416667 0.5 0\nvt 0.416667 1 0\nvt 0.444444 0.25 0\nvt 0.444444 0.5 0\nvt 0.444444 1 0\nvt 0.472222 0.25 0\nvt 0.472222 0.5 0\nvt 0.472222 1 0\nvt 0.5 0.25 0\nvt 0.5 0.5 0\nvt 0.5 1 0\nvt 0.527778 0.25 0\nvt 0.527778 0.5 0\nvt 0.527778 1 0\nvt 0.555556 0.25 0\nvt 0.555556 0.5 0\nvt 0.555556 1 0\nvt 0.583333 0.25 0\nvt 0.583333 0.5 0\nvt 0.583333 1 0\nvt 0.611111 0.25 0\nvt 0.611111 0.5 0\nvt 0.611111 1 0\nvt 0.638889 0.25 0\nvt 0.638889 0.5 0\nvt 0.638889 1 0\nvt 0.666667 0.25 0\nvt 0.666667 0.5 0\nvt 0.666667 1 0\nvt 0.694444 0.25 0\nvt 0.694444 0.5 0\nvt 0.694444 1 0\nvt 0.722222 0.25 0\nvt 0.722222 0.5 0\nvt 0.722222 1 0\nvt 0.75 0.25 0\nvt 0.75 0.5 0\nvt 0.75 1 0\nvt 0.777778 0.25 0\nvt 0.777778 0.5 0\nvt 0.777778 1 0\nvt 0.805556 0.25 0\nvt 0.805556 0.5 0\nvt 0.805556 1 0\nvt 0.833333 0.25 0\nvt 0.833333 0.5 0\nvt 0.833333 1 0\nvt 0.861111 0.25 0\nvt 0.861111 0.5 0\nvt 0.861111 1 0\nvt 0.888889 0.25 0\nvt 0.888889 0.5 0\nvt 0.888889 1 0\nvt 0.916667 0.25 0\nvt 0.916667 0.5 0\nvt 0.916667 1 0\nvt 0.944444 0.25 0\nvt 0.944444 0.5 0\nvt 0.944444 1 0\nvt 0.972222 0.25 0\nvt 0.972222 0.5 0\nvt 0.972222 1 0\nvt 0.870372 0.25 0\nvt 0.879628 0.25 0\nvt 0.148149 0.25 0\nvt 0.157406 0.25 0\nvt 0.842594 0.25 0\nvt 0.851851 0.25 0\nvt 0.175927 0.25 0\nvt 0.185184 0.25 0\nvt 0.814816 0.25 0\nvt 0.824073 0.25 0\nvt 0.203705 0.25 0\nvt 0.212962 0.25 0\nvt 0.787038 0.25 0\nvt 0.796295 0.25 0\nvt 0.231483 0.25 0\nvt 0.240739 0.25 0\nvt 0.759261 0.25 0\nvt 0.768517 0.25 0\nvt 0.259261 0.25 0\nvt 0.268517 0.25 0\nvt 0.731483 0.25 0\nvt 0.740739 0.25 0\nvt 0.287038 0.25 0\nvt 0.296295 0.25 0\nvt 0.703705 0.25 0\nvt 0.712962 0.25 0\nvt 0.314816 0.25 0\nvt 0.324073 0.25 0\nvt 0.675927 0.25 0\nvt 0.685184 0.25 0\nvt 0.342594 0.25 0\nvt 0.351851 0.25 0\nvt 0.648149 0.25 0\nvt 0.657406 0.25 0\nvt 0.370372 0.25 0\nvt 0.379628 0.25 0\nvt 1.009261 0.25 0\nvt 0.009261 0.25 0\nvt 0.620372 0.25 0\nvt 0.629628 0.25 0\nvt 0.018517 0.25 0\nvt 0.981483 0.25 0\nvt 0.398149 0.25 0\nvt 0.407406 0.25 0\nvt 0.990739 0.25 0\nvt 0.037038 0.25 0\nvt 0.592594 0.25 0\nvt 0.601851 0.25 0\nvt 0.046295 0.25 0\nvt 0.953705 0.25 0\nvt 0.425927 0.25 0\nvt 0.435184 0.25 0\nvt 0.962962 0.25 0\nvt 0.064816 0.25 0\nvt 0.564816 0.25 0\nvt 0.574073 0.25 0\nvt 0.074073 0.25 0\nvt 0.925927 0.25 0\nvt 0.453705 0.25 0\nvt 0.462962 0.25 0\nvt 0.935184 0.25 0\nvt 0.092594 0.25 0\nvt 0.537038 0.25 0\nvt 0.546295 0.25 0\nvt 0.101851 0.25 0\nvt 0.898149 0.25 0\nvt 0.481483 0.25 0\nvt 0.490739 0.25 0\nvt 0.907406 0.25 0\nvt 0.120372 0.25 0\nvt 0.509261 0.25 0\nvt 0.518517 0.25 0\nvt 0.129628 0.25 0\n\nf 1/4 2/6 4/9 3/8 \nf 3/8 4/9 6/12 5/11 \nf 5/11 6/12 8/15 7/14 \nf 7/14 8/15 10/18 9/17 \nf 9/17 10/18 12/21 11/20 \nf 11/20 12/21 14/24 13/23 \nf 13/23 14/24 16/27 15/26 \nf 15/26 16/27 18/30 17/29 \nf 17/29 18/30 20/33 19/32 \nf 19/32 20/33 22/36 21/35 \nf 21/35 22/36 24/39 23/38 \nf 23/38 24/39 26/42 25/41 \nf 25/41 26/42 28/45 27/44 \nf 27/44 28/45 30/48 29/47 \nf 29/47 30/48 32/51 31/50 \nf 31/50 32/51 34/54 33/53 \nf 33/53 34/54 36/57 35/56 \nf 35/56 36/57 38/60 37/59 \nf 37/59 38/60 40/63 39/62 \nf 39/62 40/63 42/66 41/65 \nf 41/65 42/66 44/69 43/68 \nf 43/68 44/69 46/72 45/71 \nf 45/71 46/72 48/75 47/74 \nf 47/74 48/75 50/78 49/77 \nf 49/77 50/78 52/81 51/80 \nf 51/80 52/81 54/84 53/83 \nf 53/83 54/84 56/87 55/86 \nf 55/86 56/87 58/90 57/89 \nf 57/89 58/90 60/93 59/92 \nf 59/92 60/93 62/96 61/95 \nf 61/95 62/96 64/99 63/98 \nf 63/98 64/99 66/102 65/101 \nf 65/101 66/102 68/105 67/104 \nf 67/104 68/105 70/108 69/107 \nf 69/107 70/108 72/111 71/110 \nf 71/110 72/111 2/5 1/3 \nf 1/2 109/149 112/152 3/7 \nf 71/109 113/153 116/156 1/1 \nf 3/7 117/157 120/160 5/10 \nf 69/106 121/161 124/164 71/109 \nf 5/10 125/165 128/168 7/13 \nf 67/103 129/169 132/172 69/106 \nf 7/13 133/173 136/176 9/16 \nf 65/100 137/177 140/180 67/103 \nf 9/16 141/181 144/184 11/19 \nf 63/97 73/112 74/113 65/100 \nf 11/19 75/114 76/115 13/22 \nf 61/94 77/116 78/117 63/97 \nf 13/22 79/118 80/119 15/25 \nf 59/91 81/120 82/121 61/94 \nf 15/25 83/122 84/123 17/28 \nf 57/88 85/124 86/125 59/91 \nf 17/28 87/126 88/127 19/31 \nf 55/85 89/128 90/129 57/88 \nf 19/31 91/130 92/131 21/34 \nf 53/82 93/132 94/133 55/85 \nf 21/34 95/134 96/135 23/37 \nf 51/79 97/136 98/137 53/82 \nf 23/37 99/138 100/139 25/40 \nf 49/76 101/140 102/141 51/79 \nf 25/40 103/142 104/143 27/43 \nf 47/73 105/144 106/145 49/76 \nf 27/43 107/146 108/147 29/46 \nf 45/70 110/150 111/151 47/73 \nf 29/46 114/154 115/155 31/49 \nf 43/67 118/158 119/159 45/70 \nf 31/49 122/162 123/163 33/52 \nf 41/64 126/166 127/167 43/67 \nf 33/52 130/170 131/171 35/55 \nf 39/61 134/174 135/175 41/64 \nf 35/55 138/178 139/179 37/58 \nf 37/58 142/182 143/183 39/61 \nf 121/161 69/106 132/172 \nf 129/169 67/103 140/180 \nf 137/177 65/100 74/113 \nf 73/112 63/97 78/117 \nf 77/116 61/94 82/121 \nf 81/120 59/91 86/125 \nf 85/124 57/88 90/129 \nf 113/153 71/109 124/164 \nf 109/148 1/1 116/156 \nf 117/157 3/7 112/152 \nf 125/165 5/10 120/160 \nf 133/173 7/13 128/168 \nf 141/181 9/16 136/176 \nf 75/114 11/19 144/184 \nf 79/118 13/22 76/115 \nf 83/122 15/25 80/119 \nf 87/126 17/28 84/123 \nf 91/130 19/31 88/127 \nf 95/134 21/34 92/131 \nf 99/138 23/37 96/135 \nf 103/142 25/40 100/139 \nf 107/146 27/43 104/143 \nf 114/154 29/46 108/147 \nf 122/162 31/49 115/155 \nf 130/170 33/52 123/163 \nf 138/178 35/55 131/171 \nf 142/182 37/58 139/179 \nf 134/174 39/61 143/183 \nf 126/166 41/64 135/175 \nf 118/158 43/67 127/167 \nf 110/150 45/70 119/159 \nf 105/144 47/73 111/151 \nf 101/140 49/76 106/145 \nf 97/136 51/79 102/141 \nf 93/132 53/82 98/137 \nf 89/128 55/85 94/133 \n\n"
  },
  {
    "path": "static/models/controller.dae",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<COLLADA xmlns=\"http://www.collada.org/2008/03/COLLADASchema\" version=\"1.5.0\">\n\t<asset>\n\t\t<contributor>\n\t\t\t<authoring_tool>CINEMA4D 16.050 COLLADA Exporter</authoring_tool>\n\t\t</contributor>\n\t\t<created>2017-02-21T20:23:28Z</created>\n\t\t<modified>2017-02-21T20:23:28Z</modified>\n\t\t<unit meter=\"0.01\" name=\"centimeter\"/>\n\t\t<up_axis>Y_UP</up_axis>\n\t</asset>\n\t<library_effects>\n\t\t<effect id=\"ID2\">\n\t\t\t<profile_COMMON>\n\t\t\t\t<technique sid=\"COMMON\">\n\t\t\t\t\t<blinn>\n\t\t\t\t\t\t<diffuse>\n\t\t\t\t\t\t\t<color>0.072 0.072 0.072 1</color>\n\t\t\t\t\t\t</diffuse>\n\t\t\t\t\t\t<specular>\n\t\t\t\t\t\t\t<color>0.2 0.2 0.2 1</color>\n\t\t\t\t\t\t</specular>\n\t\t\t\t\t\t<shininess>\n\t\t\t\t\t\t\t<float>0.5</float>\n\t\t\t\t\t\t</shininess>\n\t\t\t\t\t</blinn>\n\t\t\t\t</technique>\n\t\t\t</profile_COMMON>\n\t\t</effect>\n\t\t<effect id=\"ID4\">\n\t\t\t<profile_COMMON>\n\t\t\t\t<technique sid=\"COMMON\">\n\t\t\t\t\t<blinn>\n\t\t\t\t\t\t<diffuse>\n\t\t\t\t\t\t\t<color>0.8 0.8 0.8 1</color>\n\t\t\t\t\t\t</diffuse>\n\t\t\t\t\t\t<specular>\n\t\t\t\t\t\t\t<color>0.2 0.2 0.2 1</color>\n\t\t\t\t\t\t</specular>\n\t\t\t\t\t\t<shininess>\n\t\t\t\t\t\t\t<float>0.5</float>\n\t\t\t\t\t\t</shininess>\n\t\t\t\t\t</blinn>\n\t\t\t\t</technique>\n\t\t\t</profile_COMMON>\n\t\t</effect>\n\t</library_effects>\n\t<library_materials>\n\t\t<material id=\"ID1\" name=\"Mat\">\n\t\t\t<instance_effect url=\"#ID2\"/>\n\t\t</material>\n\t\t<material id=\"ID3\" name=\"Bezel\">\n\t\t\t<instance_effect url=\"#ID4\"/>\n\t\t</material>\n\t</library_materials>\n\t<library_geometries>\n\t\t<geometry id=\"ID7\">\n\t\t\t<mesh>\n\t\t\t\t<source id=\"ID8\">\n\t\t\t\t\t<float_array id=\"ID9\" count=\"264\" digits=\"2490374\">-2.2549 -0.1485 5.3795 -2.2535 -0.1485 4.2118 -1.8888 -0.1485 3.2558 -1.0171 -0.1485 2.5711 1.0171 -0.1485 2.5711 1.8888 -0.1485 3.2558 2.2535 -0.1485 4.2118 2.2549 -0.1485 5.3795 -0.9548 -0.1485 17.8157 -1.422 0.3515 17.186 0 0.3515 2.3695 1.0171 0.3515 2.5711 1.8888 0.3515 3.2558 2.2535 0.3515 4.2118 2.2549 0.3515 5.3795 1.422 0.3515 17.186 0.9548 0.3515 17.8157 0 0.3515 18.0805 -1.422 -0.1485 17.186 0 -0.1485 2.3695 1.422 -0.1485 17.186 0.9548 -0.1485 17.8157 0 -0.1485 18.0805 -2.2549 0.3515 5.3795 -2.2535 0.3515 4.2118 -1.8888 0.3515 3.2558 -1.0171 0.3515 2.5711 -0.9548 0.3515 17.8157 -2.0245 -0.1485 8.64552 2.0245 -0.1485 8.64552 -2.0245 0.3515 8.64552 2.0245 0.3515 8.64552 -2.14437 -0.1485 6.94628 2.14437 -0.1485 6.94628 2.14437 0.3515 6.94628 -2.14437 0.3515 6.94628 -1.48128 0.3515 16.3457 1.48128 0.3515 16.3457 -1.48128 -0.1485 16.3457 1.48128 -0.1485 16.3457 -1.33212 -1.14635 5.3795 -1.27843 -1.30407 4.2118 -1.61681 -0.76715 3.34645 -1.0171 -0.621514 2.66175 1.0171 -0.621514 2.66175 1.61681 -0.76715 3.34645 1.27843 -1.30407 4.2118 1.33212 -1.14635 5.3795 -0.9548 -0.520102 17.5876 -0.991745 -1.06956 16.9579 0 -0.621514 2.46015 0.991745 -1.06956 16.9579 0.9548 -0.520102 17.5876 0 -0.520102 17.8524 -1.57086 -1.55864 8.64552 1.57086 -1.55864 8.64552 -1.98998 -0.744035 6.94628 1.98998 -0.744035 6.94628 -1.0843 -1.14109 16.3457 1.0843 -1.14109 16.3457 -1.79939 -0.1485 11.8365 1.79942 -0.1485 11.836 -1.79939 0.3515 11.8365 1.79939 0.3515 11.8365 -1.02835 -1.73453 11.8365 1.02835 -1.73453 11.8365 -2.2549 0.0655521 5.3795 -2.2535 0.0655521 4.2118 -1.8888 0.0655521 3.2558 -1.0171 0.0655521 2.5711 1.0171 0.0655521 2.5711 1.8888 0.0655521 3.2558 2.2535 0.0655521 4.2118 2.2549 0.0655521 5.3795 -0.9548 0.0655521 17.8157 -1.422 0.0655521 17.186 0 0.0655521 2.3695 1.422 0.0655521 17.186 0.9548 0.0655521 17.8157 0 0.0655521 18.0805 -2.0245 0.0655521 8.64552 2.0245 0.0655521 8.64552 -2.14437 0.0655521 6.94628 2.14437 0.0655521 6.94628 -1.48128 0.0655521 16.3457 1.48128 0.0655521 16.3457 -1.79939 0.0655521 11.8365 1.79941 0.0655521 11.8362</float_array>\n\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t<accessor count=\"88\" source=\"#ID9\" stride=\"3\">\n\t\t\t\t\t\t\t<param name=\"X\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Y\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Z\" type=\"float\"/>\n\t\t\t\t\t\t</accessor>\n\t\t\t\t\t</technique_common>\n\t\t\t\t</source>\n\t\t\t\t<source id=\"ID10\">\n\t\t\t\t\t<float_array id=\"ID11\" count=\"243\" digits=\"2490374\">-0.974563 -0.208981 0.080959 -0.977375 -0.197688 0.0752136 -0.997521 0 0.070371 -0.997521 0 0.070371 -0.983335 0 -0.181805 -0.983335 0 -0.181805 -0.999401 0 0.0346082 -0.999401 0 0.0346082 -0.935838 -0.320969 -0.145552 -0.802917 -0.215496 -0.555775 -0.805244 0 -0.592943 -0.417551 0 -0.908654 -0.426188 -0.117689 -0.896947 0 -0.094567 -0.995519 3.79776e-09 0 -1 0.417551 0 -0.908654 0 0 -1 0.432786 -0.11228 -0.894477 0.827368 -0.206634 -0.522269 0.805244 0 -0.592943 0.983335 0 -0.181805 0.983335 0 -0.181805 0.805244 0 -0.592943 0.999401 0 0.0346082 0.999401 0 0.0346082 0.997521 0 0.070371 0.915198 -0.213152 0.342021 0.561269 -0.249455 0.789145 0.565883 0 0.824485 0.937863 0 0.347005 0 -0.273096 0.961987 0 0 1 -0.565883 0 0.824485 -0.610889 -0.220693 0.760335 -0.937766 -0.181729 0.295922 -0.937863 0 0.347005 -0.565343 -0.745683 0.352624 -0.455322 -0.596463 0.660994 0.0232068 -0.660211 0.750721 0.412042 -0.782451 0.466897 0.41247 -0.581806 0.700979 0 -0.995218 -0.0976834 0.462019 -0.880465 0.106398 -0.460352 -0.884432 0.0765248 0.394919 -0.903483 -0.166606 -0.420097 -0.8866 -0.193544 0.636504 -0.621899 -0.456184 0.384969 -0.492232 -0.780709 0.0398219 -0.62209 -0.781933 -0.32359 -0.630516 -0.705507 -0.626206 -0.603683 -0.493389 0 1 -0 -0.997521 0 0.070371 -0.997521 0 0.070371 0.937863 0 0.347005 0.997521 2.30563e-08 0.070371 0.997521 -2.98855e-08 0.070371 0 -0.968653 -0.248418 0 -0.999282 0.037886 0.979776 -0.193463 0.0510887 0.982816 -0.164421 0.0839002 0.997521 -3.2879e-08 0.070371 0.997521 4.43539e-08 0.070371 0.997521 -6.27645e-08 0.070371 0 -0.992374 0.123262 -0.980656 -0.194762 0.0195223 -0.922808 -0.383862 -0.0327739 -0.953396 -0.301488 -0.0118799 0.953773 -0.300098 0.0161091 0.924102 -0.380991 0.0296838 0.942695 -0.314751 -0.110717 0.918888 -0.387507 0.0740439 0.911178 -0.402854 0.086397 0.975314 -0.205782 0.0801021 0.977509 -0.197613 0.0736611 -0.908355 -0.408845 0.0879633 -0.918574 -0.38766 0.0770846 0.941081 -0.324524 0.09514 -0.982972 -0.164557 0.0817758 -0.941396 -0.324809 0.0909489 0.997521 2.12976e-08 0.070371</float_array>\n\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t<accessor count=\"81\" source=\"#ID11\" stride=\"3\">\n\t\t\t\t\t\t\t<param name=\"X\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Y\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Z\" type=\"float\"/>\n\t\t\t\t\t\t</accessor>\n\t\t\t\t\t</technique_common>\n\t\t\t\t</source>\n\t\t\t\t<source id=\"ID12\">\n\t\t\t\t\t<float_array id=\"ID13\" count=\"184\" digits=\"2490374\">0.0234698 1 0.149406 1 0.149406 0.571896 0.0234698 0.571896 0.362276 0.571896 0.362276 0 0.329744 0 0.329744 0.571896 0.362276 1 0.390782 1 0.390782 0.571896 0.421663 0.571896 0.421663 0 0.390782 0 0.421663 1 0.450551 1 0.450551 0.571896 0.479438 0.571896 0.479438 0 0.450551 0 0.479438 1 0.51032 1 0.51032 0.571896 0.538826 0.571896 0.538826 0 0.51032 0 0.571358 0.571896 0.571358 0 0.615116 0.571896 0.615116 0 0.901102 1 0.922946 1 0.922946 0.571896 0.901102 0.571896 0.950551 1 0.950551 0.571896 0.978155 0.571896 0.978155 0 0.950551 0 0.978155 1 1 1 1 0.571896 0.0530138 0.943065 0.0827509 0.983146 0.143524 1 0.234033 0.943065 0.204296 0.983146 0.280012 0.291311 0.287047 0.191586 0 0.191586 0.0070352 0.291311 0.286958 0.117262 8.91095e-05 0.117262 0.263745 0.0564127 0.208262 0.0128318 0.143524 0 0.0787856 0.0128318 0.0233021 0.0564127 0.0492405 0.889578 0.237807 0.889578 0.285985 0.571896 0.285985 0 0.238527 0 0.238527 0.571896 0.901102 0 0.877632 0 0.877632 0.571896 0.0146652 0.399467 0.0289934 0.602571 0.258054 0.602571 0.272382 0.399467 0.615116 1 0.662575 1 0.662575 0.571896 0.0234698 0 0 0 0 0.571896 0.751695 0 0.751688 0.571896 0.258056 0.60254 0.149406 0 0.751682 1 0.329744 1 0.538826 1 0.571358 1 0.922946 0 1 0 0.238527 1 0.285985 1 0.877632 1 0.662575 0 0 1</float_array>\n\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t<accessor count=\"92\" source=\"#ID13\" stride=\"2\">\n\t\t\t\t\t\t\t<param name=\"S\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"T\" type=\"float\"/>\n\t\t\t\t\t\t</accessor>\n\t\t\t\t\t</technique_common>\n\t\t\t\t</source>\n\t\t\t\t<vertices id=\"ID14\">\n\t\t\t\t\t<input semantic=\"POSITION\" source=\"#ID8\"/>\n\t\t\t\t</vertices>\n\t\t\t\t<triangles count=\"44\" material=\"Material2\">\n\t\t\t\t\t<input offset=\"0\" semantic=\"VERTEX\" source=\"#ID14\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"1\" semantic=\"NORMAL\" source=\"#ID10\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"2\" semantic=\"TEXCOORD\" source=\"#ID12\" set=\"0\"/>\n\t\t\t\t\t<p>86 2 2 60 1 1 38 0 0 84 3 3 86 2 2 38 0 0 68 10 10 2 9 9 1 8 8 67 4 4 68 10 10 1 8 8 76 14 16 19 13 15 3 12 14 69 11 11 76 14 16 3 12 14 71 19 22 5 18 21 4 17 20 70 15 17 71 19 22 4 17 20 78 28 32 21 27 31 20 26 30 77 29 33 78 28 32 20 26 30 79 31 35 22 30 34 21 27 31 78 28 32 79 31 35 21 27 31 75 35 41 18 34 40 8 33 39 74 32 36 75 35 41 8 33 39 81 61 73 29 60 72 33 59 71 83 25 28 81 61 73 33 59 71 61 74 81 29 60 72 81 61 73 87 63 78 61 74 81 81 61 73 1 8 8 0 67 82 66 7 7 67 4 4 1 8 8 66 7 7 3 12 14 2 9 9 68 10 10 69 11 11 3 12 14 68 10 10 4 17 20 19 13 15 76 14 16 70 15 17 4 17 20 76 14 16 6 70 83 5 18 21 71 19 22 72 20 23 6 70 83 71 19 22 7 68 84 6 70 83 72 20 23 73 23 26 7 68 84 72 20 23 33 59 71 7 68 84 73 23 26 83 25 28 33 59 71 73 23 26 8 33 39 22 30 34 79 31 35 74 32 36 8 33 39 79 31 35 32 65 88 28 78 87 80 53 63 82 52 60 32 65 88 80 53 63 20 26 30 39 73 89 85 56 66 77 29 33 20 26 30 85 56 66 0 67 82 32 65 88 82 52 60 66 7 7 0 67 82 82 52 60 38 0 0 18 34 91 75 35 76 84 3 3 38 0 0 75 35 76 39 73 89 61 74 81 87 63 78 85 56 66 39 73 89 87 63 78 28 78 87 60 1 1 86 2 2 80 53 63 28 78 87 86 2 2</p>\n\t\t\t\t</triangles>\n\t\t\t\t<triangles count=\"128\" material=\"Material1\">\n\t\t\t\t\t<input offset=\"0\" semantic=\"VERTEX\" source=\"#ID14\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"1\" semantic=\"NORMAL\" source=\"#ID10\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"2\" semantic=\"TEXCOORD\" source=\"#ID12\" set=\"0\"/>\n\t\t\t\t\t<p>23 6 6 24 5 5 67 4 4 66 7 7 23 6 6 67 4 4 25 10 13 26 11 12 69 11 11 68 10 10 25 10 13 69 11 11 10 16 19 11 15 18 70 15 17 76 14 16 10 16 19 70 15 17 12 22 25 13 21 24 72 20 23 71 19 22 12 22 25 72 20 23 13 21 24 14 24 27 73 23 26 72 20 23 13 21 24 73 23 26 14 24 27 34 25 29 83 25 28 73 23 26 14 24 27 83 25 28 17 31 38 27 32 37 74 32 36 79 31 35 17 31 38 74 32 36 53 38 44 48 37 43 49 36 42 51 39 45 53 38 44 49 36 42 51 39 45 52 40 46 53 38 44 40 43 49 47 42 48 57 41 47 56 41 50 40 43 49 57 41 47 46 44 51 47 42 48 40 43 49 41 45 52 46 44 51 40 43 49 44 47 54 45 46 53 46 44 51 50 48 55 44 47 54 46 44 51 50 48 55 46 44 51 41 45 52 43 49 56 50 48 55 41 45 52 41 45 52 42 50 57 43 49 56 37 51 59 36 51 58 9 51 42 15 51 45 37 51 59 9 51 42 15 51 45 9 51 42 17 51 44 16 51 46 15 51 45 17 51 44 27 51 43 17 51 44 9 51 42 24 51 52 23 51 49 14 51 48 13 51 51 24 51 52 14 51 48 26 51 56 25 51 57 24 51 52 10 51 55 26 51 56 24 51 52 10 51 55 24 51 52 13 51 51 11 51 54 10 51 55 13 51 51 13 51 51 12 51 53 11 51 54 30 53 62 35 52 61 82 52 60 80 53 63 30 53 62 82 52 60 37 55 65 15 54 64 77 29 33 85 56 66 37 55 65 77 29 33 65 58 69 64 58 68 54 57 67 55 57 70 65 58 69 54 57 67 14 51 48 23 51 49 35 51 50 34 51 47 14 51 48 35 51 50 55 57 70 54 57 67 56 41 50 57 41 47 55 57 70 56 41 50 35 52 61 23 6 6 66 7 7 82 52 60 35 52 61 66 7 7 30 51 67 31 51 70 34 51 47 35 51 50 30 51 67 34 51 47 75 35 76 9 35 75 36 3 74 84 3 3 75 35 76 36 3 74 63 51 69 62 51 68 36 51 58 37 51 59 63 51 69 36 51 58 87 63 78 63 62 77 37 55 65 85 56 66 87 63 78 37 55 65 51 39 45 49 36 42 58 64 58 59 64 59 51 39 45 58 64 58 48 37 43 53 38 44 22 30 44 8 33 43 48 37 43 22 30 44 49 36 42 48 37 43 8 33 43 18 34 42 49 36 42 8 33 43 52 40 46 51 39 45 20 26 45 21 27 46 52 40 46 20 26 45 53 38 44 52 40 46 21 27 46 22 30 44 53 38 44 21 27 46 40 43 49 56 66 50 32 65 50 0 67 49 40 43 49 32 65 50 57 69 47 47 42 48 7 68 48 33 59 47 57 69 47 7 68 48 41 45 52 40 43 49 0 67 49 1 8 52 41 45 52 0 67 49 47 42 48 46 44 51 6 70 51 7 68 48 47 42 48 6 70 51 44 47 54 50 48 55 19 13 55 4 17 54 44 47 54 19 13 55 45 46 53 44 47 54 4 17 54 5 18 53 45 46 53 4 17 54 46 44 51 45 46 53 5 18 53 6 70 51 46 44 51 5 18 53 50 48 55 43 49 56 3 12 56 19 13 55 50 48 55 3 12 56 42 50 57 41 45 52 1 8 52 2 9 57 42 50 57 1 8 52 43 49 56 42 50 57 2 9 57 3 12 56 43 49 56 2 9 57 39 73 59 59 72 59 65 71 69 61 74 79 39 73 59 65 71 69 64 76 68 58 75 58 38 0 58 60 1 68 64 76 68 38 0 58 55 77 70 57 69 47 33 59 47 29 60 70 55 77 70 33 59 47 56 66 50 54 79 67 28 78 67 32 65 50 56 66 50 28 78 67 51 39 45 59 72 59 39 73 59 20 26 45 51 39 45 39 73 59 58 75 58 49 36 42 18 34 42 38 0 58 58 75 58 18 34 42 62 2 80 30 53 62 80 53 63 86 2 2 62 2 80 80 53 63 59 64 59 58 64 58 64 58 68 65 58 69 59 64 59 64 58 68 31 51 70 30 51 67 62 51 68 63 51 69 31 51 70 62 51 68 55 77 70 29 60 70 61 74 79 65 71 69 55 77 70 61 74 79 28 78 67 54 79 67 64 76 68 60 1 68 28 78 67 64 76 68 36 3 74 62 2 80 86 2 2 84 3 3 36 3 74 86 2 2 24 5 5 25 10 13 68 10 10 67 4 4 24 5 5 68 10 10 26 11 12 10 16 19 76 14 16 69 11 11 26 11 12 76 14 16 11 15 18 12 22 25 71 19 22 70 15 17 11 15 18 71 19 22 15 54 64 16 28 85 78 28 32 77 29 33 15 54 64 78 28 32 16 28 85 17 31 38 79 31 35 78 28 32 16 28 85 79 31 35 27 32 37 9 35 86 75 35 41 74 32 36 27 32 37 75 35 41 34 25 29 31 80 90 81 61 73 83 25 28 34 25 29 81 61 73 31 80 90 63 62 77 87 63 78 81 61 73 31 80 90 87 63 78</p>\n\t\t\t\t</triangles>\n\t\t\t</mesh>\n\t\t</geometry>\n\t</library_geometries>\n\t<library_visual_scenes>\n\t\t<visual_scene id=\"ID5\">\n\t\t\t<node id=\"ID6\" name=\"Extrude\">\n\t\t\t\t<translate sid=\"translate\">0 0 -0</translate>\n\t\t\t\t<rotate sid=\"rotateY\">0 1 0 -0</rotate>\n\t\t\t\t<rotate sid=\"rotateX\">1 0 0 0</rotate>\n\t\t\t\t<rotate sid=\"rotateZ\">0 0 1 -0</rotate>\n\t\t\t\t<scale sid=\"scale\">1 1 1</scale>\n\t\t\t\t<instance_geometry url=\"#ID7\">\n\t\t\t\t\t<bind_material>\n\t\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t\t<instance_material symbol=\"Material1\" target=\"#ID1\">\n\t\t\t\t\t\t\t\t<bind_vertex_input semantic=\"UVSET0\" input_semantic=\"TEXCOORD\" input_set=\"0\"/>\n\t\t\t\t\t\t\t</instance_material>\n\t\t\t\t\t\t\t<instance_material symbol=\"Material2\" target=\"#ID3\">\n\t\t\t\t\t\t\t\t<bind_vertex_input semantic=\"UVSET0\" input_semantic=\"TEXCOORD\" input_set=\"0\"/>\n\t\t\t\t\t\t\t</instance_material>\n\t\t\t\t\t\t</technique_common>\n\t\t\t\t\t</bind_material>\n\t\t\t\t</instance_geometry>\n\t\t\t</node>\n\t\t</visual_scene>\n\t</library_visual_scenes>\n\t<scene>\n\t\t<instance_visual_scene url=\"#ID5\"/>\n\t</scene>\n</COLLADA>\n"
  },
  {
    "path": "static/models/door-frame.obj",
    "content": "# Blender v2.76 (sub 0) OBJ File: ''\n# www.blender.org\no DoorFrame\nv 2.860165 0.000004 -0.260001\nv 2.704165 0.000004 -0.260001\nv 2.704165 5.412390 -0.260000\nv 2.860165 5.568390 -0.260000\nv 2.704165 0.000004 0.259999\nv 2.704165 5.412390 0.260000\nv 2.860165 0.000004 0.259999\nv 2.860165 5.568390 0.260000\nv -0.164165 5.412390 -0.260000\nv -0.320165 5.568390 -0.260000\nv -0.164165 5.412390 0.260000\nv -0.320165 5.568390 0.260000\nv -0.164165 0.000004 -0.260001\nv -0.320165 0.000004 -0.260001\nv -0.164165 0.000004 0.259999\nv -0.320165 0.000004 0.259999\nv 2.860165 0.000004 0.259999\nv 2.704165 0.000004 0.259999\nv 2.704165 0.000004 -0.260001\nv 2.860165 0.000004 -0.260001\nv -0.320165 0.000004 -0.260001\nv -0.164165 0.000004 -0.260001\nv -0.320165 0.000004 0.259999\nv -0.164165 0.000004 0.259999\nvn -0.000000 0.000000 -1.000000\nvn 0.000000 -1.000000 -0.000000\nvn 0.000000 -0.000000 1.000000\nvn -0.000000 1.000000 0.000000\nvn 1.000000 0.000000 -0.000000\nvn -1.000000 -0.000000 0.000000\ns 1\nf 1//1 2//1 3//1\nf 5//2 6//2 3//2\nf 7//3 8//3 6//3\nf 1//4 4//4 8//4\nf 3//1 9//1 10//1\nf 6//5 11//5 9//5\nf 8//3 12//3 11//3\nf 4//6 10//6 12//6\nf 9//1 13//1 14//1\nf 11//4 15//4 13//4\nf 11//3 12//3 16//3\nf 10//2 14//2 16//2\nf 17//5 18//5 19//5\nf 17//5 19//5 20//5\nf 21//5 22//5 23//5\nf 22//5 24//5 23//5\nf 4//1 1//1 3//1\nf 2//2 5//2 3//2\nf 5//3 7//3 6//3\nf 7//4 1//4 8//4\nf 4//1 3//1 10//1\nf 3//5 6//5 9//5\nf 6//3 8//3 11//3\nf 8//6 4//6 12//6\nf 10//1 9//1 14//1\nf 9//4 11//4 13//4\nf 15//3 11//3 16//3\nf 12//2 10//2 16//2\n"
  },
  {
    "path": "static/models/door-glow.obj",
    "content": "# Blender v2.78 (sub 0) OBJ File: ''\n# www.blender.org\n\no Glow.001_ID7.001\nv 3.023290 -0.039070 -1.075025\nv 3.033190 5.163200 -1.075235\nv -1.377980 -0.039075 0.000000\nv -1.377980 5.163200 0.000000\nv 1.370000 -0.039075 -0.255000\nv 1.370000 5.163200 -0.255000\nvt 0.0000 0.0000\nvt 0.0000 0.0000\nvt 0.0000 1.0000\nvt 0.0000 1.0000\nvt 1.0000 1.0000\nvt 1.0000 0.0000\nvn -0.2727 0.0003 -0.9621\nvn -0.0924 0.0000 -0.9957\nvn -0.2719 0.0000 -0.9623\nvn -0.4427 0.0002 -0.8966\nvn -0.4443 0.0008 -0.8958\n\ns 1\nf 5/1/1 3/2/2 4/3/2\nf 6/4/3 5/1/1 4/3/2\nf 2/5/4 1/6/5 5/1/1\nf 6/4/3 2/5/4 5/1/1\n"
  },
  {
    "path": "static/models/door.dae",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<COLLADA xmlns=\"http://www.collada.org/2008/03/COLLADASchema\" version=\"1.5.0\">\n\t<asset>\n\t\t<contributor>\n\t\t\t<authoring_tool>CINEMA4D 16.050 COLLADA Exporter</authoring_tool>\n\t\t</contributor>\n\t\t<created>2017-02-21T18:55:36Z</created>\n\t\t<modified>2017-02-21T18:55:36Z</modified>\n\t\t<unit meter=\"0.01\" name=\"centimeter\"/>\n\t\t<up_axis>Y_UP</up_axis>\n\t</asset>\n\t<library_effects>\n\t\t<effect id=\"ID2\">\n\t\t\t<profile_COMMON>\n\t\t\t\t<technique sid=\"COMMON\">\n\t\t\t\t\t<blinn>\n\t\t\t\t\t\t<diffuse>\n\t\t\t\t\t\t\t<color>0.8 0.8 0.8 1</color>\n\t\t\t\t\t\t</diffuse>\n\t\t\t\t\t\t<specular>\n\t\t\t\t\t\t\t<color>0.2 0.2 0.2 1</color>\n\t\t\t\t\t\t</specular>\n\t\t\t\t\t\t<shininess>\n\t\t\t\t\t\t\t<float>0.5</float>\n\t\t\t\t\t\t</shininess>\n\t\t\t\t\t</blinn>\n\t\t\t\t</technique>\n\t\t\t</profile_COMMON>\n\t\t</effect>\n\t\t<effect id=\"ID4\">\n\t\t\t<profile_COMMON>\n\t\t\t\t<technique sid=\"COMMON\">\n\t\t\t\t\t<blinn>\n\t\t\t\t\t\t<diffuse>\n\t\t\t\t\t\t\t<color>0.136 0.136 0.136 1</color>\n\t\t\t\t\t\t</diffuse>\n\t\t\t\t\t\t<specular>\n\t\t\t\t\t\t\t<color>0.2 0.2 0.2 1</color>\n\t\t\t\t\t\t</specular>\n\t\t\t\t\t\t<shininess>\n\t\t\t\t\t\t\t<float>0.5</float>\n\t\t\t\t\t\t</shininess>\n\t\t\t\t\t</blinn>\n\t\t\t\t</technique>\n\t\t\t</profile_COMMON>\n\t\t</effect>\n\t\t<effect id=\"ID6\">\n\t\t\t<profile_COMMON>\n\t\t\t\t<technique sid=\"COMMON\">\n\t\t\t\t\t<blinn>\n\t\t\t\t\t\t<diffuse>\n\t\t\t\t\t\t\t<color>0.8 0.8 0.8 1</color>\n\t\t\t\t\t\t</diffuse>\n\t\t\t\t\t\t<specular>\n\t\t\t\t\t\t\t<color>0.2 0.2 0.2 1</color>\n\t\t\t\t\t\t</specular>\n\t\t\t\t\t\t<shininess>\n\t\t\t\t\t\t\t<float>0.5</float>\n\t\t\t\t\t\t</shininess>\n\t\t\t\t\t</blinn>\n\t\t\t\t</technique>\n\t\t\t</profile_COMMON>\n\t\t</effect>\n\t</library_effects>\n\t<library_materials>\n\t\t<material id=\"ID1\" name=\"default\">\n\t\t\t<instance_effect url=\"#ID2\"/>\n\t\t</material>\n\t\t<material id=\"ID3\" name=\"Mat\">\n\t\t\t<instance_effect url=\"#ID4\"/>\n\t\t</material>\n\t\t<material id=\"ID5\" name=\"default\">\n\t\t\t<instance_effect url=\"#ID6\"/>\n\t\t</material>\n\t</library_materials>\n\t<library_geometries>\n\t\t<geometry id=\"ID9\">\n\t\t\t<mesh>\n\t\t\t\t<source id=\"ID10\">\n\t\t\t\t\t<float_array id=\"ID11\" count=\"768\" digits=\"2490374\">231.421 250.569 37.2362 14.7678 34.7234 -16.7227 244.453 242.515 -42.2138 239.475 229.484 24.2048 270.966 4.07145 -16.7227 270.966 542.094 -16.7227 143.006 34.7234 -16.7227 -16.2139 542.094 -16.7227 239.984 511.442 -16.7227 111.745 34.7234 -16.7227 247.529 234.461 -21.1285 -16.2139 4.07145 -16.7227 14.7678 214.474 -16.7227 252.507 237.537 -29.1824 247.529 234.461 -37.2362 234.498 242.515 -42.2138 14.7678 34.7234 -16.7227 239.475 229.484 -34.1599 231.421 234.461 -37.2362 143.006 34.7234 -16.7227 226.444 247.493 29.1824 111.745 34.7234 -16.7227 239.984 266.576 -16.7227 239.984 34.7234 -16.7227 143.006 511.442 -16.7227 14.7678 214.474 -16.7227 14.7678 266.576 -16.7227 143.006 214.474 -16.7227 226.444 237.537 -29.1824 231.421 250.569 21.1285 111.745 266.576 -16.7227 231.421 234.461 -21.1285 234.498 242.515 -16.151 244.453 242.515 -16.151 239.984 34.7234 -16.7227 143.006 214.474 -16.7227 111.745 511.442 -16.7227 239.984 511.442 -16.7227 239.984 266.576 -16.7227 143.006 511.442 -16.7227 14.7678 266.576 -16.7227 14.7678 511.442 -16.7227 111.745 266.576 -16.7227 14.7678 511.442 -16.7227 143.006 266.576 -16.7227 111.745 214.474 -16.7227 239.984 214.474 -16.7227 143.006 266.576 -16.7227 111.745 214.474 -16.7227 239.984 214.474 -16.7227 111.745 511.442 -16.7227 239.475 249.849 -17.3161 239.475 235.181 -17.3161 251.342 242.515 -21.8486 251.342 242.515 -36.5162 239.475 249.849 -41.0487 239.475 235.181 -41.0487 227.609 242.515 -36.5162 227.609 242.515 -21.8486 246.809 254.381 -29.1824 232.141 254.381 -29.1824 232.141 230.649 -29.1824 246.809 230.649 -29.1824 242.29 256.178 -29.1824 236.661 256.178 -29.1824 242.29 252.699 -20.0739 245.105 254.438 -24.6281 236.661 252.699 -20.0739 233.846 254.438 -24.6281 251.398 247.069 -23.553 249.659 251.623 -26.3677 244.029 248.144 -17.2592 248.584 245.33 -18.9988 251.398 247.069 -34.8117 249.659 251.623 -31.9971 253.138 242.515 -26.3677 253.138 242.515 -31.9971 242.29 252.699 -38.2909 245.105 254.438 -33.7366 248.584 245.33 -39.366 244.029 248.144 -41.1056 236.661 252.699 -38.2909 233.846 254.438 -33.7366 227.552 247.069 -34.8117 229.292 251.623 -31.9971 234.921 248.144 -41.1056 230.367 245.33 -39.366 227.552 247.069 -23.553 229.292 251.623 -26.3677 225.812 242.515 -31.9971 225.812 242.515 -26.3677 234.921 248.144 -17.2592 230.367 245.33 -18.9988 236.661 228.852 -29.1824 242.29 228.852 -29.1824 236.661 232.331 -20.0739 233.846 230.592 -24.6281 242.29 232.331 -20.0739 245.105 230.592 -24.6281 251.398 237.961 -23.553 249.659 233.406 -26.3677 244.029 236.886 -17.2592 248.584 239.7 -18.9988 251.398 237.961 -34.8117 249.659 233.406 -31.9971 248.584 239.7 -39.366 244.029 236.886 -41.1056 242.29 232.331 -38.2909 245.105 230.592 -33.7366 236.661 232.331 -38.2909 233.846 230.592 -33.7366 234.921 236.886 -41.1056 230.367 239.7 -39.366 227.552 237.961 -34.8117 229.292 233.406 -31.9971 227.552 237.961 -23.553 229.292 233.406 -26.3677 234.921 236.886 -17.2592 230.367 239.7 -18.9988 239.475 245.33 -15.5196 239.475 239.7 -15.5196 239.475 245.33 -42.8451 239.475 239.7 -42.8451 239.475 255.546 -24.2048 247.529 250.569 -21.1285 252.507 247.493 -29.1824 247.529 250.569 -37.2362 239.475 255.546 -34.1599 231.421 250.569 -37.2362 226.444 247.493 -29.1824 231.421 250.569 -21.1285 239.475 229.484 -24.2048 14.7678 34.7234 16.7227 244.453 242.515 42.2138 270.966 4.07145 16.7227 270.966 542.094 16.7227 143.006 34.7234 16.7227 -16.2139 542.094 16.7227 239.984 511.442 16.7227 111.745 34.7234 16.7227 247.529 234.461 21.1285 -16.2139 4.07145 16.7227 14.7678 214.474 16.7227 252.507 237.537 29.1824 247.529 234.461 37.2362 234.498 242.515 42.2138 14.7678 34.7234 16.7227 239.475 229.484 34.1599 231.421 234.461 37.2362 143.006 34.7234 16.7227 111.745 34.7234 16.7227 239.984 266.576 16.7227 239.984 34.7234 16.7227 143.006 511.442 16.7227 14.7678 214.474 16.7227 14.7678 266.576 16.7227 143.006 214.474 16.7227 226.444 237.537 29.1824 111.745 266.576 16.7227 231.421 234.461 21.1285 234.498 242.515 16.151 244.453 242.515 16.151 239.984 34.7234 16.7227 143.006 214.474 16.7227 111.745 511.442 16.7227 239.984 511.442 16.7227 239.984 266.576 16.7227 143.006 511.442 16.7227 14.7678 266.576 16.7227 14.7678 511.442 16.7227 111.745 266.576 16.7227 14.7678 511.442 16.7227 143.006 266.576 16.7227 111.745 214.474 16.7227 239.984 214.474 16.7227 143.006 266.576 16.7227 111.745 214.474 16.7227 239.984 214.474 16.7227 111.745 511.442 16.7227 239.475 249.849 17.3161 239.475 235.181 17.3161 251.342 242.515 21.8486 251.342 242.515 36.5162 239.475 249.849 41.0487 239.475 235.181 41.0487 227.609 242.515 36.5162 227.609 242.515 21.8486 246.809 254.381 29.1824 232.141 254.381 29.1824 232.141 230.649 29.1824 246.809 230.649 29.1824 242.29 256.178 29.1824 236.661 256.178 29.1824 242.29 252.699 20.0739 245.105 254.438 24.6281 236.661 252.699 20.0739 233.846 254.438 24.6281 251.398 247.069 23.553 249.659 251.623 26.3677 244.029 248.144 17.2592 248.584 245.33 18.9988 251.398 247.069 34.8117 249.659 251.623 31.9971 253.138 242.515 26.3677 253.138 242.515 31.9971 242.29 252.699 38.2909 245.105 254.438 33.7366 248.584 245.33 39.366 244.029 248.144 41.1056 236.661 252.699 38.2909 233.846 254.438 33.7366 227.552 247.069 34.8117 229.292 251.623 31.9971 234.921 248.144 41.1056 230.367 245.33 39.366 227.552 247.069 23.553 229.292 251.623 26.3677 225.812 242.515 31.9971 225.812 242.515 26.3677 234.921 248.144 17.2592 230.367 245.33 18.9988 236.661 228.852 29.1824 242.29 228.852 29.1824 236.661 232.331 20.0739 233.846 230.592 24.6281 242.29 232.331 20.0739 245.105 230.592 24.6281 251.398 237.961 23.553 249.659 233.406 26.3677 244.029 236.886 17.2592 248.584 239.7 18.9988 251.398 237.961 34.8117 249.659 233.406 31.9971 248.584 239.7 39.366 244.029 236.886 41.1056 242.29 232.331 38.2909 245.105 230.592 33.7366 236.661 232.331 38.2909 233.846 230.592 33.7366 234.921 236.886 41.1056 230.367 239.7 39.366 227.552 237.961 34.8117 229.292 233.406 31.9971 227.552 237.961 23.553 229.292 233.406 26.3677 234.921 236.886 17.2592 230.367 239.7 18.9988 239.475 245.33 15.5196 239.475 239.7 15.5196 239.475 245.33 42.8451 239.475 239.7 42.8451 239.475 255.546 24.2048 247.529 250.569 21.1285 252.507 247.493 29.1824 247.529 250.569 37.2362 239.475 255.546 34.1599</float_array>\n\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t<accessor count=\"256\" source=\"#ID11\" stride=\"3\">\n\t\t\t\t\t\t\t<param name=\"X\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Y\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Z\" type=\"float\"/>\n\t\t\t\t\t\t</accessor>\n\t\t\t\t\t</technique_common>\n\t\t\t\t</source>\n\t\t\t\t<source id=\"ID12\">\n\t\t\t\t\t<float_array id=\"ID13\" count=\"564\" digits=\"2490374\">0 0 -1 -2.57687e-07 0.195614 -0.980681 -2.52584e-07 -0.195614 -0.980681 0.356822 7.66799e-09 -0.934172 0.332085 0.400854 -0.853836 0.356822 0 -0.934172 0.648596 -0.20524 -0.732939 0.648596 0.20524 -0.732939 0.850651 -3.05074e-09 -0.525731 0.648596 0.20524 -0.732939 -2.55136e-07 0.195614 -0.980681 -2.55136e-07 -0.195614 -0.980681 -1.98298e-07 -0.525731 -0.850651 0.332085 -0.400854 -0.853836 0.332085 -0.400855 -0.853836 0.332085 0.400855 -0.853836 0.332085 0.400855 0.853836 -2.52584e-07 0.195614 0.980681 -1.92197e-07 0.525731 0.850651 0.648596 -0.20524 -0.732939 0.850651 -6.10148e-09 -0.525731 -1.98298e-07 0.525731 -0.850651 -0.332086 0.400854 -0.853836 -0.648596 0.20524 -0.732939 -0.356822 5.11199e-09 -0.934172 -0.850651 -3.05074e-09 -0.525731 -0.648596 -0.20524 -0.732939 0 0 -0 -1.89146e-07 -0.525731 -0.850651 -0.332086 -0.400854 -0.853835 -0.525732 0.85065 -1.83044e-08 -0.195615 0.980681 5.10272e-09 -0.400855 0.853835 0.332086 0.195615 0.980681 2.55136e-09 -1.84032e-07 0.934172 0.356823 -0.205241 0.73294 0.648595 0.525732 0.85065 -2.74567e-08 0.400855 0.853835 0.332085 0.20524 0.73294 0.648595 0.732939 0.648596 0.20524 0.853836 0.332086 0.400854 0.57735 0.577351 0.57735 0.850651 -6.10148e-09 0.525731 0.648596 0.20524 0.732939 0.732939 0.648596 -0.20524 0.853836 0.332086 -0.400854 0.934172 0.356823 2.556e-08 0.980681 -2.55136e-09 -0.195613 0.980681 -2.55136e-09 0.195613 0.400855 0.853835 -0.332085 0.20524 0.73294 -0.648595 0.57735 0.577351 -0.57735 -1.81476e-07 0.934172 -0.356823 -0.400855 0.853835 -0.332086 -0.205241 0.73294 -0.648595 -0.57735 0.57735 -0.57735 -0.732939 0.648596 -0.205241 -0.853835 0.332085 -0.400855 -0.980681 -2.55136e-09 -0.195614 -0.934173 0.356822 2.556e-08 -0.980681 -2.55136e-09 0.195614 -0.732939 0.648596 0.205241 -0.853835 0.332085 0.400855 -0.850651 -6.10148e-09 0.525731 -0.57735 0.57735 0.57735 -0.332086 0.400854 0.853836 -0.648596 0.20524 0.732939 0.525732 -0.85065 -2.13552e-08 0.195615 -0.980681 7.65408e-09 0.400855 -0.853835 0.332085 -0.195615 -0.980681 5.10272e-09 -1.81476e-07 -0.934172 0.356823 0.20524 -0.73294 0.648595 -0.525732 -0.85065 -1.52537e-08 -0.400855 -0.853835 0.332086 -0.205241 -0.73294 0.648595 -1.92197e-07 -0.525731 0.850651 0.853836 -0.332086 0.400854 0.648596 -0.20524 0.732939 0.732939 -0.648596 0.20524 0.57735 -0.577351 0.57735 0.332085 -0.400855 0.853836 0.934172 -0.356823 2.8116e-08 0.853836 -0.332086 -0.400854 0.732939 -0.648596 -0.20524 0.57735 -0.577351 -0.57735 0.20524 -0.73294 -0.648595 0.400855 -0.853835 -0.332085 -0.205241 -0.73294 -0.648595 -0.400855 -0.853835 -0.332086 -1.84032e-07 -0.934172 -0.356823 -0.57735 -0.57735 -0.57735 -0.853835 -0.332085 -0.400855 -0.732939 -0.648596 -0.205241 -0.934173 -0.356822 2.8116e-08 -0.853835 -0.332085 0.400855 -0.732939 -0.648596 0.205241 -0.648596 -0.20524 0.732939 -0.332086 -0.400854 0.853835 -0.57735 -0.57735 0.57735 -2.55136e-07 -0.195614 0.980681 -0.356822 -2.556e-09 0.934172 0.356822 0 0.934172 0 0 1 -2.60239e-07 0.195614 0.980681 0.356822 7.66799e-09 0.934172 -2.57687e-07 -0.195614 0.980681 0.332085 0.400854 0.853836 0 -1 -0 1 0 -0 0.648596 0.20524 0.732939 0 1 -0 0.332085 -0.400854 0.853836 -1.98298e-07 -0.525731 0.850651 -1.92197e-07 0.525731 -0.850651 0.648596 -0.20524 0.732939 0.850651 -3.05074e-09 0.525731 -1.95247e-07 0.525731 0.850651 -0.332086 0.400854 0.853835 -0.356822 5.11199e-09 0.934172 -0.648596 0.20524 0.732939 -0.648596 -0.20524 0.732939 -0.850651 0 0.525731 -1 0 -0 -0.332086 -0.400854 0.853835 -0.525732 0.85065 1.83044e-08 -0.400855 0.853835 -0.332086 -0.195615 0.980681 -7.65408e-09 -1.84032e-07 0.934172 -0.356823 0.195615 0.980681 -2.55136e-09 -0.205241 0.73294 -0.648595 0.400855 0.853835 -0.332085 0.525732 0.85065 2.74567e-08 0.20524 0.73294 -0.648595 0.732939 0.648596 -0.20524 0.853836 0.332086 -0.400853 0.732939 0.648596 0.20524 0.934172 0.356823 -2.3004e-08 0.853836 0.332086 0.400854 0.980681 -2.55136e-09 0.195613 0.980681 0 -0.195613 0.400855 0.853835 0.332085 0.57735 0.577351 0.57735 0.20524 0.73294 0.648595 -1.86588e-07 0.934172 0.356823 -0.400855 0.853835 0.332086 -0.205241 0.73294 0.648595 -0.57735 0.57735 0.57735 -0.732939 0.648596 0.205241 -0.853835 0.332085 0.400855 -0.980681 2.55136e-09 0.195614 -0.934173 0.356822 -2.8116e-08 -0.980681 0 -0.195614 -0.732939 0.648596 -0.205241 -0.853835 0.332085 -0.400855 -0.850651 -3.05074e-09 -0.525731 -0.57735 0.57735 -0.57735 -0.332086 0.400854 -0.853835 -0.648596 0.20524 -0.732939 0.525732 -0.85065 2.13552e-08 0.400855 -0.853835 -0.332085 0.195615 -0.980681 -5.10272e-09 -1.86588e-07 -0.934172 -0.356823 -0.195615 -0.980681 -2.55136e-09 0.20524 -0.73294 -0.648595 -0.400855 -0.853835 -0.332086 -0.525732 -0.85065 1.2203e-08 -0.205241 -0.73294 -0.648595 0.853836 -0.332086 -0.400853 0.732939 -0.648596 -0.20524 0.934172 -0.356823 -2.8116e-08 0.853836 -0.332086 0.400854 0.732939 -0.648596 0.20524 0.57735 -0.577351 0.57735 0.20524 -0.73294 0.648595 0.400855 -0.853835 0.332085 -0.205241 -0.73294 0.648595 -1.86588e-07 -0.934172 0.356823 -0.400855 -0.853835 0.332086 -0.853835 -0.332085 0.400855 -0.732939 -0.648596 0.205241 -0.934173 -0.356822 -2.8116e-08 -0.853835 -0.332085 -0.400855 -0.732939 -0.648596 -0.205241 -0.648596 -0.20524 -0.732939 -0.57735 -0.57735 -0.57735 -0.332086 -0.400854 -0.853836 -0.356822 -2.556e-09 -0.934172</float_array>\n\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t<accessor count=\"188\" source=\"#ID13\" stride=\"3\">\n\t\t\t\t\t\t\t<param name=\"X\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Y\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Z\" type=\"float\"/>\n\t\t\t\t\t\t</accessor>\n\t\t\t\t\t</technique_common>\n\t\t\t\t</source>\n\t\t\t\t<source id=\"ID14\">\n\t\t\t\t\t<float_array id=\"ID15\" count=\"98\" digits=\"2490374\">0 0.75 0.25 0.493175 0 0.25 0.333333 1 0.666667 1 0.5 0.666667 0.166667 0.666667 0.25 0.75 0 1 0.333333 0.333333 0.25 0.438529 0.666667 0.333333 0.833333 0.666667 0.25 0.25 0.476866 0.25 0.375 0 0.25 0.5 0.465299 0.75 1 1 0.465299 0.438529 0.465299 0.25 0.75 0.493175 0.75 0.75 1 0.75 1 0.25 0.75 0.25 0.75 0.438529 0.534701 0.25 0.534701 0.438529 0.5 0 0.534701 0.75 0.75 0.5 0 0.5 0.5 0.75 0.75 1 0.75 0 0.5 0.25 0.465299 0.493175 0.5 0.438529 0.534701 0.493175 0.375 1 0.476866 0.75 0.5 1 1 0 0 0 0.5 0.493175 0.534701 0.5 0.25 1 0.25 0</float_array>\n\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t<accessor count=\"49\" source=\"#ID15\" stride=\"2\">\n\t\t\t\t\t\t\t<param name=\"S\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"T\" type=\"float\"/>\n\t\t\t\t\t\t</accessor>\n\t\t\t\t\t</technique_common>\n\t\t\t\t</source>\n\t\t\t\t<vertices id=\"ID16\">\n\t\t\t\t\t<input semantic=\"POSITION\" source=\"#ID10\"/>\n\t\t\t\t</vertices>\n\t\t\t\t<triangles count=\"64\" material=\"Material2\">\n\t\t\t\t\t<input offset=\"0\" semantic=\"VERTEX\" source=\"#ID16\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"1\" semantic=\"NORMAL\" source=\"#ID12\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"2\" semantic=\"TEXCOORD\" source=\"#ID14\" set=\"0\"/>\n\t\t\t\t\t<p>4 0 2 38 0 1 5 0 0 38 0 1 37 0 7 5 0 0 4 0 2 46 0 10 38 0 1 4 0 15 6 0 14 23 0 13 7 0 23 43 0 22 40 0 21 12 0 26 1 0 25 11 0 24 39 0 33 44 0 0 42 0 32 36 0 30 7 0 34 39 0 33 9 0 27 6 0 36 11 0 35 9 0 27 45 0 28 6 0 36 45 0 28 12 0 26 40 0 21 44 0 37 38 0 1 46 0 10 45 0 28 42 0 39 27 0 38 4 0 2 23 0 13 46 0 10 39 0 41 5 0 40 37 0 7 7 0 34 5 0 42 39 0 33 44 27 44 39 27 43 39 27 43 40 0 21 12 0 26 11 0 24 6 0 36 4 0 29 11 0 35 45 0 28 27 0 38 6 0 36 42 0 39 44 0 45 27 0 38 36 0 30 43 0 22 7 0 34 11 0 24 7 0 23 40 0 21 11 0 35 1 0 25 9 0 27 36 0 30 39 0 33 42 0 32 42 0 39 45 0 28 40 0 21 27 0 19 44 0 37 46 0 10 39 27 43 44 27 44 39 27 43 166 103 1 134 103 2 135 103 0 134 108 29 141 108 48 11 108 47 165 103 7 166 103 1 135 103 0 135 109 23 134 109 24 4 109 2 174 103 10 134 103 2 166 103 1 135 111 42 5 111 29 137 111 47 136 103 14 134 103 15 152 103 13 171 103 22 137 103 23 168 103 21 132 103 25 142 103 26 141 103 24 4 108 42 134 108 29 11 108 47 5 109 0 135 109 23 4 109 2 172 103 0 167 103 33 170 103 32 137 103 34 164 103 30 167 103 33 136 103 36 139 103 27 141 103 35 173 103 28 139 103 27 136 103 36 142 103 26 173 103 28 168 103 21 166 103 1 172 103 37 174 103 10 170 103 39 173 103 28 156 103 38 152 103 13 134 103 2 174 103 10 135 103 40 167 103 41 165 103 7 7 123 23 11 123 24 141 123 2 135 103 42 137 103 34 167 103 33 167 27 43 172 27 44 167 27 43 142 103 26 168 103 21 141 103 24 134 103 29 136 103 36 141 103 35 156 103 38 173 103 28 136 103 36 172 103 45 170 103 39 156 103 38 5 111 29 7 111 48 137 111 47 137 123 0 7 123 23 141 123 2 171 103 22 164 103 30 137 103 34 137 103 23 141 103 24 168 103 21 132 103 25 141 103 35 139 103 27 167 103 33 164 103 30 170 103 32 173 103 28 170 103 39 168 103 21 172 103 37 156 103 19 174 103 10 172 27 44 167 27 43 167 27 43</p>\n\t\t\t\t</triangles>\n\t\t\t\t<triangles count=\"376\" material=\"Material1\">\n\t\t\t\t\t<input offset=\"0\" semantic=\"VERTEX\" source=\"#ID16\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"1\" semantic=\"NORMAL\" source=\"#ID12\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"2\" semantic=\"TEXCOORD\" source=\"#ID14\" set=\"0\"/>\n\t\t\t\t\t<p>2 3 5 122 2 4 121 1 3 121 1 3 80 4 6 2 3 5 200 7 6 230 6 3 161 5 5 230 6 3 200 7 6 181 8 8 79 9 9 2 3 5 80 4 6 248 11 12 247 10 11 161 5 5 24 0 17 8 0 7 47 0 16 106 13 12 56 12 18 122 2 4 19 0 20 35 0 19 34 0 13 48 0 28 21 0 27 25 0 26 229 14 4 161 5 5 230 6 3 199 15 9 161 5 5 247 10 11 51 18 29 119 17 11 71 16 9 26 0 31 41 0 22 50 0 30 122 2 4 2 3 5 106 13 12 105 19 11 106 13 12 2 3 5 2 3 5 79 9 9 105 19 11 54 20 29 105 19 11 79 9 9 121 1 6 85 22 3 55 21 8 21 0 27 16 0 25 25 0 26 15 24 5 86 23 4 85 22 3 85 22 3 121 1 6 15 24 5 122 2 9 15 24 5 121 1 6 112 26 12 57 25 18 86 23 4 86 23 4 15 24 5 112 26 12 161 5 5 199 15 9 200 7 6 180 28 18 248 11 12 229 14 4 111 29 11 112 26 12 15 24 5 161 5 5 229 14 4 248 11 12 15 24 5 122 2 9 111 29 11 8 0 7 22 0 7 47 0 16 56 12 29 111 29 11 122 2 9 35 0 19 49 0 10 34 0 13 30 0 46 26 0 31 50 0 30 80 4 6 121 1 3 55 21 8 68 32 6 64 31 3 60 30 8 123 34 5 63 33 4 64 31 3 64 31 3 68 32 6 123 34 5 67 35 9 123 34 5 68 32 6 66 37 12 59 36 18 63 33 4 63 33 4 123 34 5 66 37 12 65 38 11 66 37 12 123 34 5 123 34 5 67 35 9 65 38 11 51 18 29 65 38 11 67 35 9 66 37 6 70 39 3 59 36 8 124 41 5 69 40 4 70 39 3 70 39 3 66 37 6 124 41 5 65 38 9 124 41 5 66 37 6 72 43 12 53 42 18 69 40 4 69 40 4 124 41 5 72 43 12 71 16 11 72 43 12 124 41 5 124 41 5 65 38 9 71 16 11 51 18 29 71 16 11 65 38 9 70 39 6 74 44 3 59 36 8 125 46 5 73 45 4 74 44 3 74 44 3 70 39 6 125 46 5 69 40 9 125 46 5 70 39 6 76 47 12 54 20 18 73 45 4 73 45 4 125 46 5 76 47 12 75 48 11 76 47 12 125 46 5 125 46 5 69 40 9 75 48 11 53 42 29 75 48 11 69 40 9 74 44 6 78 49 3 59 36 8 126 51 5 77 50 4 78 49 3 78 49 3 74 44 6 126 51 5 73 45 9 126 51 5 74 44 6 80 4 12 55 21 18 77 50 4 77 50 4 126 51 5 80 4 12 79 9 11 80 4 12 126 51 5 126 51 5 73 45 9 79 9 11 54 20 29 79 9 11 73 45 9 78 49 6 63 33 3 59 36 8 127 52 5 64 31 4 63 33 3 63 33 3 78 49 6 127 52 5 77 50 9 127 52 5 78 49 6 82 53 12 60 30 18 64 31 4 64 31 4 127 52 5 82 53 12 81 54 11 82 53 12 127 52 5 127 52 5 77 50 9 81 54 11 55 21 29 81 54 11 77 50 9 85 22 6 81 54 3 55 21 8 128 55 5 82 53 4 81 54 3 81 54 3 85 22 6 128 55 5 86 23 9 128 55 5 85 22 6 84 56 12 60 30 18 82 53 4 82 53 4 128 55 5 84 56 12 83 57 11 84 56 12 128 55 5 128 55 5 86 23 9 83 57 11 57 25 29 83 57 11 86 23 9 89 58 6 83 57 3 57 25 8 129 59 5 84 56 4 83 57 3 83 57 3 89 58 6 129 59 5 90 60 9 129 59 5 89 58 6 88 61 12 60 30 18 84 56 4 84 56 4 129 59 5 88 61 12 87 62 11 88 61 12 129 59 5 129 59 5 90 60 9 87 62 11 58 63 29 87 62 11 90 60 9 88 61 6 68 32 3 60 30 8 130 64 5 67 35 4 68 32 3 68 32 3 88 61 6 130 64 5 87 62 9 130 64 5 88 61 6 91 65 12 51 18 18 67 35 4 67 35 4 130 64 5 91 65 12 92 66 11 91 65 12 130 64 5 130 64 5 87 62 9 92 66 11 58 63 29 92 66 11 87 62 9 98 69 6 94 68 3 62 67 8 131 71 5 93 70 4 94 68 3 94 68 3 98 69 6 131 71 5 97 72 9 131 71 5 98 69 6 96 74 12 61 73 18 93 70 4 93 70 4 131 71 5 96 74 12 95 75 11 96 74 12 131 71 5 131 71 5 97 72 9 95 75 11 52 76 29 95 75 11 97 72 9 102 78 6 99 77 3 53 42 8 10 80 5 100 79 4 99 77 3 99 77 3 102 78 6 10 80 5 101 81 9 10 80 5 102 78 6 98 69 12 62 67 18 100 79 4 100 79 4 10 80 5 98 69 12 97 72 11 98 69 12 10 80 5 10 80 5 101 81 9 97 72 11 52 76 29 97 72 11 101 81 9 99 77 6 75 48 3 53 42 8 13 82 5 76 47 4 75 48 3 75 48 3 99 77 6 13 82 5 100 79 9 13 82 5 99 77 6 103 83 12 54 20 18 76 47 4 76 47 4 13 82 5 103 83 12 104 84 11 103 83 12 13 82 5 13 82 5 100 79 9 104 84 11 62 67 29 104 84 11 100 79 9 103 83 6 105 19 3 54 20 8 14 85 5 106 13 4 105 19 3 105 19 3 103 83 6 14 85 5 104 84 9 14 85 5 103 83 6 107 86 12 56 12 18 106 13 4 106 13 4 14 85 5 107 86 12 108 87 11 107 86 12 14 85 5 14 85 5 104 84 9 108 87 11 62 67 29 108 87 11 104 84 9 107 86 6 109 88 3 56 12 8 17 90 5 110 89 4 109 88 3 109 88 3 107 86 6 17 90 5 108 87 9 17 90 5 107 86 6 93 70 12 61 73 18 110 89 4 110 89 4 17 90 5 93 70 12 94 68 11 93 70 12 17 90 5 17 90 5 108 87 9 94 68 11 62 67 29 94 68 11 108 87 9 109 88 6 111 29 3 56 12 8 18 91 5 112 26 4 111 29 3 111 29 3 109 88 6 18 91 5 110 89 9 18 91 5 109 88 6 113 92 12 57 25 18 112 26 4 112 26 4 18 91 5 113 92 12 114 93 11 113 92 12 18 91 5 18 91 5 110 89 9 114 93 11 61 73 29 114 93 11 110 89 9 113 92 6 89 58 3 57 25 8 28 94 5 90 60 4 89 58 3 89 58 3 113 92 6 28 94 5 114 93 9 28 94 5 113 92 6 115 95 12 58 63 18 90 60 4 90 60 4 28 94 5 115 95 12 116 96 11 115 95 12 28 94 5 28 94 5 114 93 9 116 96 11 61 73 29 116 96 11 114 93 9 115 95 6 118 97 3 58 63 8 31 99 5 117 98 4 118 97 3 118 97 3 115 95 6 31 99 5 116 96 9 31 99 5 115 95 6 95 75 12 52 76 18 117 98 4 117 98 4 31 99 5 95 75 12 96 74 11 95 75 12 31 99 5 31 99 5 116 96 9 96 74 11 61 73 29 96 74 11 116 96 9 120 100 6 117 98 3 52 76 8 32 101 5 118 97 4 117 98 3 117 98 3 120 100 6 32 101 5 119 17 9 32 101 5 120 100 6 92 66 12 58 63 18 118 97 4 118 97 4 32 101 5 92 66 12 91 65 11 92 66 12 32 101 5 32 101 5 119 17 9 91 65 11 51 18 29 91 65 11 119 17 9 72 43 6 102 78 3 53 42 8 33 102 5 101 81 4 102 78 3 102 78 3 72 43 6 33 102 5 71 16 9 33 102 5 72 43 6 120 100 12 52 76 18 101 81 4 101 81 4 33 102 5 120 100 12 119 17 11 120 100 12 33 102 5 33 102 5 71 16 9 119 17 11 250 106 4 133 105 5 249 104 3 208 107 6 249 104 3 133 105 5 133 105 5 207 110 9 208 107 6 138 103 7 153 103 17 175 103 16 184 113 18 234 112 12 250 106 4 163 103 19 149 103 20 162 103 13 150 103 27 176 103 28 154 103 26 247 10 11 179 114 29 199 15 9 169 103 22 155 103 31 178 103 30 133 105 5 250 106 4 234 112 12 234 112 12 233 115 11 133 105 5 207 110 9 133 105 5 233 115 11 233 115 11 182 116 29 207 110 9 213 118 3 249 104 6 183 117 8 146 103 25 150 103 27 154 103 26 214 120 4 145 119 5 213 118 3 249 104 6 213 118 3 145 119 5 145 119 5 250 106 9 249 104 6 185 122 18 240 121 12 214 120 4 145 119 5 214 120 4 240 121 12 240 121 12 239 124 11 145 119 5 250 106 9 145 119 5 239 124 11 151 103 7 138 103 7 175 103 16 239 124 11 184 113 29 250 106 9 177 103 10 163 103 19 162 103 13 155 103 31 158 103 46 178 103 30 249 104 3 208 107 6 183 117 8 192 127 3 196 126 6 188 125 8 191 129 4 251 128 5 192 127 3 196 126 6 192 127 3 251 128 5 251 128 5 195 130 9 196 126 6 187 132 18 194 131 12 191 129 4 251 128 5 191 129 4 194 131 12 194 131 12 193 133 11 251 128 5 195 130 9 251 128 5 193 133 11 193 133 11 179 114 29 195 130 9 198 134 3 194 131 6 187 132 8 197 135 4 252 51 5 198 134 3 194 131 6 198 134 3 252 51 5 252 51 5 193 133 9 194 131 6 181 8 18 200 7 12 197 135 4 252 51 5 197 135 4 200 7 12 200 7 12 199 15 11 252 51 5 193 133 9 252 51 5 199 15 11 199 15 11 179 114 29 193 133 9 202 136 3 198 134 6 187 132 8 201 138 4 253 137 5 202 136 3 198 134 6 202 136 3 253 137 5 253 137 5 197 135 9 198 134 6 182 116 18 204 139 12 201 138 4 253 137 5 201 138 4 204 139 12 204 139 12 203 140 11 253 137 5 197 135 9 253 137 5 203 140 11 203 140 11 181 8 29 197 135 9 206 141 3 202 136 6 187 132 8 205 143 4 254 142 5 206 141 3 202 136 6 206 141 3 254 142 5 254 142 5 201 138 9 202 136 6 183 117 18 208 107 12 205 143 4 254 142 5 205 143 4 208 107 12 208 107 12 207 110 11 254 142 5 201 138 9 254 142 5 207 110 11 207 110 11 182 116 29 201 138 9 191 129 3 206 141 6 187 132 8 192 127 4 255 144 5 191 129 3 206 141 6 191 129 3 255 144 5 255 144 5 205 143 9 206 141 6 188 125 18 210 145 12 192 127 4 255 144 5 192 127 4 210 145 12 210 145 12 209 146 11 255 144 5 205 143 9 255 144 5 209 146 11 209 146 11 183 117 29 205 143 9 209 146 3 213 118 6 183 117 8 210 145 4 0 147 5 209 146 3 213 118 6 209 146 3 0 147 5 0 147 5 214 120 9 213 118 6 188 125 18 212 148 12 210 145 4 0 147 5 210 145 4 212 148 12 212 148 12 211 149 11 0 147 5 214 120 9 0 147 5 211 149 11 211 149 11 185 122 29 214 120 9 211 149 3 217 150 6 185 122 8 212 148 4 20 151 5 211 149 3 217 150 6 211 149 3 20 151 5 20 151 5 218 152 9 217 150 6 188 125 18 216 153 12 212 148 4 20 151 5 212 148 4 216 153 12 216 153 12 215 154 11 20 151 5 218 152 9 20 151 5 215 154 11 215 154 11 186 155 29 218 152 9 196 126 3 216 153 6 188 125 8 195 130 4 29 156 5 196 126 3 216 153 6 196 126 3 29 156 5 29 156 5 215 154 9 216 153 6 179 114 18 219 157 12 195 130 4 29 156 5 195 130 4 219 157 12 219 157 12 220 158 11 29 156 5 215 154 9 29 156 5 220 158 11 220 158 11 186 155 29 215 154 9 222 161 3 226 160 6 190 159 8 221 163 4 3 162 5 222 161 3 226 160 6 222 161 3 3 162 5 3 162 5 225 164 9 226 160 6 189 166 18 224 165 12 221 163 4 3 162 5 221 163 4 224 165 12 224 165 12 223 167 11 3 162 5 225 164 9 3 162 5 223 167 11 223 167 11 180 28 29 225 164 9 227 168 3 230 6 6 181 8 8 228 169 4 140 85 5 227 168 3 230 6 6 227 168 3 140 85 5 140 85 5 229 14 9 230 6 6 190 159 18 226 160 12 228 169 4 140 85 5 228 169 4 226 160 12 226 160 12 225 164 11 140 85 5 229 14 9 140 85 5 225 164 11 225 164 11 180 28 29 229 14 9 203 140 3 227 168 6 181 8 8 204 139 4 143 170 5 203 140 3 227 168 6 203 140 3 143 170 5 143 170 5 228 169 9 227 168 6 182 116 18 231 171 12 204 139 4 143 170 5 204 139 4 231 171 12 231 171 12 232 172 11 143 170 5 228 169 9 143 170 5 232 172 11 232 172 11 190 159 29 228 169 9 233 115 3 231 171 6 182 116 8 234 112 4 144 173 5 233 115 3 231 171 6 233 115 3 144 173 5 144 173 5 232 172 9 231 171 6 184 113 18 235 174 12 234 112 4 144 173 5 234 112 4 235 174 12 235 174 12 236 175 11 144 173 5 232 172 9 144 173 5 236 175 11 236 175 11 190 159 29 232 172 9 237 176 3 235 174 6 184 113 8 238 178 4 147 177 5 237 176 3 235 174 6 237 176 3 147 177 5 147 177 5 236 175 9 235 174 6 189 166 18 221 163 12 238 178 4 147 177 5 238 178 4 221 163 12 221 163 12 222 161 11 147 177 5 236 175 9 147 177 5 222 161 11 222 161 11 190 159 29 236 175 9 239 124 3 237 176 6 184 113 8 240 121 4 148 99 5 239 124 3 237 176 6 239 124 3 148 99 5 148 99 5 238 178 9 237 176 6 185 122 18 241 179 12 240 121 4 148 99 5 240 121 4 241 179 12 241 179 12 242 180 11 148 99 5 238 178 9 148 99 5 242 180 11 242 180 11 189 166 29 238 178 9 217 150 3 241 179 6 185 122 8 218 152 4 157 181 5 217 150 3 241 179 6 217 150 3 157 181 5 157 181 5 242 180 9 241 179 6 186 155 18 243 182 12 218 152 4 157 181 5 218 152 4 243 182 12 243 182 12 244 183 11 157 181 5 242 180 9 157 181 5 244 183 11 244 183 11 189 166 29 242 180 9 246 184 3 243 182 6 186 155 8 245 186 4 159 185 5 246 184 3 243 182 6 246 184 3 159 185 5 159 185 5 244 183 9 243 182 6 180 28 18 223 167 12 245 186 4 159 185 5 245 186 4 223 167 12 223 167 12 224 165 11 159 185 5 244 183 9 159 185 5 224 165 11 224 165 11 189 166 29 244 183 9 245 186 3 248 11 6 180 28 8 246 184 4 160 187 5 245 186 3 248 11 6 245 186 3 160 187 5 160 187 5 247 10 9 248 11 6 186 155 18 220 158 12 246 184 4 160 187 5 246 184 4 220 158 12 220 158 12 219 157 11 160 187 5 247 10 9 160 187 5 219 157 11 219 157 11 179 114 29 247 10 9</p>\n\t\t\t\t</triangles>\n\t\t\t</mesh>\n\t\t</geometry>\n\t</library_geometries>\n\t<library_visual_scenes>\n\t\t<visual_scene id=\"ID7\">\n\t\t\t<node id=\"ID8\" name=\"Door.1\">\n\t\t\t\t<translate sid=\"translate\">0 0 -0</translate>\n\t\t\t\t<rotate sid=\"rotateY\">0 1 0 -0</rotate>\n\t\t\t\t<rotate sid=\"rotateX\">1 0 0 0</rotate>\n\t\t\t\t<rotate sid=\"rotateZ\">0 0 1 -0</rotate>\n\t\t\t\t<scale sid=\"scale\">1 1 1</scale>\n\t\t\t\t<instance_geometry url=\"#ID9\">\n\t\t\t\t\t<bind_material>\n\t\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t\t<instance_material symbol=\"Material1\" target=\"#ID3\">\n\t\t\t\t\t\t\t\t<bind_vertex_input semantic=\"UVSET0\" input_semantic=\"TEXCOORD\" input_set=\"0\"/>\n\t\t\t\t\t\t\t</instance_material>\n\t\t\t\t\t\t\t<instance_material symbol=\"Material2\" target=\"#ID5\">\n\t\t\t\t\t\t\t\t<bind_vertex_input semantic=\"UVSET0\" input_semantic=\"TEXCOORD\" input_set=\"0\"/>\n\t\t\t\t\t\t\t</instance_material>\n\t\t\t\t\t\t</technique_common>\n\t\t\t\t\t</bind_material>\n\t\t\t\t</instance_geometry>\n\t\t\t</node>\n\t\t</visual_scene>\n\t</library_visual_scenes>\n\t<scene>\n\t\t<instance_visual_scene url=\"#ID7\"/>\n\t</scene>\n</COLLADA>\n"
  },
  {
    "path": "static/models/headset.dae",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<COLLADA xmlns=\"http://www.collada.org/2008/03/COLLADASchema\" version=\"1.5.0\">\n\t<asset>\n\t\t<contributor>\n\t\t\t<authoring_tool>CINEMA4D 16.050 COLLADA Exporter</authoring_tool>\n\t\t</contributor>\n\t\t<created>2017-02-21T21:06:11Z</created>\n\t\t<modified>2017-02-21T21:06:11Z</modified>\n\t\t<unit meter=\"0.01\" name=\"centimeter\"/>\n\t\t<up_axis>Y_UP</up_axis>\n\t</asset>\n\t<library_effects>\n\t\t<effect id=\"ID2\">\n\t\t\t<profile_COMMON>\n\t\t\t\t<technique sid=\"COMMON\">\n\t\t\t\t\t<blinn>\n\t\t\t\t\t\t<diffuse>\n\t\t\t\t\t\t\t<color>0.8 0.8 0.8 1</color>\n\t\t\t\t\t\t</diffuse>\n\t\t\t\t\t\t<specular>\n\t\t\t\t\t\t\t<color>0.2 0.2 0.2 1</color>\n\t\t\t\t\t\t</specular>\n\t\t\t\t\t\t<shininess>\n\t\t\t\t\t\t\t<float>0.5</float>\n\t\t\t\t\t\t</shininess>\n\t\t\t\t\t</blinn>\n\t\t\t\t</technique>\n\t\t\t</profile_COMMON>\n\t\t</effect>\n\t\t<effect id=\"ID4\">\n\t\t\t<profile_COMMON>\n\t\t\t\t<technique sid=\"COMMON\">\n\t\t\t\t\t<blinn>\n\t\t\t\t\t\t<diffuse>\n\t\t\t\t\t\t\t<color>0.8 0.8 0.8 1</color>\n\t\t\t\t\t\t</diffuse>\n\t\t\t\t\t\t<specular>\n\t\t\t\t\t\t\t<color>0.2 0.2 0.2 1</color>\n\t\t\t\t\t\t</specular>\n\t\t\t\t\t\t<shininess>\n\t\t\t\t\t\t\t<float>0.5</float>\n\t\t\t\t\t\t</shininess>\n\t\t\t\t\t</blinn>\n\t\t\t\t</technique>\n\t\t\t</profile_COMMON>\n\t\t</effect>\n\t</library_effects>\n\t<library_materials>\n\t\t<material id=\"ID1\" name=\"Mat.1\">\n\t\t\t<instance_effect url=\"#ID2\"/>\n\t\t</material>\n\t\t<material id=\"ID3\" name=\"Mat\">\n\t\t\t<instance_effect url=\"#ID4\"/>\n\t\t</material>\n\t</library_materials>\n\t<library_geometries>\n\t\t<geometry id=\"ID7\">\n\t\t\t<mesh>\n\t\t\t\t<source id=\"ID8\">\n\t\t\t\t\t<float_array id=\"ID9\" count=\"690\" digits=\"2490374\">-2.9479 0.8165 1.3314 -3.1091 -0.0978 1.3314 -3.4426 -0.6754 1.3314 -3.5674 -0.8789 1.3314 -4.25 -1.4517 1.3314 -4.5394 -1.557 1.3314 -5.1167 -1.7672 1.3314 -6.0666 -1.7672 1.3314 -6.9141 -1.4587 1.3314 -7.605 -0.8789 1.3314 -8.056 -0.0978 1.3314 -8.2172 0.8165 1.3314 -8.056 1.7309 1.3314 -7.605 2.512 1.3314 -6.9141 3.0917 1.3314 -6.0666 3.4002 1.3314 -5.1167 3.4002 1.3314 -4.5394 3.19 1.3314 -4.25 3.0847 1.3314 -3.5674 2.512 1.3314 -3.4426 2.3085 1.3314 -3.1091 1.7309 1.3314 4.5392 -1.5569 1.3314 4.2502 -1.4517 1.3314 3.5676 -0.8789 1.3314 3.4427 -0.6754 1.3314 3.1093 -0.0978 1.3314 2.9481 0.8165 1.3314 3.1093 1.7309 1.3314 3.4428 2.3085 1.3314 3.5676 2.512 1.3314 4.2502 3.0847 1.3314 4.5392 3.1899 1.3314 5.1169 3.4002 1.3314 6.0668 3.4002 1.3314 6.9143 3.0917 1.3314 7.6052 2.512 1.3314 8.0562 1.7309 1.3314 8.2174 0.8165 1.3314 8.0562 -0.0978 1.3314 7.6052 -0.8789 1.3314 6.9143 -1.4587 1.3314 6.0668 -1.7672 1.3314 5.1169 -1.7672 1.3314 -3.3316 -5.69354 12.5911 -3.80335 -6.38427 12.5911 -4.67863 -6.65578 12.5911 -9.11033 -6.54428 12.5911 -9.55339 -6.34912 12.5911 -10.0438 -5.9417 12.5911 -10.3654 -5.49723 12.5911 -10.5991 -4.99873 12.5911 -10.6554 -4.45481 12.5911 -10.6554 4.46366 12.5911 -10.5989 5.01576 12.5911 -10.3654 5.51358 12.5911 -10.0438 5.95779 12.5911 -9.55332 6.36563 12.5911 -9.12258 6.5551 12.5911 9.12246 6.5551 12.5911 9.55365 6.3655 12.5911 10.0443 5.95788 12.5911 10.3542 5.52931 12.5911 10.576 5.01375 12.5911 10.6551 4.44546 12.5911 10.6551 -4.43648 12.5911 10.5764 -4.99651 12.5911 10.3542 -5.51301 12.5911 10.0443 -5.94171 12.5911 9.55372 -6.349 12.5911 9.1102 -6.54428 12.5911 4.67851 -6.65578 12.5911 3.80347 -6.38418 12.5911 3.36388 -6.16999 12.5911 2.823 -5.45925 12.5911 2.08924 -3.73983 12.5911 1.21424 -2.68898 12.5911 -4.06528e-05 -2.35788 12.5911 -1.21401 -2.689 12.5911 -2.08916 -3.73981 12.5911 -2.8233 -5.4593 12.5911 -3.83761 -6.10865 12.5911 -2.712 -6.9648 1.47523 -1.9523 -5.966 1.47523 -1.227 -4.2672 1.47523 -0.6471 -3.5709 1.47523 0 -3.3944 1.47523 0.6473 -3.5709 1.47523 1.227 -4.2671 1.47523 1.952 -5.966 1.47523 2.7121 -6.9648 1.47523 3.4341 -7.3166 1.47523 4.5392 -7.6596 1.47523 9.3326 -7.539 1.47523 10.0848 -7.2078 1.47523 10.7805 -6.6302 1.47523 11.2292 -6.0095 1.47523 11.548 -5.2685 1.47523 11.6551 -4.5064 1.47523 11.6551 4.5147 1.47523 11.5479 5.2851 1.47523 11.2292 6.0259 1.47523 10.7805 6.6463 1.47523 10.0847 7.2244 1.47523 9.3326 7.5551 1.47523 -9.3328 7.5551 1.47523 -10.0846 7.2244 1.47523 -10.78 6.6462 1.47523 -11.2307 6.0236 1.47523 -11.5764 5.2866 1.47523 -11.6554 4.5147 1.47523 -11.6554 -4.5064 1.47523 -11.5764 -5.27 1.47523 -11.2308 -6.0071 1.47523 -10.7799 -6.6302 1.47523 -10.0847 -7.2078 1.47523 -9.3328 -7.539 1.47523 -4.5394 -7.6596 1.47523 -3.434 -7.3167 1.47523 -2.712 -6.9648 12.4752 -1.9523 -5.966 12.4752 -1.227 -4.2672 12.4752 -0.6471 -3.5709 12.4752 0 -3.3944 12.4752 0.6473 -3.5709 12.4752 1.227 -4.2671 12.4752 1.952 -5.966 12.4752 2.7121 -6.9648 12.4752 3.4341 -7.3166 12.4752 4.5392 -7.6596 12.4752 9.3326 -7.539 12.4752 10.0848 -7.2078 12.4752 10.7805 -6.6302 12.4752 11.2292 -6.0095 12.4752 11.548 -5.2685 12.4752 11.6551 -4.5064 12.4752 11.6551 4.5147 12.4752 11.5479 5.2851 12.4752 11.2292 6.0259 12.4752 10.7805 6.6463 12.4752 10.0847 7.2244 12.4752 9.3326 7.5551 12.4752 -9.3328 7.5551 12.4752 -10.0846 7.2244 12.4752 -10.78 6.6462 12.4752 -11.2307 6.0236 12.4752 -11.5764 5.2866 12.4752 -11.6554 4.5147 12.4752 -11.6554 -4.5064 12.4752 -11.5764 -5.27 12.4752 -11.2308 -6.0071 12.4752 -10.7799 -6.6302 12.4752 -10.0847 -7.2078 12.4752 -9.3328 -7.539 12.4752 -4.5394 -7.6596 12.4752 -3.434 -7.3167 12.4752 -2.712 -6.9648 1.47523 -1.9523 -5.966 1.47523 -1.227 -4.2672 1.47523 -0.6471 -3.5709 1.47523 0 -3.3944 1.47523 0.6473 -3.5709 1.47523 1.227 -4.2671 1.47523 1.952 -5.966 1.47523 2.7121 -6.9648 1.47523 3.4341 -7.3166 1.47523 4.5392 -7.6596 1.47523 9.3326 -7.539 1.47523 10.0848 -7.2078 1.47523 10.7805 -6.6302 1.47523 11.2292 -6.0095 1.47523 11.548 -5.2685 1.47523 11.6551 -4.5064 1.47523 11.6551 4.5147 1.47523 11.5479 5.2851 1.47523 11.2292 6.0259 1.47523 10.7805 6.6463 1.47523 10.0847 7.2244 1.47523 9.3326 7.5551 1.47523 -9.3328 7.5551 1.47523 -10.0846 7.2244 1.47523 -10.78 6.6462 1.47523 -11.2307 6.0236 1.47523 -11.5764 5.2866 1.47523 -11.6554 4.5147 1.47523 -11.6554 -4.5064 1.47523 -11.5764 -5.27 1.47523 -11.2308 -6.0071 1.47523 -10.7799 -6.6302 1.47523 -10.0847 -7.2078 1.47523 -9.3328 -7.539 1.47523 -4.5394 -7.6596 1.47523 -3.434 -7.3167 1.47523 -2.712 -6.9648 12.4752 -1.9523 -5.966 12.4752 -1.227 -4.2672 12.4752 -0.6471 -3.5709 12.4752 0 -3.3944 12.4752 0.6473 -3.5709 12.4752 1.227 -4.2671 12.4752 1.952 -5.966 12.4752 2.7121 -6.9648 12.4752 3.4341 -7.3166 12.4752 4.5392 -7.6596 12.4752 9.3326 -7.539 12.4752 10.0848 -7.2078 12.4752 10.7805 -6.6302 12.4752 11.2292 -6.0095 12.4752 11.548 -5.2685 12.4752 11.6551 -4.5064 12.4752 11.6551 4.5147 12.4752 11.5479 5.2851 12.4752 11.2292 6.0259 12.4752 10.7805 6.6463 12.4752 10.0847 7.2244 12.4752 9.3326 7.5551 12.4752 -9.3328 7.5551 12.4752 -10.0846 7.2244 12.4752 -10.78 6.6462 12.4752 -11.2307 6.0236 12.4752 -11.5764 5.2866 12.4752 -11.6554 4.5147 12.4752 -11.6554 -4.5064 12.4752 -11.5764 -5.27 12.4752 -11.2308 -6.0071 12.4752 -10.7799 -6.6302 12.4752 -10.0847 -7.2078 12.4752 -9.3328 -7.539 12.4752 -4.5394 -7.6596 12.4752 -3.434 -7.3167 12.4752</float_array>\n\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t<accessor count=\"230\" source=\"#ID9\" stride=\"3\">\n\t\t\t\t\t\t\t<param name=\"X\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Y\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Z\" type=\"float\"/>\n\t\t\t\t\t\t</accessor>\n\t\t\t\t\t</technique_common>\n\t\t\t\t</source>\n\t\t\t\t<source id=\"ID10\">\n\t\t\t\t\t<float_array id=\"ID11\" count=\"117\" digits=\"2490374\">0 0 -1 0 0 1 0.63424 -0.773136 -0 0.864375 -0.502847 -0 0.853057 -0.521818 -0 0.54074 -0.84119 -0 3.92157e-05 -1 -0 -0.540749 -0.841184 -0 -0.853127 -0.521703 -0 -0.864355 -0.502883 -0 -0.634099 -0.773252 -0 -0.368297 -0.929708 -0 -0.137462 -0.990507 -0 0.21819 -0.975906 -0 0.525956 -0.850512 -0 0.730389 -0.683032 -0 0.869746 -0.4935 -0 0.962979 -0.269578 -0 0.997564 -0.0697522 -0 0.997611 0.0690751 -0 0.963163 0.268917 -0 0.869693 0.493592 -0 0.730431 0.682986 -0 0.52589 0.850552 -0 0.205651 0.978625 -0 -0.205723 0.97861 -0 -0.526109 0.850417 -0 -0.73041 0.683009 -0 -0.861495 0.507767 -0 -0.963693 0.267013 -0 -0.9987 0.0509731 -0 -0.998672 -0.0515228 -0 -0.963568 -0.267465 -0 -0.861579 -0.507623 -0 -0.730342 -0.683082 -0 -0.526169 -0.85038 -0 -0.218262 -0.97589 -0 0.137383 -0.990518 -0 0.368275 -0.929717 -0</float_array>\n\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t<accessor count=\"39\" source=\"#ID11\" stride=\"3\">\n\t\t\t\t\t\t\t<param name=\"X\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Y\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"Z\" type=\"float\"/>\n\t\t\t\t\t\t</accessor>\n\t\t\t\t\t</technique_common>\n\t\t\t\t</source>\n\t\t\t\t<source id=\"ID12\">\n\t\t\t\t\t<float_array id=\"ID13\" count=\"390\" digits=\"2490374\">0.290521 0.933573 0.290521 0.752011 0.310814 0.787156 0.320622 0.842789 0.310814 0.898428 0.223784 0.698368 0.241393 0.704775 0.282927 0.739629 0.223784 0.98721 0.07929 0.704349 0.130858 0.685578 0.188657 0.685578 0.00980857 0.787156 0.0372507 0.739629 0 0.842789 0.0372507 0.945955 0.00980857 0.898428 0.130858 1 0.07929 0.981229 0.188657 1 0.282927 0.945955 0.241393 0.980803 1 0.842789 0.776192 0.987204 0.709479 0.933573 0.776192 0.698374 0.962749 0.739629 0.990191 0.787156 0.869142 0.685578 0.92071 0.704349 0.811343 0.685578 0.709473 0.752011 0.717073 0.739629 0.758607 0.704775 0.679378 0.842789 0.689186 0.787156 0.689186 0.898428 0.758607 0.980803 0.717073 0.945955 0.92071 0.981229 0.869142 1 0.811343 1 0.990191 0.898428 0.962749 0.945955 0.985881 0.951865 0.996289 0.927672 0.971336 0.971975 1 0.901005 0.948314 0.991103 0.92808 1 1 0.484218 0.996307 0.457938 0.985879 0.433701 0.971337 0.413584 0.948317 0.394472 0.927505 0.385309 0.719547 0.380077 0.678486 0.392821 0.657858 0.402872 0.598045 0.516908 0.556985 0.56622 0.500005 0.581756 0.443039 0.566219 0.0719279 1 0.401973 0.516909 0.367523 0.436221 0.0517153 0.991109 0.319926 0.405751 0.343671 0.42523 0.321534 0.392817 0.0286985 0.971971 0.013609 0.951127 0.280461 0.380077 0.00265149 0.927766 0 0.901859 0.0725029 0.385309 0.0517119 0.394467 0 0.483358 0.0287012 0.413585 0.00264061 0.457834 0.0136083 0.434442 0.632477 0.436224 0 1 0 0 0.0165375 0 0.0165375 1 0.0408802 0 0.0408802 1 0.0528219 0 0.0528219 1 0.0616612 0 0.0616612 1 0.0705031 0 0.0705031 1 0.0824421 0 0.0824421 1 0.106784 0 0.106784 1 0.123325 0 0.123325 1 0.133909 0 0.133909 1 0.149158 0 0.149158 1 0.212348 0 0.212348 1 0.223179 0 0.223179 1 0.235095 0 0.235095 1 0.245189 0 0.245189 1 0.255819 0 0.255819 1 0.265961 0 0.265961 1 0.384846 0 0.384846 1 0.395096 0 0.395096 1 0.405724 0 0.405724 1 0.415814 0 0.415814 1 0.427736 0 0.427736 1 0.438563 0 0.438563 1 0.684544 0 0.684544 1 0.695368 0 0.695368 1 0.707286 0 0.707286 1 0.717415 0 0.717415 1 0.728143 0 0.728143 1 0.738369 0 0.738369 1 0.857253 0 0.857253 1 0.86737 0 0.86737 1 0.878098 0 0.878098 1 0.888234 0 0.888234 1 0.900146 0 0.900146 1 0.910973 0 0.910973 1 0.974163 0 0.974163 1 0.989415 0 0.989415 1 1 0 1 1 0.383664 0.377109 0.416254 0.419957 0.352691 0.362013 0.447369 0.492834 0.30527 0.347303 0.472246 0.522704 0 0.86957 0 0.482572 0.500006 0.530276 0.0996375 1 0.527775 0.522704 0.900367 1 0.552644 0.492838 1 0.86957 0.583746 0.419957 0.694734 0.347303 1 0.482572 0.616353 0.377109 0.647326 0.362017 0.900367 0.352476 0.932635 0.366685 0.96248 0.391463 0.981729 0.418091 0.995405 0.449879 0.995401 0.902619 0.981729 0.934399 0.96248 0.961013 0.932631 0.985813 0.0673859 0.985813 0.0375539 0.961009 0.0182193 0.9343 0.00338903 0.902683 0.00338903 0.449814 0.018215 0.418194 0.0996375 0.352476 0.0375582 0.391463 0.0673817 0.366685</float_array>\n\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t<accessor count=\"195\" source=\"#ID13\" stride=\"2\">\n\t\t\t\t\t\t\t<param name=\"S\" type=\"float\"/>\n\t\t\t\t\t\t\t<param name=\"T\" type=\"float\"/>\n\t\t\t\t\t\t</accessor>\n\t\t\t\t\t</technique_common>\n\t\t\t\t</source>\n\t\t\t\t<vertices id=\"ID14\">\n\t\t\t\t\t<input semantic=\"POSITION\" source=\"#ID8\"/>\n\t\t\t\t</vertices>\n\t\t\t\t<triangles count=\"76\" material=\"Material2\">\n\t\t\t\t\t<input offset=\"0\" semantic=\"VERTEX\" source=\"#ID14\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"1\" semantic=\"NORMAL\" source=\"#ID10\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"2\" semantic=\"TEXCOORD\" source=\"#ID12\" set=\"0\"/>\n\t\t\t\t\t<p>1 0 2 2 0 1 20 0 0 0 0 3 1 0 2 20 0 0 0 0 3 20 0 0 21 0 4 3 0 7 4 0 6 5 0 5 2 0 1 3 0 7 5 0 5 5 0 5 17 0 8 20 0 0 2 0 1 5 0 5 20 0 0 6 0 11 7 0 10 8 0 9 5 0 5 6 0 11 8 0 9 8 0 9 9 0 13 10 0 12 5 0 5 8 0 9 10 0 12 10 0 12 11 0 14 17 0 8 5 0 5 10 0 12 17 0 8 12 0 16 13 0 15 17 0 8 11 0 14 12 0 16 17 0 8 14 0 18 15 0 17 17 0 8 13 0 15 14 0 18 17 0 8 15 0 17 16 0 19 17 0 8 18 0 21 19 0 20 20 0 0 17 0 8 18 0 21 20 0 0 29 0 24 32 0 23 38 0 22 22 0 25 29 0 24 38 0 22 38 0 22 39 0 27 40 0 26 22 0 25 38 0 22 40 0 26 40 0 26 41 0 29 42 0 28 22 0 25 40 0 26 42 0 28 22 0 25 42 0 28 43 0 30 23 0 33 24 0 32 25 0 31 22 0 25 23 0 33 25 0 31 25 0 31 27 0 34 29 0 24 22 0 25 25 0 31 29 0 24 25 0 31 26 0 35 27 0 34 27 0 34 28 0 36 29 0 24 30 0 38 31 0 37 32 0 23 29 0 24 30 0 38 32 0 23 33 0 41 34 0 40 35 0 39 32 0 23 33 0 41 35 0 39 35 0 39 36 0 43 37 0 42 32 0 23 35 0 39 37 0 42 32 0 23 37 0 42 38 0 22 61 1 46 63 1 45 62 1 44 61 1 46 64 1 47 63 1 45 60 1 48 64 1 47 61 1 46 60 1 48 59 1 49 64 1 47 59 1 49 65 1 50 64 1 47 66 1 51 65 1 50 59 1 49 67 1 52 66 1 51 59 1 49 68 1 53 67 1 52 59 1 49 69 1 54 68 1 53 59 1 49 70 1 55 69 1 54 59 1 49 71 1 56 70 1 55 59 1 49 72 1 57 71 1 56 59 1 49 73 1 58 72 1 57 59 1 49 75 1 59 73 1 58 59 1 49 76 1 60 75 1 59 59 1 49 77 1 61 76 1 60 59 1 49 78 1 62 77 1 61 59 1 49 58 1 63 78 1 62 59 1 49 58 1 63 79 1 64 78 1 62 58 1 63 80 1 65 79 1 64 58 1 63 57 1 66 80 1 65 57 1 66 81 1 67 80 1 65 45 1 69 44 1 68 81 1 67 45 1 69 81 1 67 57 1 66 56 1 70 45 1 69 57 1 66 56 1 70 55 1 71 45 1 69 55 1 71 46 1 72 45 1 69 54 1 73 46 1 72 55 1 71 54 1 73 53 1 74 46 1 72 53 1 74 47 1 75 46 1 72 48 1 76 47 1 75 53 1 74 52 1 77 48 1 76 53 1 74 52 1 77 49 1 78 48 1 76 51 1 79 49 1 78 52 1 77 51 1 79 50 1 80 49 1 78 75 1 59 74 1 81 73 1 58</p>\n\t\t\t\t</triangles>\n\t\t\t\t<triangles count=\"144\" material=\"Material1\">\n\t\t\t\t\t<input offset=\"0\" semantic=\"VERTEX\" source=\"#ID14\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"1\" semantic=\"NORMAL\" source=\"#ID10\" set=\"0\"/>\n\t\t\t\t\t<input offset=\"2\" semantic=\"TEXCOORD\" source=\"#ID12\" set=\"0\"/>\n\t\t\t\t\t<p>120 3 84 119 2 83 82 2 82 83 3 85 120 3 84 82 2 82 121 4 86 120 3 84 83 3 85 84 4 87 121 4 86 83 3 85 122 5 88 121 4 86 84 4 87 85 5 89 122 5 88 84 4 87 123 6 90 122 5 88 85 5 89 86 6 91 123 6 90 85 5 89 124 7 92 123 6 90 86 6 91 87 7 93 124 7 92 86 6 91 125 8 94 124 7 92 87 7 93 88 8 95 125 8 94 87 7 93 126 9 96 125 8 94 88 8 95 89 9 97 126 9 96 88 8 95 127 10 98 126 9 96 89 9 97 90 10 99 127 10 98 89 9 97 128 11 100 127 10 98 90 10 99 91 11 101 128 11 100 90 10 99 129 12 102 128 11 100 91 11 101 92 12 103 129 12 102 91 11 101 130 13 104 129 12 102 92 12 103 93 13 105 130 13 104 92 12 103 131 14 106 130 13 104 93 13 105 94 14 107 131 14 106 93 13 105 132 15 108 131 14 106 94 14 107 95 15 109 132 15 108 94 14 107 133 16 110 132 15 108 95 15 109 96 16 111 133 16 110 95 15 109 134 17 112 133 16 110 96 16 111 97 17 113 134 17 112 96 16 111 135 18 114 134 17 112 97 17 113 98 18 115 135 18 114 97 17 113 136 19 116 135 18 114 98 18 115 99 19 117 136 19 116 98 18 115 137 20 118 136 19 116 99 19 117 100 20 119 137 20 118 99 19 117 138 21 120 137 20 118 100 20 119 101 21 121 138 21 120 100 20 119 139 22 122 138 21 120 101 21 121 102 22 123 139 22 122 101 21 121 140 23 124 139 22 122 102 22 123 103 23 125 140 23 124 102 22 123 141 24 126 140 23 124 103 23 125 104 24 127 141 24 126 103 23 125 142 25 128 141 24 126 104 24 127 105 25 129 142 25 128 104 24 127 143 26 130 142 25 128 105 25 129 106 26 131 143 26 130 105 25 129 144 27 132 143 26 130 106 26 131 107 27 133 144 27 132 106 26 131 145 28 134 144 27 132 107 27 133 108 28 135 145 28 134 107 27 133 146 29 136 145 28 134 108 28 135 109 29 137 146 29 136 108 28 135 147 30 138 146 29 136 109 29 137 110 30 139 147 30 138 109 29 137 148 31 140 147 30 138 110 30 139 111 31 141 148 31 140 110 30 139 149 32 142 148 31 140 111 31 141 112 32 143 149 32 142 111 31 141 150 33 144 149 32 142 112 32 143 113 33 145 150 33 144 112 32 143 151 34 146 150 33 144 113 33 145 114 34 147 151 34 146 113 33 145 152 35 148 151 34 146 114 34 147 115 35 149 152 35 148 114 34 147 153 36 150 152 35 148 115 35 149 116 36 151 153 36 150 115 35 149 154 37 152 153 36 150 116 36 151 117 37 153 154 37 152 116 36 151 155 38 154 154 37 152 117 37 153 118 38 155 155 38 154 117 37 153 119 2 156 155 38 154 118 38 155 82 2 157 119 2 156 118 38 155 192 0 160 157 0 159 156 0 158 191 0 162 158 0 161 157 0 159 192 0 160 191 0 162 157 0 159 184 0 164 159 0 163 158 0 161 185 0 165 184 0 164 158 0 161 191 0 162 185 0 165 158 0 161 179 0 167 160 0 166 159 0 163 184 0 164 179 0 167 159 0 163 178 0 169 161 0 168 160 0 166 179 0 167 178 0 169 160 0 166 173 0 171 162 0 170 161 0 168 178 0 169 173 0 171 161 0 168 166 0 173 163 0 172 162 0 170 172 0 174 166 0 173 162 0 170 173 0 171 172 0 174 162 0 170 165 0 176 164 0 175 163 0 172 166 0 173 165 0 176 163 0 172 172 0 174 167 0 177 166 0 173 169 0 179 168 0 178 167 0 177 170 0 180 169 0 179 167 0 177 172 0 174 170 0 180 167 0 177 172 0 174 171 0 181 170 0 180 175 0 183 174 0 182 173 0 171 178 0 169 175 0 183 173 0 171 178 0 169 176 0 184 175 0 183 178 0 169 177 0 185 176 0 184 181 0 187 180 0 186 179 0 167 182 0 188 181 0 187 179 0 167 184 0 164 182 0 188 179 0 167 184 0 164 183 0 189 182 0 188 187 0 191 186 0 190 185 0 165 190 0 192 187 0 191 185 0 165 191 0 162 190 0 192 185 0 165 190 0 192 188 0 193 187 0 191 190 0 192 189 0 194 188 0 193 193 1 158 194 1 159 229 1 160 194 1 159 228 1 162 229 1 160 194 1 159 195 1 161 228 1 162 222 1 165 227 1 192 228 1 162 195 1 161 222 1 165 228 1 162 225 1 193 226 1 194 227 1 192 224 1 191 225 1 193 227 1 192 222 1 165 224 1 191 227 1 192 222 1 165 223 1 190 224 1 191 195 1 161 221 1 164 222 1 165 219 1 188 220 1 189 221 1 164 216 1 167 219 1 188 221 1 164 196 1 163 216 1 167 221 1 164 195 1 161 196 1 163 221 1 164 216 1 167 218 1 187 219 1 188 216 1 167 217 1 186 218 1 187 197 1 166 215 1 169 216 1 167 196 1 163 197 1 166 216 1 167 213 1 184 214 1 185 215 1 169 212 1 183 213 1 184 215 1 169 210 1 171 212 1 183 215 1 169 198 1 168 210 1 171 215 1 169 197 1 166 198 1 168 215 1 169 210 1 171 211 1 182 212 1 183 199 1 170 209 1 174 210 1 171 198 1 168 199 1 170 210 1 171 207 1 180 208 1 181 209 1 174 204 1 177 207 1 180 209 1 174 203 1 173 204 1 177 209 1 174 199 1 170 203 1 173 209 1 174 204 1 177 206 1 179 207 1 180 204 1 177 205 1 178 206 1 179 200 1 172 202 1 176 203 1 173 199 1 170 200 1 172 203 1 173 200 1 172 201 1 175 202 1 176</p>\n\t\t\t\t</triangles>\n\t\t\t</mesh>\n\t\t</geometry>\n\t</library_geometries>\n\t<library_visual_scenes>\n\t\t<visual_scene id=\"ID5\">\n\t\t\t<node id=\"ID6\" name=\"Eyes.1\">\n\t\t\t\t<translate sid=\"translate\">0 0 -0.14383</translate>\n\t\t\t\t<rotate sid=\"rotateY\">0 1 0 -0</rotate>\n\t\t\t\t<rotate sid=\"rotateX\">1 0 0 0</rotate>\n\t\t\t\t<rotate sid=\"rotateZ\">0 0 1 -0</rotate>\n\t\t\t\t<scale sid=\"scale\">1 1 1</scale>\n\t\t\t\t<instance_geometry url=\"#ID7\">\n\t\t\t\t\t<bind_material>\n\t\t\t\t\t\t<technique_common>\n\t\t\t\t\t\t\t<instance_material symbol=\"Material1\" target=\"#ID3\">\n\t\t\t\t\t\t\t\t<bind_vertex_input semantic=\"UVSET0\" input_semantic=\"TEXCOORD\" input_set=\"0\"/>\n\t\t\t\t\t\t\t</instance_material>\n\t\t\t\t\t\t\t<instance_material symbol=\"Material2\" target=\"#ID1\">\n\t\t\t\t\t\t\t\t<bind_vertex_input semantic=\"UVSET0\" input_semantic=\"TEXCOORD\" input_set=\"0\"/>\n\t\t\t\t\t\t\t</instance_material>\n\t\t\t\t\t\t</technique_common>\n\t\t\t\t\t</bind_material>\n\t\t\t\t</instance_geometry>\n\t\t\t</node>\n\t\t</visual_scene>\n\t</library_visual_scenes>\n\t<scene>\n\t\t<instance_visual_scene url=\"#ID5\"/>\n\t</scene>\n</COLLADA>\n"
  },
  {
    "path": "static/models/stump-shadow.obj",
    "content": "# WaveFront *.obj file (generated by CINEMA 4D)\r\n\r\nmtllib ./shadowUpdated.mtl\r\n\r\nv 0.14950437671081 0.05640361318484 0.29568244315973\r\nv 0.16634495138771 0.05640361318484 0.29966922580218\r\nv 0.17401577298040 0.05640361318484 0.30970348700778\r\nv 0.18462235904675 0.05640361318484 0.32149329583816\r\nv 0.19553021914222 0.05640361318484 0.33854293906683\r\nv 0.20410491305365 0.05640361318484 0.36435680580983\r\nv 0.21336806422455 0.05640361318484 0.39501629619561\r\nv 0.22634131183965 0.05640361318484 0.42660298042218\r\nv 0.24049432598953 0.05640361318484 0.44988549773341\r\nv 0.25329679350851 0.05640361318484 0.45563252148733\r\nv 0.26018140789744 0.05640361318484 0.44498175983335\r\nv 0.25658082967087 0.05640361318484 0.41907078471485\r\nv 0.24995525947560 0.05640361318484 0.38590302570700\r\nv 0.24776489921158 0.05640361318484 0.35348174244070\r\nv 0.24454173579479 0.05640361318484 0.32297977584284\r\nv 0.23481782562270 0.05640361318484 0.29556976340846\r\nv 0.18211250219123 0.05640361318484 0.24747097659571\r\nv 0.21515434871003 0.05640361318484 0.27061305324623\r\nv 0.14856284193008 0.05640361318484 0.17312850561623\r\nv 0.15385957358931 0.05640361318484 0.21776797891917\r\nv 0.18699764575706 0.05640361318484 0.11381239081366\r\nv 0.16126222205994 0.05640361318484 0.13224561920189\r\nv 0.21963609506720 0.05640361318484 0.12842620998537\r\nv 0.21128397448339 0.05640361318484 0.11386164162341\r\nv 0.24790292128626 0.05640361318484 0.15105071379687\r\nv 0.22689531420517 0.05640361318484 0.14499343891710\r\nv 0.28987860703287 0.05640361318484 0.12421420123501\r\nv 0.27233774324924 0.05640361318484 0.14474273458330\r\nv 0.33150214087084 0.05640361318484 0.10563451652653\r\nv 0.30733142426381 0.05640361318484 0.10574988540893\r\nv 0.39886360438621 0.05640361318484 0.11864799747634\r\nv 0.36210734842576 0.05640361318484 0.11341743487276\r\nv 0.48592668714661 0.05640361318484 0.13332088169684\r\nv 0.51072406454373 0.05640361318484 0.14050129945336\r\nv 0.43458227458195 0.05640361318484 0.12286253247611\r\nv 0.46207472465476 0.05640361318484 0.12759739350254\r\nv 0.53823659546513 0.05640361318484 0.11908261821634\r\nv 0.52050689407968 0.05640361318484 0.10924375479644\r\nv 0.53089947328184 0.05640361318484 0.14252578095764\r\nv 0.54088556403614 0.05640361318484 0.13278142665696\r\nv 0.44789499677498 0.05640361318484 0.08032638312325\r\nv 0.41614682010024 0.05640361318484 0.06342550464276\r\nv 0.49604049800409 0.05640361318484 0.10100586282275\r\nv 0.47318137751028 0.05640361318484 0.09210995122506\r\nv 0.31934847687743 0.05640361318484 0.02672159860424\r\nv 0.27280168335637 0.05640361318484 0.01624910670947\r\nv 0.38296699750131 0.05640361318484 0.04676782253074\r\nv 0.35338554300672 0.05640361318484 0.03571384678040\r\nv 0.19347510574758 0.05640361318484 -0.04055761587822\r\nv 0.17325003623261 0.05640361318484 -0.02420608073146\r\nv 0.18790502168731 0.05640361318484 -0.00923874367124\r\nv 0.22517669356847 0.05640361318484 0.00427083872904\r\nv 0.35145694304190 0.05640361318484 -0.09285543299381\r\nv 0.31091366180373 0.05640361318484 -0.07249108037442\r\nv 0.27089562871957 0.05640361318484 -0.06246227608471\r\nv 0.23166279348083 0.05640361318484 -0.05455559703281\r\nv 0.47561869758021 0.05640361318484 -0.16272207486492\r\nv 0.44676539566509 0.05640361318484 -0.14689830012785\r\nv 0.41919371975148 0.05640361318484 -0.13232924816573\r\nv 0.38879410261487 0.05640361318484 -0.11548994902276\r\nv 0.46364446029141 0.05640361318484 -0.19350531332892\r\nv 0.48937588920588 0.05640361318484 -0.19656096805127\r\nv 0.50081694429232 0.05640361318484 -0.19021699447158\r\nv 0.49666535072068 0.05640361318484 -0.17782137470253\r\nv 0.27982034126556 0.05640361318484 -0.15720186474671\r\nv 0.23544770015814 0.05640361318484 -0.16067550968258\r\nv 0.19845218691849 0.05640361318484 -0.16578435686386\r\nv 0.17441088604015 0.05640361318484 -0.16613172624464\r\nv 0.43177421009452 0.05640361318484 -0.18576178973983\r\nv 0.40191679312734 0.05640361318484 -0.17804215596742\r\nv 0.36851032558320 0.05640361318484 -0.17011779400843\r\nv 0.32599302624851 0.05640361318484 -0.16176017007986\r\nv 0.15795525154965 0.05640361318484 -0.31309919685922\r\nv 0.16318759201641 0.05640361318484 -0.34925876703950\r\nv 0.15906958531543 0.05640361318484 -0.37770430091127\r\nv 0.13687757954575 0.05640361318484 -0.39441981538226\r\nv 0.15987238136593 0.05635788260275 -0.17303623915209\r\nv 0.15138527448498 0.05640361318484 -0.19781661875459\r\nv 0.14883232282003 0.05640361318484 -0.23353201378131\r\nv 0.15209623281052 0.05640361318484 -0.27324157333737\r\nv 0.05743107777568 0.05640361318484 -0.46751946434770\r\nv 0.05773105358220 0.05640361318484 -0.45180773856575\r\nv 0.05739863274067 0.05640361318484 -0.50828042799014\r\nv 0.05691594347042 0.05640361318484 -0.48559278468791\r\nv 0.08020460694005 0.05640361318484 -0.41296329284817\r\nv 0.10709482730674 0.05640361318484 -0.40448092868382\r\nv 0.05660289681164 0.05640361318484 -0.43620481089797\r\nv 0.06158220869091 0.05640361318484 -0.42262018410592\r\nv 0.03965969866836 0.05640361318484 -0.54706409382641\r\nv 0.04310411245281 0.05640361318484 -0.55614585586501\r\nv 0.03469332164653 0.05640361318484 -0.50740141882140\r\nv 0.03778704956751 0.05640361318484 -0.53097392690294\r\nv 0.05964233780276 0.05640361318484 -0.54562417817370\r\nv 0.05845035741089 0.05640361318484 -0.53011376794331\r\nv 0.05091312992534 0.05640361318484 -0.55869311533440\r\nv 0.05809108896026 0.05640361318484 -0.55506589365342\r\nv 0.00994350275504 0.05640361318484 -0.39507563872645\r\nv 0.02178890365339 0.05640361318484 -0.38021270562588\r\nv 0.01290828696189 0.05640361318484 -0.42578646389404\r\nv 0.00909934840048 0.05640361318484 -0.41091999805923\r\nv 0.01780949277752 0.05640361318484 -0.44955006372288\r\nv 0.01502216456079 0.05640361318484 -0.43771568251186\r\nv 0.03007755337493 0.05640361318484 -0.48292721212467\r\nv 0.02363878241069 0.05640361318484 -0.46413198335231\r\nv 0.00008827227549 0.05640361318484 -0.17909500313560\r\nv -0.05458277635327 0.05640361318484 -0.16826089007699\r\nv 0.03614902962933 0.05640361318484 -0.26638757727322\r\nv 0.02429286215660 0.05640361318484 -0.21783528770653\r\nv 0.06476393915451 0.05640361318484 -0.33663619044843\r\nv 0.05377480878334 0.05640361318484 -0.30665768834030\r\nv 0.03919223104353 0.05640361318484 -0.36655186224513\r\nv 0.05671016121555 0.05640361318484 -0.35431363613598\r\nv -0.24384573795526 0.05640361318484 -0.31510866209724\r\nv -0.24184227348621 0.05640361318484 -0.29249216646681\r\nv -0.22298667339906 0.05640361318484 -0.25543729288014\r\nv -0.20038017448753 0.05640361318484 -0.22154342741369\r\nv -0.18712414925012 0.05640361318484 -0.20840999113532\r\nv -0.17393150962190 0.05640361318484 -0.20206046040144\r\nv -0.15151520140146 0.05640361318484 -0.18851832881355\r\nv -0.11376802191348 0.05640361318484 -0.17488473186326\r\nv -0.26342857163534 0.05640361318484 -0.49849330228455\r\nv -0.25352831296675 0.05640361318484 -0.49891639223142\r\nv -0.24889724757534 0.05640361318484 -0.49344988389595\r\nv -0.24279848106049 0.05640361318484 -0.47541081534760\r\nv -0.22849525422505 0.05640361318484 -0.43811632762489\r\nv -0.21810780774524 0.05640361318484 -0.39533861361764\r\nv -0.22375644902185 0.05640361318484 -0.36085003666115\r\nv -0.23561210552336 0.05640361318484 -0.33424266325032\r\nv -0.30163916090096 0.05640361318484 -0.34020833534170\r\nv -0.28306664180858 0.05640361318484 -0.35111954779776\r\nv -0.34978939432746 0.05640361318484 -0.33010884170512\r\nv -0.32498798462066 0.05640361318484 -0.33297553396816\r\nv -0.38587666930047 0.05640361318484 -0.31606364184603\r\nv -0.37157504736381 0.05640361318484 -0.32625573125835\r\nv -0.40340609117008 0.05640361318484 -0.29575691538612\r\nv -0.39553885326494 0.05640361318484 -0.30430619646312\r\nv -0.27402172242684 0.05640361318484 -0.48676410197482\r\nv -0.27234427867757 0.05640361318484 -0.49387703210881\r\nv -0.26682249281253 0.05640361318484 -0.42754065799254\r\nv -0.27125103683518 0.05640361318484 -0.46777764466091\r\nv -0.26386820422688 0.05640361318484 -0.37854864797177\r\nv -0.26345517517388 0.05640361318484 -0.38986158073516\r\nv -0.27259404376657 0.05640361318484 -0.36502150654533\r\nv -0.26720125681400 0.05640361318484 -0.37610193229430\r\nv -0.48305609680417 0.05640361318484 -0.31347838429937\r\nv -0.46681330835225 0.05640361318484 -0.28705572639149\r\nv -0.44737600956643 0.05640361318484 -0.26613643333820\r\nv -0.42464973953878 0.05640361318484 -0.26005494524050\r\nv -0.39882082169765 0.05640361318484 -0.26389010350839\r\nv -0.37007571592804 0.05640361318484 -0.27272068257666\r\nv -0.34355822977156 0.05640361318484 -0.28012157090700\r\nv -0.32441220475866 0.05640361318484 -0.27966765721171\r\nv -0.41484951769907 0.05640361318484 -0.29560249728995\r\nv -0.43524019980806 0.05640361318484 -0.30902960635937\r\nv -0.45678659822136 0.05640361318484 -0.32936200077107\r\nv -0.47169714017572 0.05640361318484 -0.34992337047342\r\nv -0.48345430386632 0.05640361318484 -0.36369859647964\r\nv -0.49554060197844 0.05640361318484 -0.36367249207593\r\nv -0.50183055936458 0.05640361318484 -0.35330844175333\r\nv -0.49619876885494 0.05640361318484 -0.33606983050415\r\nv -0.27630975620629 0.05640361318484 -0.15257396878232\r\nv -0.28394960410633 0.05640361318484 -0.18207085293964\r\nv -0.29547847577751 0.05640361318484 -0.22525642544630\r\nv -0.30944859139795 0.05640361318484 -0.26387417891985\r\nv -0.20165659300473 0.05640361318484 -0.05108462792983\r\nv -0.22809863364121 0.05640361318484 -0.08734120656978\r\nv -0.26140930769094 0.05640361318484 -0.12493949018129\r\nv -0.20736202759764 0.05640361318484 -0.02747528270583\r\nv -0.22933636692715 0.05640361318484 -0.01274079767528\r\nv -0.25170102351390 0.05640361318484 -0.00310879842822\r\nv -0.26915086181584 0.05640361318484 -0.00651743500330\r\nv -0.27638076325012 0.05640361318484 -0.03090486234418\r\nv -0.28613649765992 0.05640361318484 -0.05594896295752\r\nv -0.31116388588736 0.05640361318484 -0.06132761760160\r\nv -0.34052379063217 0.05640361318484 -0.05754784770681\r\nv -0.36327707461762 0.05640361318484 -0.05511667151706\r\nv -0.38457342101879 0.05640361318484 -0.05323327214557\r\nv -0.40956261503999 0.05640361318484 -0.05109682496023\r\nv -0.43445280923376 0.05640361318484 -0.04917498305804\r\nv -0.45545208815929 0.05640361318484 -0.04793540115903\r\nv -0.47178926769648 0.05640361318484 -0.04689451313311\r\nv -0.48269299381235 0.05640361318484 -0.04556874734843\r\nv -0.49302679968208 0.05640361318484 -0.04296589966705\r\nv -0.50765428641155 0.05640361318484 -0.03809377282519\r\nv -0.52593973564569 0.05640361318484 -0.03220485891872\r\nv -0.54724759893842 0.05640361318484 -0.02655165607632\r\nv -0.56957123646536 0.05640361318484 -0.02146593120819\r\nv -0.59090400837866 0.05640361318484 -0.01727945441095\r\nv -0.60589162416557 0.05640361318484 -0.01088649680698\r\nv -0.60917952138301 0.05640361318484 0.00081866983102\r\nv -0.60204861310650 0.05640361318484 0.01065541308220\r\nv -0.58578008427537 0.05640361318484 0.01144309214770\r\nv -0.56434515541587 0.05640361318484 0.00795985488989\r\nv -0.54171504705423 0.05640361318484 0.00498384917110\r\nv -0.51980992053710 0.05640361318484 0.00288749407578\r\nv -0.50054980120872 0.05640361318484 0.00204320331797\r\nv -0.48504986931283 0.05640361318484 0.00151246011518\r\nv -0.47442523706845 0.05640361318484 0.00035674181323\r\nv -0.46370049829082 0.05640361318484 -0.00219620338477\r\nv -0.44790028082317 0.05640361318484 -0.00691862221559\r\nv -0.42741422271441 0.05640361318484 -0.01145466833510\r\nv -0.40263189405930 0.05640361318484 -0.01344849171146\r\nv -0.37764218672262 0.05640361318484 -0.01411035181807\r\nv -0.35653395858818 0.05640361318484 -0.01465050681558\r\nv -0.34605127454691 0.05640361318484 -0.00556339287801\r\nv -0.35293813153558 0.05640361318484 0.02265655750833\r\nv -0.35683276426059 0.05640361318484 0.05370360683366\r\nv -0.33737337347081 0.05640361318484 0.07127202208745\r\nv -0.31208130897664 0.05640361318484 0.08990899760028\r\nv -0.29847795435798 0.05640361318484 0.12416169771185\r\nv -0.29654684363749 0.05640361318484 0.15950347031736\r\nv -0.30627161255372 0.05640361318484 0.18140762857124\r\nv -0.32326777566889 0.05640361318484 0.18871370206802\r\nv -0.34315074545337 0.05640361318484 0.18026120415970\r\nv -0.36283891590967 0.05640361318484 0.17283220892213\r\nv -0.37925071502913 0.05640361318484 0.18320879018052\r\nv -0.39352632295631 0.05640361318484 0.20047502803526\r\nv -0.40680588584693 0.05640361318484 0.21371500283735\r\nv -0.42115711356509 0.05640361318484 0.22472940326308\r\nv -0.43864781806677 0.05640361318484 0.23531893423126\r\nv -0.45517281301009 0.05640361318484 0.24492664402451\r\nv -0.46662694629266 0.05640361318484 0.25299561466362\r\nv -0.47853605547785 0.05640361318484 0.25985829960536\r\nv -0.49642604635734 0.05640361318484 0.26584718579406\r\nv -0.51723246042089 0.05640361318484 0.27218931377132\r\nv -0.53789070408005 0.05640361318484 0.28011184404220\r\nv -0.55262593583353 0.05640361318484 0.28846343695847\r\nv -0.55566331518256 0.05640361318484 0.29609288882726\r\nv -0.54822396146570 0.05640361318484 0.29984314687433\r\nv -0.53152913148069 0.05640361318484 0.29655736125599\r\nv -0.51035571713400 0.05640361318484 0.29050526287923\r\nv -0.48948061183591 0.05640361318484 0.28595678658414\r\nv -0.47152479820250 0.05640361318484 0.28186476066797\r\nv -0.45910936106706 0.05640361318484 0.27718204666495\r\nv -0.44642716347938 0.05640361318484 0.27209810211571\r\nv -0.42767103399915 0.05640361318484 0.26680231683382\r\nv -0.40451291164032 0.05640361318484 0.25946994410446\r\nv -0.37862476902979 0.05640361318484 0.24827618597890\r\nv -0.35405842671150 0.05640361318484 0.24266975881972\r\nv -0.33486573871698 0.05640361318484 0.25209931076122\r\nv -0.32300667919377 0.05640361318484 0.26873818847084\r\nv -0.32044118792458 0.05640361318484 0.28475968788339\r\nv -0.32271866177141 0.05640361318484 0.30338162379047\r\nv -0.32538846385804 0.05640361318484 0.32782184522314\r\nv -0.33215983426196 0.05640361318484 0.35337934767842\r\nv -0.34674204629763 0.05640361318484 0.37535302443612\r\nv -0.35909828918493 0.05640361318484 0.39204138269945\r\nv -0.35919185411029 0.05640361318484 0.40174292891971\r\nv -0.34948329760540 0.05640361318484 0.40192757262150\r\nv -0.33243331315988 0.05640361318484 0.39006535828229\r\nv -0.31126691691729 0.05640361318484 0.36865641583427\r\nv -0.28920929521606 0.05640361318484 0.34020090794535\r\nv -0.27388342827381 0.05640361318484 0.30774423993483\r\nv -0.27291222908236 0.05640361318484 0.27433191958983\r\nv -0.27320387717831 0.05640361318484 0.24498169255796\r\nv -0.26166653472789 0.05640361318484 0.22471125362889\r\nv -0.24526690292632 0.05640361318484 0.20744551842185\r\nv -0.23097171708298 0.05640361318484 0.18710941929987\r\nv -0.22367700547229 0.05640361318484 0.16798176793520\r\nv -0.22827888146611 0.05640361318484 0.15434139236796\r\nv -0.23508394535485 0.05640361318484 0.14099087375839\r\nv -0.23439881429798 0.05640361318484 0.12273277614699\r\nv -0.21862695398085 0.05640361318484 0.10931104226703\r\nv -0.18017176211105 0.05640361318484 0.11046961535304\r\nv -0.14304502763687 0.05640361318484 0.12843447329074\r\nv -0.13125852263746 0.05640361318484 0.16543161108559\r\nv -0.13614725519273 0.05640361318484 0.21043284244991\r\nv -0.14904616515427 0.05640361318484 0.25240994760846\r\nv -0.16460171034997 0.05640361318484 0.29277241542558\r\nv -0.17746031424294 0.05640361318484 0.33292968403301\r\nv -0.19011308427649 0.05640361318484 0.37500639545230\r\nv -0.20505107640939 0.05640361318484 0.42112712410328\r\nv -0.22175703819869 0.05640361318484 0.46715262628915\r\nv -0.23971364897313 0.05640361318484 0.50894362482552\r\nv -0.24831379974442 0.05640361318484 0.54119825497631\r\nv -0.23695033154354 0.05640361318484 0.55861478833680\r\nv -0.21629590014574 0.05640361318484 0.55619447380179\r\nv -0.19702312733742 0.05640361318484 0.52893856051672\r\nv -0.17641432403605 0.05640361318484 0.48838976450056\r\nv -0.15175181865480 0.05640361318484 0.44609086962469\r\nv -0.12885726568490 0.05640361318484 0.40369671442018\r\nv -0.11355232036948 0.05640361318484 0.36286223938463\r\nv -0.09739498511590 0.05640361318484 0.32425000526786\r\nv -0.07194324558775 0.05640361318484 0.28852260693386\r\nv -0.04248612079274 0.05640361318484 0.24602633316673\r\nv -0.01431263148692 0.05640361318484 0.18710742175161\r\nv 0.01067685257304 0.05640361318484 0.15420826063910\r\nv 0.03058196028790 0.05640361318484 0.18977120377919\r\nv 0.04544043111681 0.05640361318484 0.24886574155731\r\nv 0.05529000755028 0.05640361318484 0.28656131339524\r\nv 0.07298721228214 0.05640361318484 0.30392778005720\r\nv 0.11138856538066 0.05640361318484 0.30203507026944\r\n# 292 vertices\r\n\r\nvt 0.56509065628052 -0.81152510643005 0.00000000000000\r\nvt 0.59009295701981 -0.78740584850311 0.00000000000000\r\nvt 0.58960372209549 -0.76500785350800 0.00000000000000\r\nvt 0.90531194210052 -0.74878060817719 0.00000000000000\r\nvt 0.93518441915512 -0.75294911861420 0.00000000000000\r\nvt 0.94511520862579 -0.72258603572845 0.00000000000000\r\nvt 0.31276014447212 -0.66935694217682 0.00000000000000\r\nvt 0.29860022664070 -0.70226025581360 0.00000000000000\r\nvt 0.39435943961143 -0.74488103389740 0.00000000000000\r\nvt 0.57087159156799 -0.73520195484161 0.00000000000000\r\nvt 0.48332142829895 -0.78736615180969 0.00000000000000\r\nvt 0.86585867404938 -0.73726868629456 0.00000000000000\r\nvt 0.91855299472809 -0.70829653739929 0.00000000000000\r\nvt 0.54114532470703 -0.68885970115662 0.00000000000000\r\nvt 0.45719981193542 -0.75626301765442 0.00000000000000\r\nvt 0.53003823757172 -0.65375089645386 0.00000000000000\r\nvt 0.83927667140961 -0.67679941654205 0.00000000000000\r\nvt 0.82274514436722 -0.72734379768372 0.00000000000000\r\nvt 0.56716364622116 -0.65764546394348 0.00000000000000\r\nvt 0.62523347139359 -0.56721925735474 0.00000000000000\r\nvt 0.74430227279663 -0.70101416110992 0.00000000000000\r\nvt 0.78066259622574 -0.71619570255280 0.00000000000000\r\nvt 0.79966193437576 -0.65810608863831 0.00000000000000\r\nvt 0.35804006457329 -0.41128742694855 0.00000000000000\r\nvt 0.60815954208374 -0.53656983375549 0.00000000000000\r\nvt 0.70887809991837 -0.68776869773865 0.00000000000000\r\nvt 0.76412415504456 -0.64040935039520 0.00000000000000\r\nvt 0.31708452105522 -0.44059002399445 0.00000000000000\r\nvt 0.42241942882538 -0.73933553695679 0.00000000000000\r\nvt 0.31196892261505 -0.65044653415680 0.00000000000000\r\nvt 0.69351863861084 -0.60931122303009 0.00000000000000\r\nvt 0.66960418224335 -0.68242895603180 0.00000000000000\r\nvt 0.62339460849762 -0.67553961277008 0.00000000000000\r\nvt 0.65789425373077 -0.59183824062347 0.00000000000000\r\nvt 0.37936788797379 -0.76090586185455 0.00000000000000\r\nvt 0.27702710032463 -0.74103474617004 0.00000000000000\r\nvt 0.94955557584763 -0.74084389209747 0.00000000000000\r\nvt 0.31460902094841 -0.81914186477661 0.00000000000000\r\nvt 0.25512927770615 -0.77816760540009 0.00000000000000\r\nvt 0.61929565668106 -0.50100588798523 0.00000000000000\r\nvt 0.54526948928833 -0.39776390790939 0.00000000000000\r\nvt 0.64232176542282 -0.47496366500854 0.00000000000000\r\nvt 0.56856691837311 -0.39240723848343 0.00000000000000\r\nvt 0.91726356744766 -0.38884919881821 0.00000000000000\r\nvt 0.92621064186096 -0.40586346387863 0.00000000000000\r\nvt 0.94211983680725 -0.39492946863174 0.00000000000000\r\nvt 0.23999537527561 -0.80614626407623 0.00000000000000\r\nvt 0.29958060383797 -0.83498859405518 0.00000000000000\r\nvt 0.68735671043396 -0.40141481161118 0.00000000000000\r\nvt 0.66091769933701 -0.47287970781326 0.00000000000000\r\nvt 0.90225237607956 -0.39692622423172 0.00000000000000\r\nvt 0.91240793466568 -0.41515725851059 0.00000000000000\r\nvt 0.58874547481537 -0.38363707065582 0.00000000000000\r\nvt 0.72357970476151 -0.45469778776169 0.00000000000000\r\nvt 0.67794829607010 -0.49646377563477 0.00000000000000\r\nvt 0.70119422674179 -0.51496124267578 0.00000000000000\r\nvt 0.22897557914257 -0.83246588706970 0.00000000000000\r\nvt 0.28577327728271 -0.85995900630951 0.00000000000000\r\nvt 0.90164363384247 -0.42043685913086 0.00000000000000\r\nvt 0.70149928331375 -0.43156296014786 0.00000000000000\r\nvt 0.67336577177048 -0.48372328281403 0.00000000000000\r\nvt 0.68408465385437 -0.50793266296387 0.00000000000000\r\nvt 0.72343027591705 -0.51812708377838 0.00000000000000\r\nvt 0.74494612216949 -0.51800763607025 0.00000000000000\r\nvt 0.74402111768723 -0.46184366941452 0.00000000000000\r\nvt 0.89045006036758 -0.42564398050308 0.00000000000000\r\nvt 0.89036566019058 -0.40052384138107 0.00000000000000\r\nvt 0.76543283462524 -0.52294921875000 0.00000000000000\r\nvt 0.87535947561264 -0.43472015857697 0.00000000000000\r\nvt 0.87796592712402 -0.40307557582855 0.00000000000000\r\nvt 0.78458166122437 -0.54129779338837 0.00000000000000\r\nvt 0.81188714504242 -0.50484669208527 0.00000000000000\r\nvt 0.82749015092850 -0.41562056541443 0.00000000000000\r\nvt 0.83281779289246 -0.45556002855301 0.00000000000000\r\nvt 0.85620456933975 -0.44543552398682 0.00000000000000\r\nvt 0.86141586303711 -0.40801495313644 0.00000000000000\r\nvt 0.80826544761658 -0.48674356937408 0.00000000000000\r\nvt 0.75934678316116 -0.45517015457153 0.00000000000000\r\nvt 0.81392836570740 -0.46827048063278 0.00000000000000\r\nvt 0.81055569648743 -0.41617560386658 0.00000000000000\r\nvt 0.79035347700119 -0.41489595174789 0.00000000000000\r\nvt 0.82085138559341 -0.51644742488861 0.00000000000000\r\nvt 0.80718684196472 -0.56132769584656 0.00000000000000\r\nvt 0.83452415466309 -0.52646744251251 0.00000000000000\r\nvt 0.83804273605347 -0.57131278514862 0.00000000000000\r\nvt 0.87042796611786 -0.57438933849335 0.00000000000000\r\nvt 0.87329167127609 -0.55117928981781 0.00000000000000\r\nvt 0.91598075628281 -0.56980335712433 0.00000000000000\r\nvt 0.92186504602432 -0.56329536437988 0.00000000000000\r\nvt 0.91541802883148 -0.55735540390015 0.00000000000000\r\nvt 0.89762121438980 -0.57369375228882 0.00000000000000\r\nvt 0.89678376913071 -0.55516910552979 0.00000000000000\r\nvt 0.93431156873703 -0.37907004356384 0.00000000000000\r\nvt 0.95920383930206 -0.38472920656204 0.00000000000000\r\nvt 0.77348303794861 -0.19764834642410 0.00000000000000\r\nvt 0.78944236040115 -0.18458580970764 0.00000000000000\r\nvt 0.77329629659653 -0.16294074058533 0.00000000000000\r\nvt 0.21942020952702 -0.86462199687958 0.00000000000000\r\nvt 0.26647987961769 -0.88740134239197 0.00000000000000\r\nvt 0.21091064810753 -0.89598560333252 0.00000000000000\r\nvt 0.95230865478516 -0.37036615610123 0.00000000000000\r\nvt 0.97169548273087 -0.37576711177826 0.00000000000000\r\nvt 0.75855153799057 -0.21077761054039 0.00000000000000\r\nvt 0.75613719224930 -0.17449632287025 0.00000000000000\r\nvt 0.97382777929306 -0.36854779720306 0.00000000000000\r\nvt 0.96692425012589 -0.36582839488983 0.00000000000000\r\nvt 0.74608081579208 -0.22293949127197 0.00000000000000\r\nvt 0.73923271894455 -0.18503192067146 0.00000000000000\r\nvt 0.77336710691452 -0.41928511857986 0.00000000000000\r\nvt 0.76608020067215 -0.43684691190720 0.00000000000000\r\nvt 0.73614275455475 -0.23281896114349 0.00000000000000\r\nvt 0.72404271364212 -0.19343119859695 0.00000000000000\r\nvt 0.69072902202606 -0.37322902679443 0.00000000000000\r\nvt 0.59729540348053 -0.37003642320633 0.00000000000000\r\nvt 0.70367842912674 -0.20524585247040 0.00000000000000\r\nvt 0.72880905866623 -0.23910108208656 0.00000000000000\r\nvt 0.69119691848755 -0.34954547882080 0.00000000000000\r\nvt 0.66834092140198 -0.33290410041809 0.00000000000000\r\nvt 0.69606006145477 -0.21160659193993 0.00000000000000\r\nvt 0.72049319744110 -0.24458479881287 0.00000000000000\r\nvt 0.58570712804794 -0.35018855333328 0.00000000000000\r\nvt 0.64549571275711 -0.31998223066330 0.00000000000000\r\nvt 0.68526804447174 -0.22172185778618 0.00000000000000\r\nvt 0.70760887861252 -0.25206899642944 0.00000000000000\r\nvt 0.64599597454071 -0.30745714902878 0.00000000000000\r\nvt 0.60784083604813 -0.29146391153336 0.00000000000000\r\nvt 0.67146116495132 -0.23478457331657 0.00000000000000\r\nvt 0.69194853305817 -0.26275444030762 0.00000000000000\r\nvt 0.62351757287979 -0.27798324823380 0.00000000000000\r\nvt 0.65940964221954 -0.29389005899429 0.00000000000000\r\nvt 0.63793218135834 -0.26512318849564 0.00000000000000\r\nvt 0.67530465126038 -0.27784198522568 0.00000000000000\r\nvt 0.65479797124863 -0.24998766183853 0.00000000000000\r\nvt 0.22222928702831 -0.54956734180450 0.00000000000000\r\nvt 0.80499631166458 -0.17262417078018 0.00000000000000\r\nvt 0.78925025463104 -0.15148141980171 0.00000000000000\r\nvt 0.81454157829285 -0.16142290830612 0.00000000000000\r\nvt 0.80273205041885 -0.14556580781937 0.00000000000000\r\nvt 0.26975423097610 -0.06945975124836 0.00000000000000\r\nvt 0.26335111260414 -0.07571804523468 0.00000000000000\r\nvt 0.26408633589745 -0.08214962482452 0.00000000000000\r\nvt 0.81247472763062 -0.15064153075218 0.00000000000000\r\nvt 0.28403186798096 -0.06973929703236 0.00000000000000\r\nvt 0.27830979228020 -0.06644383072853 0.00000000000000\r\nvt 0.58718901872635 -0.30846130847931 0.00000000000000\r\nvt 0.57524883747101 -0.32827097177505 0.00000000000000\r\nvt 0.27219355106354 -0.09732538461685 0.00000000000000\r\nvt 0.29482179880142 -0.08330936729908 0.00000000000000\r\nvt 0.52619153261185 -0.38691616058350 0.00000000000000\r\nvt 0.51867151260376 -0.34707319736481 0.00000000000000\r\nvt 0.39029085636139 -0.36821210384369 0.00000000000000\r\nvt 0.51451694965363 -0.30187678337097 0.00000000000000\r\nvt 0.40961194038391 -0.31177872419357 0.00000000000000\r\nvt 0.24715922772884 -0.91353178024292 0.00000000000000\r\nvt 0.20302835106850 -0.91992795467377 0.00000000000000\r\nvt 0.40466326475143 -0.33491885662079 0.00000000000000\r\nvt 0.50553536415100 -0.27496856451035 0.00000000000000\r\nvt 0.41359153389931 -0.29916250705719 0.00000000000000\r\nvt 0.28790637850761 -0.12981614470482 0.00000000000000\r\nvt 0.31858116388321 -0.11111721396446 0.00000000000000\r\nvt 0.30965271592140 -0.16312009096146 0.00000000000000\r\nvt 0.34131243824959 -0.13664379715919 0.00000000000000\r\nvt 0.41314262151718 -0.28230786323547 0.00000000000000\r\nvt 0.33586025238037 -0.18073531985283 0.00000000000000\r\nvt 0.36071723699570 -0.18938732147217 0.00000000000000\r\nvt 0.36332488059998 -0.14599171280861 0.00000000000000\r\nvt 0.23327013850212 -0.93456661701202 0.00000000000000\r\nvt 0.19474586844444 -0.94048559665680 0.00000000000000\r\nvt 0.34901830554008 -0.14337018132210 0.00000000000000\r\nvt 0.49083572626114 -0.25168985128403 0.00000000000000\r\nvt 0.46952688694000 -0.21738192439079 0.00000000000000\r\nvt 0.40480589866638 -0.24645251035690 0.00000000000000\r\nvt 0.39763396978378 -0.14221331477165 0.00000000000000\r\nvt 0.37895885109901 -0.14769476652145 0.00000000000000\r\nvt 0.37841182947159 -0.19580152630806 0.00000000000000\r\nvt 0.45273652672768 -0.18429195880890 0.00000000000000\r\nvt 0.39206701517105 -0.21111193299294 0.00000000000000\r\nvt 0.51284867525101 -0.00226900354028 0.00000000000000\r\nvt 0.50213211774826 -0.00000000000000 0.00000000000000\r\nvt 0.49463811516762 -0.00794354267418 0.00000000000000\r\nvt 0.45159214735031 -0.16466718912125 0.00000000000000\r\nvt 0.41684129834175 -0.13131079077721 0.00000000000000\r\nvt 0.46313729882240 -0.15177738666534 0.00000000000000\r\nvt 0.49643689393997 -0.02420808002353 0.00000000000000\r\nvt 0.52071756124496 -0.01664206013083 0.00000000000000\r\nvt 0.52746534347534 -0.03927292674780 0.00000000000000\r\nvt 0.48441550135612 -0.13889205455780 0.00000000000000\r\nvt 0.45008635520935 -0.10478687286377 0.00000000000000\r\nvt 0.53481847047806 -0.06631550192833 0.00000000000000\r\nvt 0.50075405836105 -0.04674737900496 0.00000000000000\r\nvt 0.50801330804825 -0.12542408704758 0.00000000000000\r\nvt 0.48986327648163 -0.10269097983837 0.00000000000000\r\nvt 0.53657108545303 -0.09205740690231 0.00000000000000\r\nvt 0.50081545114517 -0.07351520657539 0.00000000000000\r\nvt 0.47936615347862 -0.10258337855339 0.00000000000000\r\nvt 0.46564581990242 -0.10167324542999 0.00000000000000\r\nvt 0.49704408645630 -0.09525018930435 0.00000000000000\r\nvt 0.52651727199554 -0.11078640818596 0.00000000000000\r\nvt 0.35269153118134 -0.14268869161606 0.00000000000000\r\nvt 0.18921807408333 -0.50487291812897 0.00000000000000\r\nvt 0.21115991473198 -0.52864837646484 0.00000000000000\r\nvt 0.27659785747528 -0.43256503343582 0.00000000000000\r\nvt 0.18503575026989 -0.96169447898865 0.00000000000000\r\nvt 0.21167291700840 -0.97458827495575 0.00000000000000\r\nvt 0.54877799749374 -0.91113877296448 0.00000000000000\r\nvt 0.51460713148117 -0.93316447734833 0.00000000000000\r\nvt 0.53460383415222 -0.95466339588165 0.00000000000000\r\nvt 0.16104736924171 -0.48245733976364 0.00000000000000\r\nvt 0.23728826642036 -0.41033786535263 0.00000000000000\r\nvt 0.53708213567734 -0.88952279090881 0.00000000000000\r\nvt 0.49651327729225 -0.91217267513275 0.00000000000000\r\nvt 0.13117377460003 -0.46165931224823 0.00000000000000\r\nvt 0.19986389577389 -0.39703387022018 0.00000000000000\r\nvt 0.48447722196579 -0.88881254196167 0.00000000000000\r\nvt 0.17332282662392 -0.38572573661804 0.00000000000000\r\nvt 0.09065736830235 -0.39747172594070 0.00000000000000\r\nvt 0.10412329435349 -0.44273620843887 0.00000000000000\r\nvt 0.48020562529564 -0.86042404174805 0.00000000000000\r\nvt 0.53260153532028 -0.87179195880890 0.00000000000000\r\nvt 0.16666318476200 -0.36948615312576 0.00000000000000\r\nvt 0.10245556384325 -0.37163364887238 0.00000000000000\r\nvt 0.52953559160233 -0.84625422954559 0.00000000000000\r\nvt 0.48540520668030 -0.82434749603271 0.00000000000000\r\nvt 0.16944034397602 -0.35037857294083 0.00000000000000\r\nvt 0.11350409686565 -0.34867715835571 0.00000000000000\r\nvt 0.53732788562775 -0.83269715309143 0.00000000000000\r\nvt 0.17120972275734 -0.33046638965607 0.00000000000000\r\nvt 0.11866450309753 -0.33043909072876 0.00000000000000\r\nvt 0.38120642304420 -0.80802786350250 0.00000000000000\r\nvt 0.39021295309067 -0.79044568538666 0.00000000000000\r\nvt 0.38379293680191 -0.77541625499725 0.00000000000000\r\nvt 0.16874919831753 -0.31347084045410 0.00000000000000\r\nvt 0.15883676707745 -0.30311328172684 0.00000000000000\r\nvt 0.55234849452972 -0.97954499721527 0.00000000000000\r\nvt 0.57601571083069 -0.96428966522217 0.00000000000000\r\nvt 0.22636914253235 -0.69108712673187 0.00000000000000\r\nvt 0.18361742794514 -0.60698568820953 0.00000000000000\r\nvt 0.15845535695553 -0.63835704326630 0.00000000000000\r\nvt 0.11279834806919 -0.31875616312027 0.00000000000000\r\nvt 0.14668984711170 -0.29642510414124 0.00000000000000\r\nvt 0.25584641098976 -0.67014074325562 0.00000000000000\r\nvt 0.20313899219036 -0.57946109771729 0.00000000000000\r\nvt 0.13752593100071 -0.29043757915497 0.00000000000000\r\nvt 0.12800784409046 -0.28495228290558 0.00000000000000\r\nvt 0.56718534231186 -0.99794518947601 0.00000000000000\r\nvt 0.58259356021881 -0.98801338672638 0.00000000000000\r\nvt 0.21778261661530 -0.56341326236725 0.00000000000000\r\nvt 0.10182480514050 -0.30984687805176 0.00000000000000\r\nvt 0.11479850858450 -0.27977067232132 0.00000000000000\r\nvt 0.08793735504150 -0.42242729663849 0.00000000000000\r\nvt 0.09166307747364 -0.29992973804474 0.00000000000000\r\nvt 0.09843736141920 -0.27238398790359 0.00000000000000\r\nvt 0.03565083071589 -0.23923555016518 0.00000000000000\r\nvt 0.03359920158982 -0.24620661139488 0.00000000000000\r\nvt 0.03885761275887 -0.25306904315948 0.00000000000000\r\nvt 0.05027095228434 -0.23901826143265 0.00000000000000\r\nvt 0.04215918481350 -0.23566851019859 0.00000000000000\r\nvt 0.08007917553186 -0.28841000795364 0.00000000000000\r\nvt 0.07946395128965 -0.26028358936310 0.00000000000000\r\nvt 0.57845854759216 -1.00000000000000 0.00000000000000\r\nvt 0.06483913958073 -0.27469289302826 0.00000000000000\r\nvt 0.06202593073249 -0.24773865938187 0.00000000000000\r\nvt 0.04980970919132 -0.26187902688980 0.00000000000000\r\nvt 0.28868865966797 -0.65365087985992 0.00000000000000\r\nvt 0.19500856101513 -0.71123790740967 0.00000000000000\r\nvt 0.12689012289047 -0.66594529151917 0.00000000000000\r\nvt 0.33756572008133 -0.81907033920288 0.00000000000000\r\nvt 0.36193633079529 -0.82021772861481 0.00000000000000\r\nvt 0.09508404880762 -0.68877577781677 0.00000000000000\r\nvt 0.11851247400045 -0.73592782020569 0.00000000000000\r\nvt 0.06919944286346 -0.70587420463562 0.00000000000000\r\nvt 0.08861683309078 -0.74553108215332 0.00000000000000\r\nvt 0.04564516618848 -0.72076189517975 0.00000000000000\r\nvt 0.06196573004127 -0.75467634201050 0.00000000000000\r\nvt 0.18054743111134 -0.97945725917816 0.00000000000000\r\nvt 0.18793037533760 -0.98967611789703 0.00000000000000\r\nvt 0.20052531361580 -0.98912763595581 0.00000000000000\r\nvt 0.02082998305559 -0.73696041107178 0.00000000000000\r\nvt 0.03369546681643 -0.76388943195343 0.00000000000000\r\nvt 0.00289968517609 -0.75201618671417 0.00000000000000\r\nvt 0.00000000000000 -0.76347541809082 0.00000000000000\r\nvt 0.01073165331036 -0.76840949058533 0.00000000000000\r\nvt 0.87992233037949 -0.69426965713501 0.00000000000000\r\nvt 0.72972315549850 -0.62403571605682 0.00000000000000\r\nvt 0.84362185001373 -0.41298300027847 0.00000000000000\r\nvt 0.85227137804031 -0.53982830047607 0.00000000000000\r\nvt 0.71178525686264 -0.20005047321320 0.00000000000000\r\nvt 0.22228421270847 -0.95381557941437 0.00000000000000\r\nvt 0.43407180905342 -0.11675067245960 0.00000000000000\r\nvt 0.56323909759521 -0.93620574474335 0.00000000000000\r\nvt 0.53139853477478 -0.85751342773438 0.00000000000000\r\nvt 0.15651637315750 -0.72534084320068 0.00000000000000\r\n# 292 texture coordinates\r\n\r\no Extrude.1\r\nusemtl default\r\nf 292/1 291/2 290/3\r\nf 278/4 277/5 275/6\r\nf 51/7 52/8 21/9\r\nf 292/1 289/10 20/11\r\nf 279/12 278/4 274/13\r\nf 20/11 289/10 288/14\r\nf 19/15 288/14 287/16\r\nf 272/17 280/18 279/12\r\nf 287/16 286/19 266/20\r\nf 282/21 281/22 271/23\r\nf 281/22 280/18 272/17\r\nf 106/24 287/16 265/25\r\nf 283/26 282/21 270/27\r\nf 287/16 106/24 105/28\r\nf 22/29 287/16 50/30\r\nf 268/31 284/32 283/26\r\nf 266/20 286/19 285/33\r\nf 267/34 285/33 284/32\r\nf 24/35 52/8 46/36\r\nf 52/8 24/35 21/9\r\nf 277/5 276/37 275/6\r\nf 30/38 46/36 45/39\r\nf 265/25 264/40 168/41\r\nf 264/40 263/42 169/43\r\nf 224/44 232/45 231/46\r\nf 45/39 48/47 29/48\r\nf 209/49 263/42 262/50\r\nf 223/51 233/52 232/45\r\nf 263/42 209/49 170/53\r\nf 211/54 260/55 258/56\r\nf 48/47 47/57 32/58\r\nf 234/59 233/52 223/51\r\nf 210/60 262/50 261/61\r\nf 261/61 260/55 211/54\r\nf 260/55 259/62 258/56\r\nf 257/63 256/64 212/65\r\nf 235/66 234/59 222/67\r\nf 256/64 255/68 212/65\r\nf 236/69 235/66 221/70\r\nf 255/68 254/71 241/72\r\nf 218/73 238/74 237/75\r\nf 237/75 236/69 220/76\r\nf 255/68 240/77 213/78\r\nf 239/79 217/80 216/81\r\nf 242/82 254/71 253/83\r\nf 217/80 239/79 238/74\r\nf 254/71 242/82 241/72\r\nf 213/78 240/77 239/79\r\nf 243/84 253/83 252/85\r\nf 252/85 251/86 245/87\r\nf 249/88 248/89 247/90\r\nf 251/86 250/91 246/92\r\nf 246/92 250/91 249/88\r\nf 225/93 231/46 230/94\r\nf 194/95 193/96 187/97\r\nf 32/58 47/57 42/98\r\nf 31/99 42/98 41/100\r\nf 226/101 230/94 229/102\r\nf 195/103 194/95 186/104\r\nf 229/102 228/105 227/106\r\nf 196/107 195/103 185/108\r\nf 216/81 215/109 214/110\r\nf 197/111 196/107 184/112\r\nf 209/49 208/113 171/114\r\nf 182/115 198/116 197/111\r\nf 208/113 207/117 206/118\r\nf 171/114 208/113 206/118\r\nf 181/119 199/120 198/116\r\nf 172/121 171/114 205/122\r\nf 180/123 200/124 199/120\r\nf 205/122 204/125 175/126\r\nf 179/127 201/128 200/124\r\nf 176/129 204/125 203/130\r\nf 177/131 203/130 202/132\r\nf 178/133 202/132 201/128\r\nf 204/125 176/129 175/126\r\nf 77/134 50/30 105/28\r\nf 193/96 192/135 188/136\r\nf 192/135 191/137 189/138\r\nf 121/139 122/140 123/141\r\nf 191/137 190/142 189/138\r\nf 137/143 138/144 121/139\r\nf 174/145 173/146 172/121\r\nf 124/147 140/148 137/143\r\nf 165/149 166/150 120/151\r\nf 166/150 167/152 118/153\r\nf 35/154 41/100 44/155\r\nf 166/150 119/156 120/151\r\nf 167/152 161/157 117/158\r\nf 125/159 139/160 140/148\r\nf 126/161 142/162 139/160\r\nf 116/163 117/158 161/157\r\nf 127/164 128/165 143/166\r\nf 36/167 44/155 43/168\r\nf 126/161 127/164 141/169\r\nf 162/170 163/171 115/172\r\nf 129/173 130/174 113/175\r\nf 128/165 113/175 130/174\r\nf 163/171 164/176 114/177\r\nf 159/178 158/179 157/180\r\nf 164/176 152/181 129/173\r\nf 132/182 152/181 151/183\r\nf 156/184 160/185 159/178\r\nf 152/181 132/182 129/173\r\nf 145/186 160/185 156/184\r\nf 151/183 150/187 134/188\r\nf 146/189 145/186 155/190\r\nf 150/187 149/191 135/192\r\nf 147/193 146/189 154/194\r\nf 150/187 136/195 133/196\r\nf 153/197 149/191 148/198\r\nf 149/191 153/197 135/192\r\nf 143/166 144/199 141/169\r\nf 79/200 78/201 108/202\r\nf 108/202 78/201 77/134\r\nf 43/168 38/203 34/204\r\nf 6/205 14/206 13/207\r\nf 80/208 79/200 107/209\r\nf 5/210 15/211 14/206\r\nf 73/212 80/208 110/213\r\nf 16/214 15/211 5/210\r\nf 109/215 76/216 74/217\r\nf 18/218 16/214 4/219\r\nf 109/215 112/220 86/221\r\nf 2/222 17/223 18/218\r\nf 112/220 111/224 85/225\r\nf 1/226 20/11 17/223\r\nf 111/224 98/227 88/228\r\nf 25/229 26/230 23/231\r\nf 98/227 97/232 100/233\r\nf 13/207 12/234 8/235\r\nf 88/228 98/227 100/233\r\nf 55/236 66/237 65/238\r\nf 87/239 88/228 99/240\r\nf 56/241 67/242 66/237\r\nf 87/239 102/243 101/244\r\nf 12/234 11/245 9/246\r\nf 50/30 77/134 68/247\r\nf 82/248 101/244 104/249\r\nf 76/216 75/250 74/217\r\nf 81/251 104/249 103/252\r\nf 95/253 96/254 93/255\r\nf 93/255 89/256 90/257\r\nf 84/258 103/252 91/259\r\nf 11/245 10/260 9/246\r\nf 83/261 91/259 92/262\r\nf 94/263 92/262 89/256\r\nf 49/264 68/247 67/242\r\nf 54/265 65/238 72/266\r\nf 23/231 27/267 28/268\r\nf 72/266 71/269 60/270\r\nf 24/35 30/38 27/267\r\nf 71/269 70/271 59/272\r\nf 70/271 69/273 58/274\r\nf 37/275 40/276 39/277\r\nf 69/273 61/278 57/279\r\nf 57/279 61/278 62/280\r\nf 62/280 63/281 64/282\r\nf 38/203 37/275 39/277\r\nf 292/1 290/3 289/10\r\nf 278/4 275/6 274/13\r\nf 51/7 21/9 22/29\r\nf 292/1 20/11 1/226\r\nf 279/12 274/13 273/283\r\nf 20/11 288/14 19/15\r\nf 19/15 287/16 22/29\r\nf 272/17 279/12 273/283\r\nf 287/16 266/20 265/25\r\nf 282/21 271/23 270/27\r\nf 281/22 272/17 271/23\r\nf 106/24 265/25 165/149\r\nf 283/26 270/27 269/284\r\nf 287/16 105/28 50/30\r\nf 22/29 50/30 51/7\r\nf 268/31 283/26 269/284\r\nf 266/20 285/33 267/34\r\nf 267/34 284/32 268/31\r\nf 24/35 46/36 30/38\r\nf 30/38 45/39 29/48\r\nf 265/25 168/41 165/149\r\nf 264/40 169/43 168/41\r\nf 224/44 231/46 225/93\r\nf 209/49 262/50 210/60\r\nf 223/51 232/45 224/44\r\nf 263/42 170/53 169/43\r\nf 211/54 258/56 257/63\r\nf 48/47 32/58 29/48\r\nf 234/59 223/51 222/67\r\nf 210/60 261/61 211/54\r\nf 257/63 212/65 211/54\r\nf 235/66 222/67 221/70\r\nf 236/69 221/70 220/76\r\nf 255/68 241/72 240/77\r\nf 218/73 237/75 219/285\r\nf 237/75 220/76 219/285\r\nf 255/68 213/78 212/65\r\nf 239/79 216/81 214/110\r\nf 242/82 253/83 243/84\r\nf 217/80 238/74 218/73\r\nf 213/78 239/79 214/110\r\nf 243/84 252/85 244/286\r\nf 252/85 245/87 244/286\r\nf 251/86 246/92 245/87\r\nf 246/92 249/88 247/90\r\nf 225/93 230/94 226/101\r\nf 194/95 187/97 186/104\r\nf 32/58 42/98 31/99\r\nf 31/99 41/100 35/154\r\nf 226/101 229/102 227/106\r\nf 195/103 186/104 185/108\r\nf 196/107 185/108 184/112\r\nf 197/111 184/112 183/287\r\nf 209/49 171/114 170/53\r\nf 182/115 197/111 183/287\r\nf 171/114 206/118 205/122\r\nf 181/119 198/116 182/115\r\nf 172/121 205/122 174/145\r\nf 180/123 199/120 181/119\r\nf 205/122 175/126 174/145\r\nf 179/127 200/124 180/123\r\nf 176/129 203/130 177/131\r\nf 177/131 202/132 178/133\r\nf 178/133 201/128 179/127\r\nf 77/134 105/28 108/202\r\nf 193/96 188/136 187/97\r\nf 192/135 189/138 188/136\r\nf 137/143 121/139 123/141\r\nf 124/147 137/143 123/141\r\nf 165/149 120/151 106/24\r\nf 166/150 118/153 119/156\r\nf 35/154 44/155 36/167\r\nf 167/152 117/158 118/153\r\nf 125/159 140/148 124/147\r\nf 126/161 139/160 125/159\r\nf 116/163 161/157 162/170\r\nf 127/164 143/166 141/169\r\nf 36/167 43/168 33/288\r\nf 126/161 141/169 142/162\r\nf 162/170 115/172 116/163\r\nf 129/173 113/175 114/177\r\nf 128/165 130/174 143/166\r\nf 163/171 114/177 115/172\r\nf 164/176 129/173 114/177\r\nf 132/182 151/183 131/289\r\nf 156/184 159/178 157/180\r\nf 145/186 156/184 155/190\r\nf 151/183 134/188 131/289\r\nf 146/189 155/190 154/194\r\nf 150/187 135/192 136/195\r\nf 147/193 154/194 153/197\r\nf 150/187 133/196 134/188\r\nf 153/197 148/198 147/193\r\nf 79/200 108/202 107/209\r\nf 43/168 34/204 33/288\r\nf 6/205 13/207 7/290\r\nf 80/208 107/209 110/213\r\nf 5/210 14/206 6/205\r\nf 73/212 110/213 109/215\r\nf 16/214 5/210 4/219\r\nf 109/215 74/217 73/212\r\nf 18/218 4/219 3/291\r\nf 109/215 86/221 76/216\r\nf 2/222 18/218 3/291\r\nf 112/220 85/225 86/221\r\nf 1/226 17/223 2/222\r\nf 111/224 88/228 85/225\r\nf 13/207 8/235 7/290\r\nf 88/228 100/233 99/240\r\nf 55/236 65/238 54/265\r\nf 87/239 99/240 102/243\r\nf 56/241 66/237 55/236\r\nf 87/239 101/244 82/248\r\nf 12/234 9/246 8/235\r\nf 50/30 68/247 49/264\r\nf 82/248 104/249 81/251\r\nf 81/251 103/252 84/258\r\nf 93/255 90/257 95/253\r\nf 84/258 91/259 83/261\r\nf 83/261 92/262 94/263\r\nf 94/263 89/256 93/255\r\nf 49/264 67/242 56/241\r\nf 54/265 72/266 53/292\r\nf 23/231 28/268 25/229\r\nf 72/266 60/270 53/292\r\nf 24/35 27/267 23/231\r\nf 71/269 59/272 60/270\r\nf 70/271 58/274 59/272\r\nf 69/273 57/279 58/274\r\nf 57/279 62/280 64/282\r\nf 38/203 39/277 34/204\r\n\r\n"
  },
  {
    "path": "static/models/stump.obj",
    "content": "# Blender v2.76 (sub 0) OBJ File: 'stump.blend'\n# www.blender.org\nmtllib stump-test.mtl\no Mesh\nv -0.107668 0.351923 -0.107757\nv 0.000101 0.347774 -0.144886\nv 0.000257 0.214742 -0.144888\nv -0.109719 0.223690 -0.106736\nv 0.107762 0.351923 -0.110072\nv 0.107801 0.223690 -0.109998\nv 0.136634 0.137433 -0.141185\nv 0.055548 0.154955 -0.160610\nv -0.000675 0.130719 -0.165272\nv -0.056783 0.161377 -0.158195\nv -0.141379 0.142411 -0.136948\nv 0.109789 0.351923 0.104730\nv 0.002879 0.347774 0.141702\nv 0.003374 0.214742 0.142534\nv 0.109914 0.223690 0.105013\nv -0.105499 0.351923 0.107392\nv -0.107042 0.223690 0.109664\nv -0.131795 0.143986 0.136709\nv -0.049364 0.141648 0.145143\nv 0.005628 0.125335 0.155586\nv 0.057414 0.157698 0.152116\nv 0.136898 0.141991 0.135082\nv 0.144538 0.347774 -0.002964\nv 0.144541 0.214742 -0.002667\nv 0.149447 0.160467 0.053329\nv 0.154151 0.127795 -0.000430\nv 0.152122 0.150888 -0.056671\nv -0.144561 0.347774 0.000964\nv -0.152922 0.214742 0.005050\nv -0.175700 0.157093 -0.045602\nv -0.200043 0.131801 0.021821\nv -0.171581 0.161085 0.069900\nv 0.136215 0.079403 -0.312582\nv 0.137637 0.043276 -0.317976\nv 0.099349 0.044578 -0.324281\nv 0.097306 0.080162 -0.319005\nv 0.136830 0.007921 -0.308153\nv 0.097347 0.009692 -0.314579\nv 0.068812 0.019463 -0.316710\nv 0.060783 0.044195 -0.326107\nv 0.068783 0.069810 -0.319864\nv 0.474721 0.036402 0.170602\nv 0.479890 0.028470 0.175671\nv 0.491367 0.029061 0.169278\nv 0.483817 0.038952 0.165113\nv 0.474259 0.018476 0.172450\nv 0.483246 0.016808 0.167397\nv 0.480155 0.017520 0.157206\nv 0.487174 0.027290 0.156839\nv 0.480617 0.035446 0.155357\nv -0.125825 0.067754 0.155076\nv -0.066405 0.065528 0.148309\nv -0.134483 0.004155 0.147637\nv -0.066134 0.009400 0.150591\nv 0.005160 0.001275 0.156483\nv -0.011483 0.063811 0.159083\nv -0.361263 0.055539 -0.069580\nv -0.370353 0.038320 -0.073999\nv -0.366382 0.034187 -0.042829\nv -0.358653 0.057358 -0.046580\nv -0.361136 0.018785 -0.067782\nv -0.358383 0.010862 -0.044225\nv -0.351464 0.005698 -0.007857\nv -0.359411 0.029772 -0.014504\nv -0.351940 0.053232 -0.010166\nv -0.078790 1.285750 0.080271\nv 0.002381 1.284592 0.105636\nv 0.001382 1.283103 -0.001731\nv -0.105985 1.284592 -0.000731\nv 0.081653 1.285750 0.077060\nv 0.108748 1.284592 -0.002730\nv 0.080172 1.285750 -0.082002\nv 0.000382 1.284592 -0.109097\nv -0.078890 1.285750 -0.080521\nv 0.114062 0.000808 0.116066\nv 0.074710 0.002270 0.129907\nv 0.074205 0.003456 0.075607\nv 0.127511 0.002270 0.076471\nv 0.003474 0.002118 0.130420\nv 0.002878 0.002849 0.075365\nv 0.002204 0.001938 0.002923\nv 0.073535 0.002849 0.003619\nv 0.126847 0.002118 0.005163\nv -0.106984 0.688060 -0.108098\nv 0.000049 0.688060 -0.144886\nv 0.000049 0.522066 -0.144886\nv -0.106984 0.522066 -0.108098\nv 0.107749 0.688060 -0.110097\nv 0.107749 0.522066 -0.110097\nv 0.144537 0.688060 -0.003063\nv 0.144537 0.522066 -0.003063\nv 0.109747 0.688060 0.104636\nv 0.109747 0.522066 0.104636\nv 0.002714 0.688060 0.141425\nv 0.002714 0.522066 0.141425\nv -0.104985 0.688060 0.106635\nv -0.104985 0.522066 0.106635\nv -0.141774 0.688060 -0.000398\nv -0.141774 0.522066 -0.000398\nv -0.106984 0.994652 -0.108098\nv 0.000049 0.994652 -0.144886\nv 0.000049 0.843228 -0.144886\nv -0.106984 0.843228 -0.108098\nv 0.107749 0.994652 -0.110097\nv 0.107749 0.843228 -0.110097\nv 0.144537 0.994652 -0.003063\nv 0.144537 0.843228 -0.003063\nv 0.109747 0.994652 0.104636\nv 0.109747 0.843228 0.104636\nv 0.002714 0.994652 0.141425\nv 0.002714 0.843228 0.141425\nv -0.104985 0.994652 0.106635\nv -0.104985 0.843228 0.106635\nv -0.141774 0.994652 -0.000398\nv -0.141774 0.843228 -0.000398\nv -0.106984 1.266596 -0.108098\nv 0.000049 1.266596 -0.144886\nv 0.000049 1.141063 -0.144886\nv -0.106984 1.141063 -0.108098\nv 0.107749 1.266596 -0.110097\nv 0.107749 1.141063 -0.110097\nv 0.144537 1.266596 -0.003063\nv 0.144537 1.141063 -0.003063\nv 0.109747 1.266596 0.104636\nv 0.109747 1.141063 0.104636\nv 0.002714 1.266596 0.141425\nv 0.002714 1.141063 0.141425\nv -0.104985 1.266596 0.106635\nv -0.104985 1.141063 0.106635\nv -0.141774 1.266596 -0.000398\nv -0.141774 1.141063 -0.000398\nv 0.000049 1.289059 -0.144886\nv -0.106984 1.289059 -0.108098\nv 0.107749 1.289059 -0.110097\nv 0.144537 1.289059 -0.003063\nv 0.109747 1.289059 0.104636\nv 0.002714 1.289059 0.141425\nv -0.104985 1.289059 0.106635\nv -0.141774 1.289059 -0.000398\nv 0.128008 -0.003717 0.129864\nv 0.074879 -0.003717 0.148007\nv 0.140650 -0.001646 0.143780\nv 0.075340 -0.010308 0.158781\nv 0.003896 -0.003717 0.148972\nv 0.111989 0.000808 -0.106620\nv 0.125676 -0.003717 -0.120658\nv 0.143947 -0.003717 -0.066396\nv 0.126179 0.002270 -0.066684\nv 0.138672 -0.002905 -0.136859\nv 0.152022 -0.011087 -0.067433\nv 0.152968 -0.002516 0.006061\nv 0.144618 -0.003717 0.005677\nv -0.112223 0.000808 -0.104020\nv -0.128278 -0.003717 -0.117140\nv -0.070942 -0.003717 -0.136971\nv -0.070777 0.002270 -0.119282\nv -0.143284 -0.000335 -0.132857\nv -0.074540 -0.004177 -0.151683\nv -0.000086 0.000073 -0.152973\nv 0.000895 -0.003717 -0.137640\nv 0.001058 0.002118 -0.120178\nv -0.109257 0.000808 0.119471\nv -0.123936 -0.003717 0.135193\nv -0.153422 -0.003717 0.085375\nv -0.126108 0.002270 0.080291\nv -0.173527 -0.005531 0.099825\nv -0.196234 -0.000869 0.028207\nv -0.161954 -0.003717 0.015759\nv -0.128740 0.002118 0.009349\nv -0.254519 0.008260 -0.557969\nv -0.262327 0.023454 -0.563748\nv -0.250949 0.024216 -0.581260\nv -0.246265 0.003742 -0.571309\nv -0.254519 0.037463 -0.557969\nv -0.246265 0.043167 -0.571309\nv -0.230724 0.037463 -0.569170\nv -0.230204 0.023454 -0.578869\nv -0.230724 0.008260 -0.569170\nv 0.280622 0.152459 0.125233\nv 0.294723 0.162988 0.093084\nv 0.239857 0.161826 0.086945\nv 0.227248 0.148207 0.124915\nv 0.303917 0.149706 0.061388\nv 0.253803 0.148053 0.050894\nv 0.206655 0.130570 0.042850\nv 0.190889 0.144880 0.083197\nv 0.181805 0.128381 0.126670\nv -0.068950 0.003456 0.076940\nv -0.068110 0.002270 0.131541\nv -0.069621 0.002849 0.004952\nv -0.066937 -0.003717 0.150547\nv 0.072213 -0.003717 -0.138304\nv 0.073587 -0.008446 -0.153693\nv 0.072378 0.002270 -0.120615\nv 0.516658 0.000600 -0.159741\nv 0.520929 0.010119 -0.164918\nv 0.530976 0.009807 -0.155152\nv 0.524230 -0.003060 -0.152380\nv 0.517259 0.020124 -0.160888\nv 0.525041 0.023298 -0.153928\nv 0.522750 0.020124 -0.143460\nv 0.528342 0.010119 -0.141391\nv 0.522150 0.000600 -0.142314\nv -0.288215 0.060304 0.319034\nv -0.295491 0.077194 0.332379\nv -0.314163 0.077280 0.321458\nv -0.307520 0.060392 0.307851\nv -0.295996 0.099842 0.333004\nv -0.315301 0.099930 0.321821\nv -0.339377 0.099656 0.314246\nv -0.337624 0.077003 0.314240\nv -0.331000 0.060118 0.300783\nv 0.072872 0.003456 -0.067548\nv 0.001546 0.002849 -0.067791\nv 0.145280 -0.003717 0.076759\nv 0.154751 -0.006476 0.079656\nv -0.156094 -0.003717 -0.058988\nv -0.175477 -0.009631 -0.056602\nv -0.127776 0.002270 -0.063166\nv -0.070283 0.003456 -0.066215\nv -0.066807 0.047096 -0.262908\nv -0.055062 0.089334 -0.267738\nv -0.026656 0.077830 -0.208627\nv -0.041318 0.027871 -0.204518\nv -0.066814 0.131572 -0.263588\nv -0.041343 0.127790 -0.207238\nv -0.001498 0.066185 -0.175639\nv -0.102059 0.145651 -0.250229\nv -0.085370 0.144444 -0.199444\nv -0.137302 0.131572 -0.236644\nv -0.129389 0.127790 -0.190743\nv -0.149047 0.089334 -0.231814\nv -0.144050 0.077830 -0.186633\nv -0.137296 0.047096 -0.235964\nv -0.129363 0.027871 -0.188023\nv -0.155814 0.066811 -0.149458\nv -0.102050 0.033016 -0.249323\nv -0.085336 0.011217 -0.195817\nv -0.118782 0.065282 -0.342216\nv -0.110353 0.096315 -0.346183\nv -0.084725 0.099239 -0.310356\nv -0.094395 0.063637 -0.305804\nv -0.118782 0.127347 -0.342216\nv -0.094395 0.134841 -0.305804\nv -0.144067 0.137692 -0.330313\nv -0.123403 0.146709 -0.292149\nv -0.169352 0.127347 -0.318411\nv -0.152412 0.134841 -0.278494\nv -0.177781 0.096315 -0.314443\nv -0.162082 0.099239 -0.273942\nv -0.169352 0.065282 -0.318411\nv -0.152412 0.063637 -0.278494\nv -0.144067 0.054938 -0.330313\nv -0.123403 0.051769 -0.292149\nv -0.149784 0.010256 -0.422286\nv -0.142102 0.038540 -0.425902\nv -0.126653 0.069328 -0.384926\nv -0.134668 0.039818 -0.381153\nv -0.149784 0.066825 -0.422286\nv -0.134668 0.098837 -0.381153\nv -0.172830 0.076254 -0.411437\nv -0.158712 0.108674 -0.369835\nv -0.195877 0.066825 -0.400589\nv -0.182757 0.098837 -0.358517\nv -0.203559 0.038540 -0.396973\nv -0.190771 0.069328 -0.354744\nv -0.195877 0.010256 -0.400589\nv -0.182757 0.039818 -0.358517\nv -0.172830 0.000827 -0.411437\nv -0.158712 0.029982 -0.369835\nv -0.242105 0.094666 -0.023947\nv -0.242193 0.050083 -0.007523\nv -0.220098 0.058356 0.007230\nv -0.217225 0.110070 -0.012394\nv -0.241764 0.005847 -0.019817\nv -0.216212 0.006642 -0.007789\nv -0.214323 0.066014 0.030928\nv -0.241011 -0.008689 -0.062187\nv -0.206163 -0.010596 -0.058928\nv -0.241811 -0.000410 -0.114820\nv -0.196322 0.006642 -0.111631\nv -0.231676 0.048580 -0.130108\nv -0.193062 0.058356 -0.131346\nv -0.241823 0.097566 -0.119364\nv -0.196561 0.110070 -0.116417\nv -0.241305 0.110240 -0.067727\nv -0.206997 0.127308 -0.065188\nv -0.291674 0.060946 -0.024358\nv -0.282187 0.031388 -0.013695\nv -0.264670 0.039609 -0.017152\nv -0.266889 0.075820 -0.030828\nv -0.291108 0.001027 -0.021261\nv -0.266616 0.004783 -0.027361\nv -0.303534 -0.002766 -0.059310\nv -0.272880 -0.005987 -0.062602\nv -0.315211 0.004218 -0.100658\nv -0.278860 -0.006829 -0.106410\nv 0.476358 0.000409 -0.150643\nv 0.479955 -0.004121 -0.138185\nv 0.455320 0.000566 -0.130335\nv 0.451645 0.005505 -0.144103\nv 0.484022 0.000409 -0.126320\nv 0.460062 0.005505 -0.117392\nv 0.433810 0.012271 -0.106058\nv 0.428031 0.006363 -0.121542\nv 0.423722 0.012271 -0.138075\nv -0.315241 0.070788 -0.103779\nv -0.303898 0.074466 -0.063127\nv -0.273116 0.090742 -0.067239\nv -0.278823 0.088663 -0.110845\nv -0.255138 -0.010707 -0.163060\nv -0.248264 0.021076 -0.161888\nv -0.247540 0.033595 -0.143555\nv -0.255785 -0.003675 -0.142102\nv -0.254393 0.052814 -0.165728\nv -0.255383 0.069566 -0.145335\nv -0.273837 0.058166 -0.173472\nv -0.279480 0.073868 -0.146103\nv -0.299744 0.045252 -0.185703\nv -0.303695 0.056735 -0.145924\nv -0.310020 0.020272 -0.180359\nv -0.311891 0.028402 -0.144865\nv -0.300387 -0.005866 -0.183581\nv -0.303999 0.001367 -0.143480\nv -0.325485 0.036520 -0.110443\nv -0.274708 -0.016195 -0.170349\nv -0.279951 -0.011871 -0.142318\nv -0.242540 0.000186 -0.189697\nv -0.236861 0.020780 -0.189015\nv -0.241456 0.018699 -0.175308\nv -0.246830 -0.010313 -0.178241\nv -0.241897 0.040486 -0.191428\nv -0.245936 0.047983 -0.180706\nv -0.248052 0.045756 -0.205225\nv -0.260642 0.053364 -0.193413\nv -0.263641 0.042239 -0.224454\nv -0.279652 0.048506 -0.209564\nv -0.331474 0.039444 -0.287592\nv -0.336803 0.021707 -0.290001\nv -0.348680 0.018074 -0.273269\nv -0.344088 0.035447 -0.269793\nv -0.332053 0.004231 -0.286076\nv -0.344620 0.000923 -0.268330\nv -0.368051 0.000364 -0.254963\nv -0.367934 0.020149 -0.263763\nv -0.367173 0.037456 -0.255807\nv -0.264335 0.000098 -0.222619\nv -0.248866 -0.003685 -0.203073\nv -0.261687 -0.014880 -0.190527\nv -0.280540 -0.009413 -0.207114\nv 0.510557 0.011056 -0.131476\nv 0.508916 -0.002123 -0.134624\nv 0.509727 0.024236 -0.136172\nv 0.485431 0.027588 -0.128100\nv 0.486004 0.013998 -0.123156\nv 0.506156 0.028629 -0.148193\nv 0.502314 0.024236 -0.159699\nv 0.477767 0.027588 -0.152423\nv 0.481834 0.032117 -0.140559\nv 0.500673 0.011056 -0.162846\nv 0.501503 -0.002123 -0.158151\nv 0.475785 0.013998 -0.155587\nv 0.505074 -0.006516 -0.146129\nv -0.335081 0.040700 -0.219954\nv -0.336910 0.017230 -0.209640\nv -0.319828 0.017293 -0.202336\nv -0.313938 0.038932 -0.208652\nv -0.335736 -0.005906 -0.218047\nv -0.314541 -0.003305 -0.206914\nv -0.314673 -0.008451 -0.238786\nv -0.297900 -0.008322 -0.222950\nv -0.295958 -0.001333 -0.262085\nv -0.281046 0.000510 -0.239572\nv -0.286745 0.023372 -0.266241\nv -0.275120 0.021459 -0.245908\nv -0.295178 0.045570 -0.264123\nv -0.280372 0.041365 -0.241352\nv -0.258618 0.021709 -0.228602\nv -0.313829 0.046366 -0.241108\nv -0.297049 0.047073 -0.225295\nv -0.402088 0.036711 -0.225626\nv -0.407486 0.020346 -0.221314\nv -0.393117 0.018600 -0.204003\nv -0.387512 0.037234 -0.208879\nv -0.403100 0.003844 -0.225341\nv -0.388030 -0.000516 -0.207348\nv -0.375552 0.003592 -0.199975\nv -0.376616 0.018517 -0.193615\nv -0.375148 0.032987 -0.201167\nv -0.331765 -0.006644 -0.255374\nv -0.351308 -0.007787 -0.236632\nv -0.317065 -0.003438 -0.276229\nv -0.368412 0.004127 -0.410883\nv -0.373286 0.009242 -0.413751\nv -0.373399 0.008838 -0.423493\nv -0.368529 0.001905 -0.418346\nv -0.368323 0.014597 -0.411436\nv -0.368407 0.016191 -0.419101\nv -0.360848 0.014901 -0.419178\nv -0.363650 0.008854 -0.423697\nv -0.360948 0.003221 -0.418561\nv -0.330990 0.043684 -0.257507\nv -0.316222 0.047894 -0.278439\nv -0.350596 0.042545 -0.238697\nv 0.220820 0.084723 0.033571\nv 0.182445 0.072602 0.019589\nv 0.172455 0.124347 0.031091\nv 0.226702 0.038145 0.052232\nv 0.187711 0.020263 0.039621\nv 0.162104 0.060909 0.003208\nv 0.217618 0.021647 0.095705\nv 0.183166 0.002024 0.088344\nv 0.201852 0.035956 0.136052\nv 0.173536 0.018480 0.134224\nv 0.187687 0.081804 0.145331\nv 0.163545 0.070226 0.145726\nv 0.158280 0.122565 0.125694\nv 0.148959 0.064761 0.152688\nv 0.162824 0.140803 0.076971\nv -0.240068 0.055283 0.380217\nv -0.230174 0.036266 0.376657\nv -0.235914 0.065036 0.342035\nv -0.245612 0.085434 0.350502\nv -0.231668 0.014930 0.371744\nv -0.236276 0.040719 0.338030\nv -0.247190 0.065468 0.312815\nv -0.247871 0.091330 0.315339\nv -0.256461 0.111895 0.328387\nv -0.247793 0.006862 0.368751\nv -0.250306 0.029753 0.343305\nv -0.266496 0.011178 0.368358\nv -0.267201 0.032510 0.352408\nv -0.271656 0.053262 0.345623\nv -0.258000 0.052241 0.326829\nv -0.275722 0.026993 0.371245\nv -0.276159 0.049359 0.359886\nv -0.273562 0.045126 0.375485\nv -0.275055 0.070127 0.362900\nv -0.279456 0.092320 0.358723\nv -0.279511 0.070142 0.357435\nv -0.258104 0.056396 0.379151\nv -0.261766 0.084641 0.358615\nv -0.269381 0.109231 0.345945\nv -0.259674 0.023628 0.457260\nv -0.254813 0.011977 0.457113\nv -0.240522 0.020026 0.419634\nv -0.248044 0.035349 0.420687\nv -0.257098 -0.000229 0.455064\nv -0.242627 0.003515 0.415945\nv -0.267525 -0.003777 0.451960\nv -0.256451 -0.001890 0.411451\nv -0.278742 -0.000003 0.449531\nv -0.271938 0.002475 0.408412\nv -0.283399 0.009755 0.449503\nv -0.279031 0.015271 0.409089\nv -0.280910 0.020068 0.451378\nv -0.276496 0.029256 0.412402\nv -0.270688 0.025509 0.454656\nv -0.263101 0.037187 0.417272\nv -0.356860 0.017850 -0.201113\nv -0.360297 0.038283 -0.206777\nv -0.360859 -0.002689 -0.205115\nv -0.372079 -0.007528 -0.219424\nv -0.388593 -0.001838 -0.237800\nv -0.563199 0.001328 -0.309745\nv -0.567424 0.004799 -0.309362\nv -0.570489 0.005196 -0.317018\nv -0.565728 0.000474 -0.315798\nv -0.562429 0.008881 -0.308646\nv -0.564721 0.010339 -0.314363\nv -0.559061 0.009812 -0.317402\nv -0.563025 0.006014 -0.320798\nv -0.559832 0.002259 -0.318501\nv -0.371387 0.042805 -0.221466\nv -0.387244 0.041984 -0.238181\nv -0.294008 0.011410 -0.327827\nv -0.287550 0.033029 -0.329266\nv -0.286510 0.029939 -0.295715\nv -0.292836 0.007748 -0.296093\nv -0.293350 0.054678 -0.329586\nv -0.292120 0.051994 -0.297932\nv -0.311661 0.059666 -0.328108\nv -0.309944 0.056845 -0.302034\nv -0.330175 0.051366 -0.326091\nv -0.327988 0.048107 -0.305572\nv -0.336581 0.033150 -0.324790\nv -0.334258 0.029395 -0.306094\nv -0.330730 0.014903 -0.324608\nv -0.328591 0.010820 -0.304022\nv -0.312470 0.006512 -0.325947\nv -0.310824 0.002490 -0.299775\nv -0.327945 0.002254 -0.387876\nv -0.325208 0.013566 -0.390361\nv -0.303003 0.024509 -0.361829\nv -0.307767 0.007684 -0.359535\nv -0.327710 0.025027 -0.388999\nv -0.307315 0.041452 -0.360997\nv -0.335539 0.027853 -0.383359\nv -0.320879 0.045487 -0.356476\nv -0.343441 0.023684 -0.377375\nv -0.334581 0.039151 -0.351506\nv -0.346159 0.014163 -0.374979\nv -0.339310 0.024981 -0.349327\nv -0.343639 0.004493 -0.376428\nv -0.334962 0.010694 -0.350274\nv -0.335828 -0.000123 -0.381980\nv -0.321434 0.004003 -0.354680\nv -0.271617 0.041319 0.128646\nv -0.274183 0.087136 0.105208\nv -0.240805 0.074645 0.067388\nv -0.236272 0.023066 0.089940\nv -0.254750 0.132833 0.101089\nv -0.226063 0.126170 0.070198\nv -0.218941 0.147904 0.125474\nv -0.195449 0.143274 0.104952\nv -0.188754 0.132470 0.159045\nv -0.168238 0.126010 0.146287\nv -0.186188 0.086653 0.182483\nv -0.163704 0.074431 0.168840\nv -0.205621 0.040956 0.186602\nv -0.178446 0.022905 0.166030\nv -0.241430 0.025885 0.162216\nv -0.209060 0.005801 0.131276\nv -0.287243 0.082063 0.171647\nv -0.296706 0.119775 0.162623\nv -0.289092 0.106380 0.132988\nv -0.283723 0.064554 0.150626\nv -0.285171 0.157221 0.163468\nv -0.272044 0.147988 0.129323\nv -0.253327 0.169348 0.176908\nv -0.236472 0.161568 0.146734\nv -0.222173 0.156423 0.193075\nv -0.204793 0.147337 0.171245\nv -0.212710 0.118711 0.202099\nv -0.199424 0.105512 0.188884\nv -0.224246 0.081265 0.201254\nv -0.216472 0.063904 0.192549\nv -0.256090 0.069138 0.187813\nv -0.252044 0.050324 0.175138\nv -0.353232 0.053698 0.030965\nv -0.353760 0.030469 0.037688\nv -0.324269 0.031326 0.041426\nv -0.323885 0.057345 0.033224\nv -0.352740 0.006698 0.033129\nv -0.323338 0.004550 0.035631\nv -0.304853 0.010923 0.030018\nv -0.299148 0.031153 0.035976\nv -0.305273 0.050592 0.028161\nv -0.334734 0.000697 -0.051452\nv -0.323320 -0.001602 -0.015558\nv -0.346810 0.011670 -0.080776\nv -0.350846 0.039462 -0.091678\nv -0.346922 0.064462 -0.083303\nv -0.335118 0.067061 -0.054793\nv -0.323967 0.061743 -0.018855\nv -0.289400 0.029871 0.010808\nv -0.297750 0.056382 0.010173\nv -0.297168 0.003330 0.012755\nv -0.321271 -0.003372 0.015053\nv -0.350358 -0.000443 0.016477\nv -0.615244 -0.000200 -0.010372\nv -0.620331 0.008253 -0.008654\nv -0.623655 0.008129 -0.020402\nv -0.617745 -0.004041 -0.019251\nv -0.616288 0.017266 -0.011102\nv -0.619100 0.020900 -0.019696\nv -0.613420 0.018537 -0.026731\nv -0.616514 0.008607 -0.030293\nv -0.612457 -0.000947 -0.026802\nv -0.322003 0.062994 0.011807\nv -0.350969 0.059212 0.013772\nv -0.313442 0.072740 0.247063\nv -0.322153 0.103672 0.244819\nv -0.308200 0.114723 0.206012\nv -0.296831 0.083134 0.207471\nv -0.323616 0.128532 0.263570\nv -0.301355 0.146119 0.212095\nv -0.295182 0.134947 0.272333\nv -0.274789 0.156327 0.224178\nv -0.269272 0.128484 0.286490\nv -0.246714 0.145541 0.234720\nv -0.254208 0.103631 0.273425\nv -0.235345 0.113952 0.236179\nv -0.259551 0.072692 0.269597\nv -0.242190 0.082556 0.230096\nv -0.284425 0.066955 0.254185\nv -0.268756 0.072348 0.218013\nv -0.499435 -0.010364 0.338635\nv -0.496695 -0.003966 0.345400\nv -0.508832 0.000335 0.345397\nv -0.509635 -0.010336 0.338243\nv -0.494233 0.004552 0.341386\nv -0.502229 0.010899 0.342159\nv -0.507633 0.011658 0.333834\nv -0.515168 0.004529 0.335002\nv -0.513403 -0.004885 0.330783\nv -0.308830 0.117650 0.305163\nv -0.333993 0.117384 0.297161\nv -0.287626 0.117555 0.317133\nv -0.282376 0.011723 0.475378\nv -0.284447 0.004155 0.475714\nv -0.275485 0.004326 0.480332\nv -0.276146 0.014657 0.479215\nv -0.280861 -0.001906 0.474084\nv -0.273842 -0.004720 0.477237\nv -0.266777 -0.001534 0.477736\nv -0.265542 0.005781 0.480738\nv -0.268437 0.013585 0.479153\nv -0.297477 0.059960 0.284779\nv -0.276273 0.059865 0.296748\nv -0.321770 0.059693 0.277516\nv -0.352483 0.069385 0.260691\nv -0.356374 0.094502 0.261201\nv -0.337149 0.099071 0.260708\nv -0.334281 0.072688 0.259948\nv -0.367278 0.114018 0.273306\nv -0.346364 0.119685 0.274143\nv -0.379477 0.110693 0.292135\nv -0.357258 0.116378 0.294771\nv -0.387137 0.093672 0.307092\nv -0.364444 0.098647 0.311043\nv -0.384419 0.072097 0.307583\nv -0.362535 0.075993 0.311410\nv -0.374690 0.056124 0.296478\nv -0.354279 0.059109 0.299101\nv -0.361316 0.055906 0.276649\nv -0.342426 0.058687 0.277347\nv -0.256176 0.100143 0.296141\nv -0.264397 0.120764 0.310716\nv -0.255148 0.073766 0.294110\nv -0.264885 0.059785 0.311037\nv -0.277461 0.060227 0.333061\nv -0.284948 0.077118 0.346319\nv -0.285242 0.099765 0.347032\nv -0.276239 0.117476 0.331422\nv -0.402697 0.049273 0.242594\nv -0.411900 0.066992 0.245008\nv -0.383018 0.083546 0.253820\nv -0.376357 0.062230 0.252914\nv -0.420260 0.080184 0.257567\nv -0.393624 0.099766 0.265615\nv -0.420993 0.076909 0.274488\nv -0.401507 0.096392 0.283394\nv -0.416336 0.064149 0.286813\nv -0.404091 0.081498 0.297274\nv -0.408526 0.048883 0.285588\nv -0.398800 0.063161 0.297376\nv -0.401560 0.038145 0.274217\nv -0.389565 0.049920 0.286588\nv -0.399433 0.038966 0.256108\nv -0.380312 0.050315 0.267802\nv -0.452884 0.008712 0.240077\nv -0.460655 0.025765 0.247359\nv -0.437845 0.045628 0.241207\nv -0.428294 0.028568 0.235709\nv -0.460246 0.038980 0.262022\nv -0.442046 0.058522 0.255659\nv -0.448813 0.036666 0.275591\nv -0.435584 0.055681 0.271357\nv -0.435121 0.025064 0.282425\nv -0.424903 0.043648 0.280933\nv -0.427935 0.010414 0.276885\nv -0.416443 0.028965 0.277018\nv -0.428928 -0.000399 0.263963\nv -0.413334 0.018448 0.264150\nv -0.439777 -0.000488 0.248653\nv -0.418704 0.018912 0.246868\nv -0.393307 0.011050 -0.001450\nv -0.391118 0.029331 -0.005860\nv -0.369920 0.027789 -0.005358\nv -0.370685 0.008147 0.000041\nv -0.390651 0.047551 -0.000444\nv -0.370829 0.047649 -0.000595\nv -0.393086 0.052090 0.013714\nv -0.373457 0.054541 0.014131\nv -0.396259 0.045190 0.027911\nv -0.375989 0.048275 0.029275\nv -0.398003 0.029087 0.033441\nv -0.376613 0.028661 0.035291\nv -0.398025 0.013045 0.029145\nv -0.375564 0.008829 0.031144\nv -0.396035 0.006327 0.013867\nv -0.373077 0.001909 0.015801\nv -0.449802 0.028638 -0.005528\nv -0.441104 0.043937 -0.008430\nv -0.416214 0.035492 -0.006476\nv -0.422244 0.019336 -0.003112\nv -0.434155 0.059103 -0.001858\nv -0.411905 0.051507 -0.000385\nv -0.435161 0.062433 0.012012\nv -0.413477 0.054970 0.013352\nv -0.440888 0.056381 0.025134\nv -0.418139 0.048554 0.026630\nv -0.448099 0.043405 0.029462\nv -0.423101 0.034931 0.031344\nv -0.453561 0.030562 0.024318\nv -0.426340 0.021448 0.026603\nv -0.454042 0.024910 0.009021\nv -0.425837 0.015453 0.011516\nv -0.480800 0.037883 -0.012970\nv -0.477561 0.058700 -0.017657\nv -0.461685 0.052334 -0.012945\nv -0.468285 0.034589 -0.009285\nv -0.476331 0.079338 -0.011118\nv -0.456961 0.069925 -0.006304\nv -0.479041 0.083726 0.005032\nv -0.458670 0.073729 0.008662\nv -0.483020 0.075421 0.021064\nv -0.463762 0.066682 0.023126\nv -0.485597 0.057981 0.027245\nv -0.469189 0.051719 0.028258\nv -0.486164 0.040720 0.022201\nv -0.472740 0.036910 0.023090\nv -0.484116 0.032955 0.004556\nv -0.472204 0.030324 0.006651\nv -0.496118 -0.005903 0.293452\nv -0.496392 0.007661 0.294968\nv -0.480133 0.013715 0.268403\nv -0.476204 -0.001695 0.263520\nv -0.490065 0.018259 0.300459\nv -0.476125 0.025755 0.278787\nv -0.479472 0.016558 0.307218\nv -0.464210 0.023823 0.288776\nv -0.470737 0.007443 0.311827\nv -0.452320 0.013467 0.294079\nv -0.469982 -0.004204 0.310868\nv -0.448385 0.000236 0.290408\nv -0.475829 -0.012884 0.305933\nv -0.452386 -0.009626 0.281236\nv -0.486902 -0.013101 0.298617\nv -0.464307 -0.009872 0.270035\nv 0.214935 -0.011545 -0.036822\nv 0.214193 0.023545 -0.025489\nv 0.176791 0.039978 -0.011851\nv 0.176669 -0.005375 -0.024981\nv 0.212547 0.060925 -0.039984\nv 0.176626 0.086859 -0.029612\nv 0.210794 0.073386 -0.079251\nv 0.176187 0.102486 -0.076721\nv 0.208829 0.063248 -0.125249\nv 0.175763 0.088386 -0.122286\nv 0.200029 0.033257 -0.135513\nv 0.175641 0.046087 -0.135416\nv 0.210610 -0.001686 -0.122659\nv 0.175806 0.002260 -0.117655\nv 0.149762 0.058548 -0.153877\nv 0.213978 -0.017134 -0.075035\nv 0.176245 -0.016421 -0.070546\nv 0.039811 0.010002 0.246021\nv 0.024898 0.058414 0.246671\nv 0.012898 0.058658 0.197938\nv 0.028870 0.004908 0.197900\nv 0.038258 0.106660 0.239018\nv 0.028823 0.112184 0.192889\nv 0.080409 0.122522 0.225396\nv 0.076660 0.129727 0.184423\nv 0.123077 0.106165 0.214109\nv 0.124513 0.111512 0.177627\nv 0.137990 0.057753 0.213459\nv 0.140485 0.057763 0.177589\nv 0.124630 0.009507 0.221112\nv 0.124560 0.004236 0.182638\nv 0.082480 -0.006355 0.234734\nv 0.076722 -0.013307 0.191105\nv -0.500202 0.038011 -0.016318\nv -0.499007 0.059020 -0.021339\nv -0.488335 0.061054 -0.020199\nv -0.490454 0.039212 -0.015171\nv -0.499793 0.079847 -0.015191\nv -0.488270 0.082707 -0.013695\nv -0.502917 0.084241 0.000799\nv -0.491314 0.087290 0.002850\nv -0.506068 0.075843 0.016890\nv -0.494922 0.078566 0.019404\nv -0.506931 0.058295 0.023341\nv -0.496548 0.060300 0.025935\nv -0.505813 0.040928 0.018621\nv -0.496121 0.042222 0.020932\nv -0.503020 0.033074 0.001202\nv -0.493569 0.034064 0.002885\nv -0.530227 0.025482 -0.017873\nv -0.533915 0.041303 -0.022653\nv -0.514573 0.052222 -0.021842\nv -0.512997 0.033713 -0.016842\nv -0.539125 0.056987 -0.018620\nv -0.517913 0.070571 -0.016763\nv -0.542543 0.060218 -0.006183\nv -0.521278 0.074398 -0.002436\nv -0.543169 0.053856 0.006831\nv -0.523055 0.066977 0.012267\nv -0.540003 0.040759 0.012599\nv -0.521629 0.051584 0.018477\nv -0.535314 0.027798 0.009553\nv -0.518438 0.036350 0.014607\nv -0.531374 0.021843 -0.003871\nv -0.514923 0.029408 -0.000929\nv -0.577991 0.000650 -0.024082\nv -0.578647 0.014504 -0.027660\nv -0.555916 0.026906 -0.024715\nv -0.553283 0.012486 -0.020540\nv -0.580600 0.028238 -0.023848\nv -0.559923 0.041203 -0.020971\nv -0.582968 0.031111 -0.013325\nv -0.562857 0.044160 -0.009769\nv -0.584473 0.025561 -0.002581\nv -0.563693 0.038367 0.001880\nv -0.583838 0.014026 0.001900\nv -0.561411 0.026410 0.006963\nv -0.581905 0.002611 -0.001010\nv -0.557754 0.014577 0.004127\nv -0.579517 -0.002581 -0.012435\nv -0.554470 0.009156 -0.007983\nv -0.600178 0.009568 -0.031116\nv -0.600182 -0.004097 -0.027738\nv -0.601447 0.023114 -0.027281\nv -0.603627 0.025959 -0.016986\nv -0.605354 0.020491 -0.006542\nv -0.605268 0.009097 -0.002263\nv -0.603909 -0.002180 -0.005197\nv -0.601820 -0.007295 -0.016393\nv -0.509546 0.005785 0.317680\nv -0.510490 -0.007164 0.317286\nv -0.502445 0.015902 0.321540\nv -0.492293 0.014278 0.327223\nv -0.484610 0.005577 0.331600\nv -0.484915 -0.005542 0.331544\nv -0.491378 -0.013828 0.328022\nv -0.502168 -0.014035 0.322002\nv 0.313315 0.007703 -0.058138\nv 0.308716 0.036194 -0.048217\nv 0.261994 0.025344 -0.037610\nv 0.265288 -0.004698 -0.048134\nv 0.299290 0.065066 -0.056707\nv 0.255849 0.056912 -0.048506\nv 0.289713 0.074690 -0.084085\nv 0.250000 0.067435 -0.080696\nv 0.282192 0.068344 -0.117373\nv 0.244793 0.067849 -0.117902\nv 0.256094 0.056557 -0.157524\nv 0.261279 0.037416 -0.166642\nv 0.236729 0.038862 -0.172929\nv 0.237751 0.062360 -0.161267\nv 0.259483 0.018081 -0.165288\nv 0.239172 0.013511 -0.172180\nv 0.222139 0.019557 -0.165252\nv 0.215645 0.038455 -0.166805\nv 0.221522 0.054665 -0.159525\nv 0.298437 0.006194 -0.120665\nv 0.308412 -0.000776 -0.085993\nv 0.262586 -0.010639 -0.080201\nv 0.255775 -0.004619 -0.119663\nv 0.208388 0.033650 -0.150155\nv 0.216451 0.057582 -0.147589\nv 0.217960 0.010471 -0.151697\nv 0.246746 0.001224 -0.152374\nv 0.273989 0.008943 -0.150918\nv 0.278933 0.036664 -0.146061\nv 0.266241 0.063631 -0.142228\nv 0.296495 0.039024 -0.127554\nv 0.240575 0.069090 -0.143842\nv 0.045855 0.084815 -0.269362\nv 0.033227 0.048277 -0.270761\nv 0.018422 0.055851 -0.211389\nv 0.033213 0.101688 -0.211726\nv 0.045896 0.012246 -0.265054\nv 0.033257 0.010313 -0.206932\nv 0.083848 -0.001521 -0.253515\nv 0.077706 -0.005857 -0.199859\nv 0.130864 0.005496 -0.247093\nv 0.122140 0.009125 -0.194430\nv 0.142162 0.048781 -0.239743\nv 0.136929 0.057047 -0.194906\nv 0.130449 0.091083 -0.252290\nv 0.122092 0.104670 -0.199503\nv 0.083791 0.100102 -0.259585\nv 0.077645 0.118756 -0.206436\nv 0.043045 0.044910 -0.308633\nv 0.055080 0.078326 -0.307129\nv 0.055118 0.012025 -0.302982\nv 0.091288 -0.001006 -0.291372\nv 0.133589 -0.002835 -0.284589\nv 0.229401 0.004030 -0.466031\nv 0.228709 0.011812 -0.473002\nv 0.241554 0.012334 -0.478798\nv 0.239709 0.002263 -0.470508\nv 0.227656 0.020004 -0.466111\nv 0.237488 0.022592 -0.470610\nv 0.243198 0.020971 -0.462272\nv 0.248488 0.013043 -0.468116\nv 0.244943 0.004997 -0.462191\nv 0.091233 0.092954 -0.297274\nv 0.132770 0.092473 -0.290494\nv 0.200437 0.010398 -0.296628\nv 0.203611 0.035857 -0.292929\nv 0.170475 0.040945 -0.269626\nv 0.169646 0.009511 -0.274374\nv 0.196437 0.060405 -0.299024\nv 0.167321 0.071520 -0.277974\nv 0.180251 0.067372 -0.314113\nv 0.160958 0.080565 -0.298218\nv 0.165397 0.057671 -0.328405\nv 0.155370 0.068942 -0.317264\nv 0.162224 0.032211 -0.332103\nv 0.154540 0.037507 -0.322012\nv 0.169397 0.007663 -0.326008\nv 0.157695 0.006933 -0.313664\nv 0.185584 0.000696 -0.310919\nv 0.164058 -0.002113 -0.293419\nv 0.228964 0.014758 -0.347284\nv 0.233020 0.034702 -0.345572\nv 0.223308 0.035835 -0.318029\nv 0.219233 0.013680 -0.320632\nv 0.224763 0.053856 -0.347744\nv 0.214769 0.057113 -0.321909\nv 0.205595 0.059188 -0.353646\nv 0.195104 0.063036 -0.331847\nv 0.187828 0.051488 -0.359394\nv 0.176927 0.054482 -0.341360\nv 0.183772 0.031544 -0.361106\nv 0.172852 0.032327 -0.343963\nv 0.192029 0.012390 -0.358934\nv 0.181391 0.011049 -0.340083\nv 0.211197 0.007058 -0.353032\nv 0.201056 0.005126 -0.330145\nv 0.235287 0.002638 -0.409834\nv 0.238334 0.017092 -0.408770\nv 0.236197 0.026281 -0.376208\nv 0.232561 0.009033 -0.377478\nv 0.232191 0.030974 -0.409976\nv 0.228867 0.042846 -0.377648\nv 0.217891 0.034838 -0.413405\nv 0.211802 0.047458 -0.381739\nv 0.204622 0.029257 -0.416786\nv 0.195969 0.040798 -0.385775\nv 0.201575 0.014804 -0.417850\nv 0.192333 0.023550 -0.387044\nv 0.207718 0.000922 -0.416644\nv 0.199663 0.006985 -0.385605\nv 0.222018 -0.002943 -0.413215\nv 0.216728 0.002373 -0.381513\nv -0.195709 -0.000531 -0.508181\nv -0.190023 0.020406 -0.510858\nv -0.165175 0.024215 -0.468430\nv -0.171859 -0.000396 -0.465283\nv -0.195709 0.041343 -0.508181\nv -0.171859 0.048826 -0.465283\nv -0.212769 0.048322 -0.500151\nv -0.191912 0.057029 -0.455844\nv -0.229828 0.041343 -0.492121\nv -0.211965 0.048826 -0.446404\nv -0.235515 0.020406 -0.489444\nv -0.218649 0.024215 -0.443258\nv -0.229828 -0.000531 -0.492121\nv -0.211965 -0.000396 -0.446404\nv -0.212769 -0.007510 -0.500151\nv -0.191912 -0.008600 -0.455844\nv -0.210797 0.021168 -0.551537\nv -0.216151 0.001456 -0.549017\nv -0.216151 0.040881 -0.549017\nv -0.232212 0.047451 -0.541456\nv -0.248274 0.040881 -0.533895\nv -0.253628 0.021168 -0.531375\nv -0.248274 0.001456 -0.533895\nv -0.232212 -0.005115 -0.541456\nv -0.260902 0.007780 0.475240\nv -0.265090 0.018120 0.475280\nv -0.263024 -0.003003 0.473533\nv -0.272255 -0.006071 0.470834\nv -0.282120 -0.002656 0.468673\nv -0.286145 0.006007 0.468575\nv -0.283859 0.015114 0.470143\nv -0.274792 0.019858 0.472980\nv 0.072525 0.054676 0.333097\nv 0.053770 0.086720 0.317159\nv 0.042676 0.071424 0.287076\nv 0.058050 0.030078 0.289776\nv 0.060277 0.129018 0.310931\nv 0.051970 0.113229 0.275939\nv 0.098755 0.140408 0.294198\nv 0.087957 0.127774 0.260978\nv 0.132187 0.130110 0.286796\nv 0.125971 0.114603 0.250629\nv 0.147306 0.095469 0.293342\nv 0.141345 0.073257 0.253329\nv 0.141130 0.059893 0.307075\nv 0.132052 0.031452 0.264465\nv 0.110679 0.046787 0.321236\nv 0.096064 0.016906 0.279427\nv 0.013864 0.068704 0.405022\nv 0.001870 0.078760 0.406784\nv -0.001984 0.078039 0.391862\nv 0.010169 0.069730 0.389591\nv 0.000004 0.095434 0.406493\nv -0.004644 0.092533 0.389678\nv -0.002777 0.088830 0.376945\nv -0.001167 0.077564 0.375950\nv 0.009391 0.072528 0.376940\nv 0.091257 0.154626 0.368762\nv 0.123189 0.158347 0.356935\nv 0.112254 0.155349 0.328085\nv 0.077898 0.150945 0.341658\nv 0.139850 0.147914 0.346302\nv 0.140718 0.145966 0.322220\nv 0.148551 0.130015 0.358216\nv 0.152631 0.115588 0.329643\nv 0.141917 0.104535 0.361341\nv 0.146173 0.084652 0.343320\nv 0.124203 0.101623 0.376707\nv 0.119528 0.073596 0.356217\nv 0.094828 0.100003 0.386024\nv 0.087984 0.073681 0.366252\nv -0.244224 0.021633 -0.208208\nv 0.300438 0.113530 0.051428\nv 0.260233 0.106938 0.041435\nv 0.289195 0.078272 0.062749\nv 0.257812 0.065875 0.056649\nv 0.275094 0.067742 0.094898\nv 0.245203 0.052256 0.094618\nv 0.265901 0.081024 0.126594\nv 0.231258 0.066030 0.130669\nv 0.269379 0.117200 0.136554\nv 0.224827 0.107144 0.140129\nv 0.382747 0.060207 0.101125\nv 0.378729 0.036589 0.094456\nv 0.341191 0.078779 0.071799\nv 0.347017 0.108442 0.080499\nv 0.369645 0.013795 0.101511\nv 0.329006 0.050152 0.080409\nv 0.359863 0.007298 0.122162\nv 0.316466 0.041991 0.106361\nv 0.354448 0.016270 0.142683\nv 0.309929 0.053261 0.132342\nv 0.358466 0.039888 0.149352\nv 0.315754 0.082924 0.141042\nv 0.367550 0.062682 0.142297\nv 0.327939 0.111550 0.132431\nv 0.377332 0.069179 0.121646\nv 0.340480 0.119711 0.106480\nv 0.436351 0.037635 0.129290\nv 0.437004 0.020240 0.125883\nv 0.409289 0.020862 0.111307\nv 0.410751 0.040946 0.116164\nv 0.433913 0.003454 0.132240\nv 0.403511 0.001479 0.117934\nv 0.427890 -0.001332 0.147378\nv 0.395831 -0.004046 0.135456\nv 0.422679 0.005276 0.161532\nv 0.390566 0.003584 0.152388\nv 0.422026 0.022671 0.164939\nv 0.392028 0.023668 0.157245\nv 0.425117 0.039458 0.158582\nv 0.397806 0.043050 0.150617\nv 0.431140 0.044243 0.143444\nv 0.405485 0.048576 0.133095\nv 0.466009 0.023366 0.141717\nv 0.464866 0.037250 0.144177\nv 0.464162 0.009968 0.146991\nv 0.459558 0.006148 0.159061\nv 0.455188 0.011422 0.170193\nv 0.454045 0.025306 0.172653\nv 0.455892 0.038704 0.167379\nv 0.460496 0.042524 0.155309\nv -0.346505 0.009389 -0.411818\nv -0.348371 0.000808 -0.409511\nv -0.348224 0.018115 -0.410425\nv -0.353585 0.020309 -0.404979\nv -0.358991 0.017187 -0.399252\nv -0.360845 0.009967 -0.397016\nv -0.359115 0.002602 -0.398481\nv -0.353766 -0.000953 -0.403855\nv -0.422209 0.028613 -0.279045\nv -0.418633 0.041197 -0.280535\nv -0.392615 0.029587 -0.269345\nv -0.397456 0.015199 -0.266530\nv -0.419219 0.051924 -0.273295\nv -0.394831 0.043081 -0.263469\nv -0.424963 0.053025 -0.259243\nv -0.404981 0.046385 -0.249923\nv -0.431704 0.046355 -0.247107\nv -0.416006 0.040394 -0.237398\nv -0.435280 0.033771 -0.245616\nv -0.420847 0.026006 -0.234584\nv -0.434694 0.023044 -0.252856\nv -0.418630 0.012512 -0.240460\nv -0.428950 0.021943 -0.266909\nv -0.408481 0.009209 -0.254005\nv -0.459678 0.039994 -0.291077\nv -0.460748 0.049158 -0.293135\nv -0.441903 0.049785 -0.287853\nv -0.443145 0.038957 -0.286408\nv -0.464000 0.056699 -0.288367\nv -0.443401 0.058626 -0.281389\nv -0.467993 0.057049 -0.277677\nv -0.447555 0.058922 -0.268688\nv -0.470547 0.051830 -0.267890\nv -0.451622 0.052662 -0.257661\nv -0.469477 0.042666 -0.265832\nv -0.452864 0.041834 -0.256216\nv -0.466226 0.035125 -0.270600\nv -0.451366 0.032993 -0.262681\nv -0.462232 0.034776 -0.281290\nv -0.447213 0.032697 -0.275381\nv -0.483774 0.008588 -0.299925\nv -0.485970 0.015128 -0.302894\nv -0.473493 0.033123 -0.298215\nv -0.471227 0.025490 -0.295506\nv -0.489914 0.021028 -0.300334\nv -0.477634 0.039660 -0.294849\nv -0.493562 0.022142 -0.292110\nv -0.481516 0.040378 -0.285626\nv -0.495163 0.019110 -0.283750\nv -0.483261 0.036373 -0.276622\nv -0.492968 0.012571 -0.280781\nv -0.480995 0.028741 -0.273913\nv -0.489023 0.006671 -0.283341\nv -0.476854 0.022204 -0.277280\nv -0.485375 0.005556 -0.291565\nv -0.472973 0.021486 -0.286503\nv -0.525887 0.001769 -0.309852\nv -0.524980 0.007814 -0.311495\nv -0.504009 0.008621 -0.306968\nv -0.503304 0.002432 -0.304560\nv -0.525791 0.013382 -0.308536\nv -0.506447 0.014323 -0.304324\nv -0.528351 0.014602 -0.301414\nv -0.509572 0.015572 -0.296707\nv -0.530944 0.011951 -0.294731\nv -0.511649 0.012858 -0.289169\nv -0.531850 0.005906 -0.293089\nv -0.510944 0.006668 -0.286760\nv -0.531039 0.000338 -0.296048\nv -0.508506 0.000967 -0.289404\nv -0.528479 -0.000882 -0.303169\nv -0.505381 -0.000283 -0.297021\nv -0.546254 0.006916 -0.317531\nv -0.547594 0.001350 -0.316243\nv -0.546503 0.012042 -0.314687\nv -0.548705 0.013166 -0.308230\nv -0.551270 0.010725 -0.302292\nv -0.552610 0.005159 -0.301004\nv -0.552361 0.000033 -0.303848\nv -0.550160 -0.001091 -0.310305\nv 0.370419 0.023367 -0.075609\nv 0.372562 0.051255 -0.068471\nv 0.342878 0.046915 -0.057313\nv 0.344601 0.018195 -0.066053\nv 0.369436 0.079142 -0.078055\nv 0.335627 0.075636 -0.066117\nv 0.361368 0.088438 -0.103546\nv 0.325840 0.085209 -0.092441\nv 0.353628 0.079142 -0.128222\nv 0.319044 0.075636 -0.118745\nv 0.351485 0.051255 -0.135360\nv 0.320767 0.046915 -0.127485\nv 0.354612 0.023367 -0.125776\nv 0.328018 0.018195 -0.118681\nv 0.362680 0.014071 -0.100285\nv 0.337805 0.008622 -0.092357\nv 0.437697 0.029993 -0.102297\nv 0.405849 0.042958 -0.085263\nv 0.402043 0.019808 -0.090538\nv 0.438221 0.047716 -0.109207\nv 0.405289 0.066109 -0.093844\nv 0.433912 0.053623 -0.125740\nv 0.399281 0.073825 -0.115178\nv 0.428133 0.047716 -0.141223\nv 0.392192 0.066109 -0.135409\nv 0.424246 0.029993 -0.144985\nv 0.388386 0.042958 -0.140684\nv 0.388945 0.019808 -0.132103\nv 0.394953 0.012091 -0.110769\nv 0.463067 0.020325 -0.114179\nv 0.463266 0.035145 -0.119869\nv 0.459591 0.040085 -0.133637\nv 0.454849 0.035145 -0.146580\nv 0.451844 0.020325 -0.149793\nv 0.126298 0.135156 0.375045\nv 0.096410 0.132315 0.385206\nv 0.040450 0.078470 0.355322\nv 0.028149 0.092307 0.347250\nv 0.045716 0.093333 0.334926\nv 0.056951 0.073328 0.347902\nv 0.020092 0.110493 0.352477\nv 0.040408 0.120704 0.336663\nv 0.023908 0.125011 0.372161\nv 0.048308 0.139963 0.357362\nv 0.033109 0.129285 0.398931\nv 0.060838 0.143273 0.381555\nv 0.050742 0.110239 0.403510\nv 0.069423 0.122799 0.393776\nv 0.054113 0.083847 0.399035\nv 0.072082 0.094959 0.391285\nv 0.049367 0.077001 0.375534\nv 0.066832 0.076169 0.371340\nv 0.010699 0.084685 0.361002\nv 0.021528 0.074605 0.366510\nv 0.003022 0.097626 0.366469\nv 0.005122 0.108695 0.383023\nv 0.011011 0.113435 0.403860\nv 0.031955 0.017637 0.535588\nv 0.032446 0.010980 0.537631\nv 0.040292 0.015143 0.540174\nv 0.036680 0.023431 0.537843\nv 0.036335 0.006818 0.534564\nv 0.043259 0.007180 0.536305\nv 0.047975 0.012008 0.533624\nv 0.047493 0.019631 0.536517\nv 0.042608 0.025264 0.534878\nv 0.028883 0.072119 0.382883\nv 0.033242 0.070559 0.401500\nv 0.034047 0.098694 0.438627\nv 0.046931 0.083665 0.432827\nv 0.045768 0.098267 0.417161\nv 0.031616 0.114371 0.421059\nv 0.049791 0.062797 0.426479\nv 0.049482 0.075689 0.413662\nv 0.035865 0.051509 0.424803\nv 0.035082 0.063257 0.413741\nv 0.017448 0.050460 0.426591\nv 0.015588 0.061856 0.415929\nv 0.006838 0.060306 0.430637\nv 0.004016 0.072376 0.418759\nv 0.006251 0.075991 0.435231\nv 0.002882 0.089368 0.421190\nv 0.017904 0.092462 0.438662\nv 0.014701 0.107386 0.422179\nv 0.037685 0.053362 0.471306\nv 0.046178 0.043490 0.466755\nv 0.046660 0.063221 0.448605\nv 0.035968 0.075700 0.454369\nv 0.047932 0.029812 0.461549\nv 0.048868 0.045929 0.442007\nv 0.038545 0.022444 0.459878\nv 0.037049 0.036613 0.439886\nv 0.026235 0.021793 0.460989\nv 0.021550 0.035789 0.441290\nv 0.019222 0.028265 0.464131\nv 0.012720 0.043970 0.445269\nv 0.018948 0.038543 0.467928\nv 0.012375 0.056963 0.450082\nv 0.026856 0.049311 0.471008\nv 0.022332 0.070577 0.453988\nv 0.041242 0.032451 0.513003\nv 0.047806 0.024254 0.511460\nv 0.046707 0.031033 0.489537\nv 0.039510 0.039656 0.492458\nv 0.049182 0.013086 0.510143\nv 0.048203 0.019173 0.486406\nv 0.041961 0.007264 0.510280\nv 0.040264 0.012877 0.485666\nv 0.032475 0.006966 0.511234\nv 0.029846 0.012422 0.486651\nv 0.027057 0.012366 0.512364\nv 0.023905 0.018088 0.488699\nv 0.026827 0.020739 0.513268\nv 0.023664 0.026990 0.490956\nv 0.032902 0.029356 0.513544\nv 0.030347 0.036244 0.492570\nv 0.049033 0.021561 0.527033\nv 0.042680 0.029615 0.528117\nv 0.050369 0.010623 0.526320\nv 0.043387 0.004960 0.526750\nv 0.034211 0.004713 0.527693\nv 0.028969 0.010025 0.528518\nv 0.028742 0.018221 0.528971\nv 0.034615 0.026627 0.528801\nv 0.244923 0.013654 -0.442090\nv 0.242404 0.001708 -0.442969\nv 0.239846 0.025128 -0.443086\nv 0.228026 0.028322 -0.445920\nv 0.217060 0.023709 -0.448715\nv 0.214542 0.011763 -0.449595\nv 0.219618 0.000290 -0.448598\nv 0.231438 -0.002904 -0.445764\nvt 0.770437 0.582312\nvt 0.770299 0.531964\nvt 0.878209 0.536342\nvt 0.683696 0.536342\nvt 0.736189 0.502710\nvt 0.776974 0.490852\nvt 0.814925 0.505853\nvt 0.456331 0.582312\nvt 0.456503 0.531964\nvt 0.531950 0.536342\nvt 0.364131 0.536342\nvt 0.416867 0.496199\nvt 0.455369 0.488217\nvt 0.487438 0.504053\nvt 0.605766 0.582312\nvt 0.605571 0.531964\nvt 0.571832 0.505407\nvt 0.604738 0.489421\nvt 0.640071 0.500720\nvt 0.248184 0.582312\nvt 0.250719 0.531964\nvt 0.102799 0.536342\nvt 0.196845 0.503757\nvt 0.254919 0.491381\nvt 0.299896 0.505710\nvt 0.745049 0.465743\nvt 0.745645 0.448066\nvt 0.759108 0.448703\nvt 0.758187 0.431633\nvt 0.768750 0.436414\nvt 0.772993 0.448515\nvt 0.769210 0.461049\nvt 0.758941 0.466114\nvt 0.572323 0.440821\nvt 0.574536 0.441110\nvt 0.574854 0.445950\nvt 0.574327 0.435115\nvt 0.576855 0.440244\nvt 0.356366 0.497343\nvt 0.365547 0.460043\nvt 0.404247 0.458954\nvt 0.404642 0.431490\nvt 0.443352 0.458113\nvt 0.201498 0.454066\nvt 0.200358 0.445641\nvt 0.212216 0.443618\nvt 0.202194 0.436082\nvt 0.211430 0.432205\nvt 0.223420 0.441458\nvt 0.225269 0.452937\nvt 0.210493 0.454956\nvt 0.429003 0.918625\nvt 0.426172 0.912411\nvt 0.438158 0.904419\nvt 0.429362 0.918625\nvt 0.438270 0.912411\nvt 0.447120 0.918625\nvt 0.450145 0.912411\nvt 0.446954 0.918625\nvt 0.438046 0.912411\nvt 0.528346 0.427286\nvt 0.504652 0.428001\nvt 0.532405 0.428582\nvt 0.459382 0.427927\nvt 0.478990 0.428285\nvt 0.592258 0.428285\nvt 0.598761 0.427927\nvt 0.553135 0.428001\nvt 0.848886 0.672062\nvt 0.777877 0.672062\nvt 0.770483 0.628282\nvt 0.683775 0.628282\nvt 0.683756 0.583406\nvt 0.873338 0.583406\nvt 0.869730 0.628282\nvt 0.605831 0.672062\nvt 0.605831 0.628282\nvt 0.532070 0.628282\nvt 0.532040 0.583406\nvt 0.456273 0.672062\nvt 0.456273 0.628282\nvt 0.364657 0.628282\nvt 0.364523 0.583406\nvt 0.364657 0.672062\nvt 0.247234 0.672062\nvt 0.247234 0.628282\nvt 0.099233 0.628282\nvt 0.100128 0.583406\nvt 0.783242 0.755781\nvt 0.770483 0.755781\nvt 0.770483 0.715843\nvt 0.683775 0.755781\nvt 0.683775 0.715843\nvt 0.832338 0.715843\nvt 0.605831 0.755781\nvt 0.605831 0.715843\nvt 0.532070 0.755781\nvt 0.532070 0.715843\nvt 0.683775 0.672062\nvt 0.456273 0.755781\nvt 0.456273 0.715843\nvt 0.364657 0.715843\nvt 0.364657 0.755781\nvt 0.247234 0.755781\nvt 0.247234 0.715843\nvt 0.099233 0.715843\nvt 0.099233 0.672062\nvt 0.753143 0.811133\nvt 0.698666 0.808948\nvt 0.770483 0.788033\nvt 0.683775 0.788033\nvt 0.778743 0.788033\nvt 0.605831 0.808948\nvt 0.605831 0.788033\nvt 0.532070 0.808948\nvt 0.532070 0.788033\nvt 0.456273 0.808948\nvt 0.456273 0.788033\nvt 0.364657 0.788033\nvt 0.364657 0.808948\nvt 0.247234 0.808948\nvt 0.247234 0.788033\nvt 0.099233 0.788033\nvt 0.454140 0.936383\nvt 0.450256 0.936383\nvt 0.698666 0.814873\nvt 0.683775 0.814873\nvt 0.753143 0.816718\nvt 0.438307 0.936383\nvt 0.426283 0.936383\nvt 0.605831 0.814873\nvt 0.532070 0.814873\nvt 0.422176 0.936383\nvt 0.426060 0.936383\nvt 0.456273 0.814873\nvt 0.364657 0.814873\nvt 0.438009 0.936383\nvt 0.457082 0.935935\nvt 0.104309 0.811133\nvt 0.247234 0.814873\nvt 0.527545 0.425072\nvt 0.497833 0.425072\nvt 0.494388 0.421847\nvt 0.454888 0.427515\nvt 0.455519 0.425072\nvt 0.680179 0.427286\nvt 0.683853 0.425072\nvt 0.647186 0.425072\nvt 0.646639 0.421466\nvt 0.600610 0.425660\nvt 0.600136 0.425072\nvt 0.650325 0.428001\nvt 0.107102 0.427286\nvt 0.111560 0.425072\nvt 0.049078 0.425072\nvt 0.049529 0.424847\nvt 0.773139 0.426926\nvt 0.767160 0.425072\nvt 0.841808 0.425072\nvt 0.759513 0.427927\nvt 0.846146 0.428001\nvt 0.365887 0.427286\nvt 0.360849 0.425072\nvt 0.319389 0.425072\nvt 0.316604 0.424184\nvt 0.260659 0.426466\nvt 0.259372 0.425072\nvt 0.334728 0.428001\nvt 0.070552 0.438367\nvt 0.066949 0.438740\nvt 0.066736 0.428722\nvt 0.069431 0.445222\nvt 0.066736 0.448012\nvt 0.063711 0.445222\nvt 0.062941 0.438367\nvt 0.564438 0.501489\nvt 0.576778 0.506641\nvt 0.572024 0.506073\nvt 0.587872 0.499333\nvt 0.586376 0.490779\nvt 0.565341 0.497781\nvt 0.555700 0.499409\nvt 0.390414 0.428582\nvt 0.264894 0.427927\nvt 0.325801 0.428285\nvt 0.401231 0.428001\nvt 0.404054 0.425072\nvt 0.716021 0.425072\nvt 0.723030 0.422758\nvt 0.705717 0.428001\nvt 0.647933 0.427184\nvt 0.648795 0.431842\nvt 0.646254 0.431689\nvt 0.648144 0.436738\nvt 0.646287 0.438291\nvt 0.644191 0.436738\nvt 0.643503 0.431842\nvt 0.643975 0.427184\nvt 0.646002 0.425393\nvt 0.341082 0.456398\nvt 0.341612 0.464662\nvt 0.335350 0.464704\nvt 0.341597 0.475744\nvt 0.335155 0.475787\nvt 0.328830 0.464568\nvt 0.327703 0.456306\nvt 0.334400 0.456441\nvt 0.664738 0.428582\nvt 0.715598 0.428285\nvt 0.560542 0.427839\nvt 0.558072 0.425072\nvt 0.559030 0.423722\nvt 0.112645 0.426727\nvt 0.178478 0.425072\nvt 0.160151 0.428001\nvt 0.186633 0.422179\nvt 0.053415 0.428001\nvt 0.083724 0.428582\nvt 0.876455 0.428582\nvt 0.811519 0.504157\nvt 0.794788 0.470602\nvt 0.786098 0.464973\nvt 0.782337 0.489419\nvt 0.780025 0.459275\nvt 0.787462 0.451857\nvt 0.795094 0.491269\nvt 0.793452 0.498158\nvt 0.792254 0.498051\nvt 0.073071 0.491269\nvt 0.080079 0.489419\nvt 0.049905 0.497567\nvt 0.109190 0.496573\nvt 0.054992 0.505853\nvt 0.079895 0.470602\nvt 0.089896 0.464973\nvt 0.073210 0.449935\nvt 0.080869 0.440528\nvt 0.111629 0.459581\nvt 0.052941 0.443046\nvt 0.050425 0.432379\nvt 0.843156 0.432379\nvt 0.827601 0.443046\nvt 0.842259 0.424847\nvt 0.047294 0.474018\nvt 0.039418 0.475449\nvt 0.043861 0.458028\nvt 0.050751 0.489202\nvt 0.043861 0.492869\nvt 0.002363 0.491269\nvt 0.002057 0.470602\nvt 0.061361 0.494263\nvt 0.057685 0.498675\nvt 0.072026 0.492869\nvt 0.043849 0.498158\nvt 0.072217 0.489202\nvt 0.075864 0.474018\nvt 0.076862 0.475449\nvt 0.072026 0.458028\nvt 0.061361 0.453772\nvt 0.057685 0.452222\nvt 0.050751 0.458833\nvt 0.018788 0.504157\nvt 0.052073 0.445749\nvt 0.050175 0.460813\nvt 0.053111 0.446374\nvt 0.054600 0.459588\nvt 0.053111 0.475252\nvt 0.062289 0.464202\nvt 0.062075 0.480065\nvt 0.071206 0.475252\nvt 0.070100 0.459588\nvt 0.072721 0.445749\nvt 0.074271 0.460813\nvt 0.071206 0.446374\nvt 0.072217 0.458833\nvt 0.070100 0.431909\nvt 0.062289 0.427296\nvt 0.062075 0.441561\nvt 0.054600 0.431909\nvt 0.218794 0.473211\nvt 0.229362 0.451397\nvt 0.240984 0.455445\nvt 0.230442 0.430141\nvt 0.258499 0.459192\nvt 0.226999 0.480748\nvt 0.221456 0.429752\nvt 0.194398 0.422639\nvt 0.191249 0.421706\nvt 0.164288 0.426690\nvt 0.152127 0.430141\nvt 0.153627 0.450661\nvt 0.139380 0.455445\nvt 0.161934 0.474630\nvt 0.149318 0.480748\nvt 0.191029 0.480831\nvt 0.186789 0.489183\nvt 0.219025 0.456712\nvt 0.224649 0.442249\nvt 0.222946 0.446272\nvt 0.217083 0.429231\nvt 0.215108 0.463990\nvt 0.220617 0.427393\nvt 0.202112 0.425537\nvt 0.197827 0.423961\nvt 0.176355 0.423550\nvt 0.648130 0.427091\nvt 0.645098 0.424874\nvt 0.644536 0.427168\nvt 0.642190 0.427091\nvt 0.641194 0.429585\nvt 0.643820 0.430004\nvt 0.648037 0.429585\nvt 0.200314 0.463327\nvt 0.195366 0.471291\nvt 0.174244 0.470273\nvt 0.143934 0.437203\nvt 0.151752 0.443329\nvt 0.154639 0.425093\nvt 0.144118 0.452733\nvt 0.153085 0.460929\nvt 0.146316 0.455351\nvt 0.158658 0.463034\nvt 0.163790 0.454651\nvt 0.148111 0.449033\nvt 0.152240 0.436810\nvt 0.165744 0.440788\nvt 0.164820 0.427560\nvt 0.184450 0.428955\nvt 0.181678 0.444760\nvt 0.147738 0.418967\nvt 0.160361 0.421082\nvt 0.145423 0.421652\nvt 0.131219 0.426982\nvt 0.129635 0.437058\nvt 0.136382 0.436040\nvt 0.130378 0.446701\nvt 0.135670 0.450369\nvt 0.136897 0.421844\nvt 0.127453 0.449279\nvt 0.135346 0.453002\nvt 0.135079 0.450625\nvt 0.126627 0.437512\nvt 0.133143 0.435734\nvt 0.133053 0.444235\nvt 0.126512 0.428961\nvt 0.133529 0.427342\nvt 0.139292 0.436750\nvt 0.128442 0.425088\nvt 0.136681 0.419610\nvt 0.136117 0.422285\nvt 0.642160 0.432301\nvt 0.643219 0.438749\nvt 0.641377 0.433740\nvt 0.642918 0.425852\nvt 0.646000 0.440899\nvt 0.648691 0.438749\nvt 0.648454 0.440389\nvt 0.645542 0.442606\nvt 0.642532 0.440389\nvt 0.649462 0.432301\nvt 0.648401 0.425852\nvt 0.649285 0.433740\nvt 0.645605 0.423702\nvt 0.148198 0.435321\nvt 0.146971 0.435352\nvt 0.143684 0.445940\nvt 0.144367 0.425273\nvt 0.149001 0.424021\nvt 0.134838 0.422756\nvt 0.135503 0.422819\nvt 0.126253 0.427141\nvt 0.123970 0.426238\nvt 0.120522 0.438327\nvt 0.122844 0.437391\nvt 0.125563 0.447131\nvt 0.123162 0.437513\nvt 0.133989 0.449577\nvt 0.134583 0.449924\nvt 0.156972 0.436846\nvt 0.159593 0.435992\nvt 0.157349 0.445110\nvt 0.155239 0.428772\nvt 0.157864 0.426639\nvt 0.158027 0.428648\nvt 0.160089 0.435951\nvt 0.157610 0.443031\nvt 0.134127 0.423640\nvt 0.143211 0.423080\nvt 0.125499 0.425209\nvt 0.110320 0.431413\nvt 0.108826 0.431215\nvt 0.108694 0.427823\nvt 0.108553 0.434813\nvt 0.106933 0.431223\nvt 0.126026 0.446191\nvt 0.133398 0.448265\nvt 0.142505 0.447708\nvt 0.123243 0.449188\nvt 0.124761 0.450325\nvt 0.592138 0.468346\nvt 0.595444 0.462415\nvt 0.588113 0.487734\nvt 0.584489 0.445555\nvt 0.585608 0.436805\nvt 0.603038 0.456694\nvt 0.565012 0.437482\nvt 0.561461 0.427881\nvt 0.539970 0.435933\nvt 0.546338 0.444484\nvt 0.539558 0.466918\nvt 0.532825 0.461252\nvt 0.539065 0.486862\nvt 0.525807 0.458579\nvt 0.545054 0.489708\nvt 0.562312 0.495786\nvt 0.528519 0.496367\nvt 0.362359 0.453941\nvt 0.364480 0.444636\nvt 0.358388 0.458713\nvt 0.357711 0.446815\nvt 0.350754 0.458925\nvt 0.350986 0.471579\nvt 0.356988 0.468693\nvt 0.363486 0.434196\nvt 0.358920 0.430249\nvt 0.354707 0.441449\nvt 0.354179 0.432360\nvt 0.351713 0.442798\nvt 0.349573 0.452952\nvt 0.350126 0.452453\nvt 0.352343 0.440098\nvt 0.350606 0.451042\nvt 0.353450 0.448971\nvt 0.351314 0.461204\nvt 0.349431 0.461212\nvt 0.357699 0.454486\nvt 0.353981 0.468306\nvt 0.350193 0.480338\nvt 0.349636 0.472063\nvt 0.366188 0.438452\nvt 0.367248 0.432751\nvt 0.366752 0.436690\nvt 0.365851 0.428611\nvt 0.365072 0.444187\nvt 0.366542 0.426779\nvt 0.363932 0.425043\nvt 0.362055 0.425966\nvt 0.361240 0.426890\nvt 0.358083 0.428102\nvt 0.360236 0.431664\nvt 0.356544 0.434363\nvt 0.357523 0.441206\nvt 0.360970 0.436710\nvt 0.363519 0.439372\nvt 0.361168 0.445086\nvt 0.154533 0.435625\nvt 0.154032 0.425575\nvt 0.153438 0.445623\nvt 0.151829 0.423207\nvt 0.149626 0.425991\nvt 0.141539 0.427069\nvt 0.157132 0.429239\nvt 0.156041 0.429433\nvt 0.155770 0.427123\nvt 0.155929 0.431950\nvt 0.154570 0.429833\nvt 0.151135 0.447835\nvt 0.149301 0.447433\nvt 0.141158 0.445218\nvt 0.108394 0.432474\nvt 0.106484 0.443052\nvt 0.113339 0.441540\nvt 0.114282 0.452332\nvt 0.114881 0.430682\nvt 0.107878 0.453645\nvt 0.112609 0.456086\nvt 0.117757 0.454705\nvt 0.121143 0.450430\nvt 0.118924 0.443111\nvt 0.122416 0.441274\nvt 0.117679 0.434183\nvt 0.121622 0.432185\nvt 0.113239 0.430077\nvt 0.118474 0.428109\nvt 0.104321 0.433529\nvt 0.104158 0.438883\nvt 0.105687 0.430650\nvt 0.105088 0.439137\nvt 0.105319 0.447173\nvt 0.107711 0.440519\nvt 0.109250 0.449148\nvt 0.113205 0.446047\nvt 0.117250 0.452024\nvt 0.110403 0.438479\nvt 0.111388 0.433821\nvt 0.114636 0.439114\nvt 0.113520 0.432123\nvt 0.110610 0.429089\nvt 0.108006 0.426830\nvt 0.109706 0.428850\nvt 0.105324 0.427994\nvt 0.284615 0.469527\nvt 0.274564 0.463414\nvt 0.287459 0.438177\nvt 0.287640 0.491886\nvt 0.279977 0.488626\nvt 0.309576 0.499260\nvt 0.309256 0.496995\nvt 0.334580 0.491709\nvt 0.339557 0.488548\nvt 0.343068 0.469290\nvt 0.349173 0.463310\nvt 0.336466 0.446931\nvt 0.341436 0.438098\nvt 0.315962 0.439556\nvt 0.315707 0.429729\nvt 0.294787 0.447108\nvt 0.305947 0.467044\nvt 0.300676 0.485497\nvt 0.292138 0.478942\nvt 0.294940 0.499301\nvt 0.299807 0.458477\nvt 0.303812 0.503819\nvt 0.317104 0.509753\nvt 0.312021 0.505946\nvt 0.332281 0.503429\nvt 0.332051 0.498983\nvt 0.338203 0.484976\nvt 0.339532 0.478518\nvt 0.333877 0.466654\nvt 0.334163 0.458159\nvt 0.319658 0.460720\nvt 0.316929 0.451514\nvt 0.241967 0.453165\nvt 0.244605 0.441799\nvt 0.248665 0.442219\nvt 0.242866 0.430168\nvt 0.246231 0.429117\nvt 0.245356 0.432235\nvt 0.248737 0.442134\nvt 0.244444 0.451645\nvt 0.245129 0.454950\nvt 0.207516 0.427232\nvt 0.223258 0.426107\nvt 0.196026 0.432601\nvt 0.192033 0.446200\nvt 0.195017 0.458432\nvt 0.206093 0.459704\nvt 0.221754 0.457102\nvt 0.237172 0.441507\nvt 0.237638 0.428520\nvt 0.236325 0.454478\nvt 0.237232 0.425241\nvt 0.236275 0.426674\nvt 0.226230 0.429679\nvt 0.223242 0.426793\nvt 0.223600 0.430929\nvt 0.221018 0.430868\nvt 0.223077 0.435339\nvt 0.221174 0.437117\nvt 0.219610 0.435961\nvt 0.218830 0.431102\nvt 0.219592 0.426427\nvt 0.221272 0.424914\nvt 0.235726 0.457714\nvt 0.235139 0.455863\nvt 0.317642 0.477618\nvt 0.310974 0.483025\nvt 0.314156 0.467568\nvt 0.314293 0.498387\nvt 0.330311 0.492921\nvt 0.324415 0.503382\nvt 0.335204 0.498104\nvt 0.341516 0.477598\nvt 0.339101 0.482648\nvt 0.335483 0.467286\nvt 0.329078 0.459652\nvt 0.324494 0.462291\nvt 0.305555 0.421820\nvt 0.307078 0.424950\nvt 0.305440 0.427055\nvt 0.306735 0.429118\nvt 0.305777 0.432224\nvt 0.303634 0.432595\nvt 0.302849 0.429107\nvt 0.302353 0.424500\nvt 0.304125 0.421833\nvt 0.333586 0.484457\nvt 0.326336 0.484327\nvt 0.340892 0.484411\nvt 0.339934 0.489758\nvt 0.321615 0.489782\nvt 0.363123 0.432627\nvt 0.362727 0.428924\nvt 0.365036 0.429008\nvt 0.363310 0.425958\nvt 0.365083 0.424581\nvt 0.366619 0.426140\nvt 0.367160 0.429720\nvt 0.366401 0.433538\nvt 0.364791 0.434062\nvt 0.332325 0.456229\nvt 0.340051 0.456183\nvt 0.325054 0.456099\nvt 0.320197 0.462482\nvt 0.339213 0.462459\nvt 0.314051 0.473131\nvt 0.317970 0.475366\nvt 0.318425 0.462457\nvt 0.318946 0.485453\nvt 0.314523 0.482680\nvt 0.316083 0.481053\nvt 0.320964 0.483834\nvt 0.322696 0.475159\nvt 0.328457 0.475652\nvt 0.317594 0.472725\nvt 0.318193 0.462168\nvt 0.323145 0.464074\nvt 0.317864 0.454352\nvt 0.322431 0.455813\nvt 0.316414 0.454246\nvt 0.320461 0.455606\nvt 0.314731 0.460841\nvt 0.345339 0.475891\nvt 0.345720 0.485980\nvt 0.345250 0.462985\nvt 0.345646 0.456144\nvt 0.346130 0.456360\nvt 0.346414 0.464625\nvt 0.346455 0.475706\nvt 0.346168 0.484372\nvt 0.350784 0.481641\nvt 0.300250 0.459670\nvt 0.307256 0.467770\nvt 0.308288 0.457340\nvt 0.301671 0.466125\nvt 0.307941 0.475707\nvt 0.305148 0.464523\nvt 0.310291 0.474055\nvt 0.312629 0.466768\nvt 0.308444 0.458279\nvt 0.309521 0.450810\nvt 0.313583 0.457795\nvt 0.313084 0.451317\nvt 0.308376 0.445555\nvt 0.304838 0.445957\nvt 0.310845 0.451510\nvt 0.293406 0.439498\nvt 0.295340 0.449216\nvt 0.295542 0.440869\nvt 0.296562 0.445964\nvt 0.297871 0.455526\nvt 0.301235 0.451000\nvt 0.301020 0.444832\nvt 0.302169 0.454136\nvt 0.305837 0.448248\nvt 0.304507 0.439155\nvt 0.304519 0.431986\nvt 0.306424 0.441063\nvt 0.304229 0.435917\nvt 0.301660 0.426695\nvt 0.296694 0.426652\nvt 0.299564 0.436144\nvt 0.227923 0.432298\nvt 0.226357 0.441242\nvt 0.226903 0.440488\nvt 0.228733 0.450206\nvt 0.228983 0.430877\nvt 0.228344 0.450157\nvt 0.233412 0.452379\nvt 0.234311 0.453578\nvt 0.239911 0.450512\nvt 0.238347 0.449002\nvt 0.240205 0.441123\nvt 0.242116 0.440915\nvt 0.238696 0.433274\nvt 0.240634 0.431211\nvt 0.233362 0.429987\nvt 0.234964 0.427825\nvt 0.224857 0.448389\nvt 0.225783 0.444257\nvt 0.226833 0.436352\nvt 0.227061 0.455810\nvt 0.227937 0.452093\nvt 0.231526 0.457439\nvt 0.232602 0.453788\nvt 0.236910 0.450648\nvt 0.235529 0.454478\nvt 0.236610 0.448129\nvt 0.238254 0.443983\nvt 0.236561 0.437386\nvt 0.230120 0.439079\nvt 0.231620 0.434452\nvt 0.225680 0.440903\nvt 0.223186 0.445427\nvt 0.221832 0.455613\nvt 0.223295 0.452498\nvt 0.225360 0.461105\nvt 0.224352 0.443815\nvt 0.223752 0.465711\nvt 0.228436 0.467858\nvt 0.229909 0.462966\nvt 0.232972 0.463795\nvt 0.234138 0.459518\nvt 0.234659 0.455261\nvt 0.235484 0.452197\nvt 0.233210 0.446815\nvt 0.233853 0.444951\nvt 0.228213 0.443016\nvt 0.229033 0.441728\nvt 0.234830 0.441845\nvt 0.297880 0.424002\nvt 0.298130 0.430639\nvt 0.295123 0.433602\nvt 0.297741 0.439493\nvt 0.294662 0.426062\nvt 0.300002 0.435825\nvt 0.302717 0.434993\nvt 0.301383 0.438547\nvt 0.304811 0.430533\nvt 0.304168 0.433480\nvt 0.304743 0.424834\nvt 0.304046 0.427006\nvt 0.302991 0.420587\nvt 0.301626 0.422181\nvt 0.300086 0.420480\nvt 0.297635 0.422060\nvt 0.292911 0.431154\nvt 0.619748 0.438411\nvt 0.612440 0.446452\nvt 0.619863 0.424261\nvt 0.626809 0.456702\nvt 0.622481 0.469391\nvt 0.645673 0.462798\nvt 0.648552 0.477037\nvt 0.671549 0.470138\nvt 0.690897 0.494137\nvt 0.666480 0.457838\nvt 0.672439 0.443163\nvt 0.677603 0.449441\nvt 0.669348 0.427997\nvt 0.688164 0.425470\nvt 0.692690 0.455538\nvt 0.643387 0.418507\nvt 0.645233 0.418856\nvt 0.625203 0.421242\nvt 0.452041 0.455473\nvt 0.452575 0.455592\nvt 0.461005 0.429292\nvt 0.458921 0.479080\nvt 0.461890 0.481782\nvt 0.479208 0.486841\nvt 0.487362 0.490366\nvt 0.498066 0.478837\nvt 0.508938 0.481454\nvt 0.503350 0.455149\nvt 0.514556 0.455154\nvt 0.507400 0.428964\nvt 0.526390 0.426086\nvt 0.496856 0.431542\nvt 0.478133 0.423781\nvt 0.485618 0.420380\nvt 0.220765 0.455769\nvt 0.221085 0.456765\nvt 0.222513 0.446077\nvt 0.222476 0.465960\nvt 0.222942 0.467359\nvt 0.226880 0.468110\nvt 0.227614 0.469602\nvt 0.232193 0.465333\nvt 0.231238 0.464001\nvt 0.232972 0.455414\nvt 0.233966 0.456396\nvt 0.232588 0.447550\nvt 0.231715 0.446917\nvt 0.226990 0.443074\nvt 0.227590 0.443558\nvt 0.222162 0.445490\nvt 0.220441 0.447100\nvt 0.220637 0.452443\nvt 0.221988 0.443386\nvt 0.221479 0.454775\nvt 0.221998 0.461421\nvt 0.224637 0.456356\nvt 0.225795 0.463294\nvt 0.229657 0.459663\nvt 0.227932 0.453243\nvt 0.229444 0.446834\nvt 0.231322 0.452131\nvt 0.230363 0.444677\nvt 0.228743 0.440492\nvt 0.225322 0.437579\nvt 0.226265 0.441280\nvt 0.221683 0.439359\nvt 0.219300 0.433988\nvt 0.219954 0.440056\nvt 0.220988 0.433000\nvt 0.220207 0.440708\nvt 0.220882 0.447051\nvt 0.222684 0.442114\nvt 0.223618 0.448498\nvt 0.226452 0.445664\nvt 0.225197 0.439398\nvt 0.226252 0.433754\nvt 0.227718 0.439813\nvt 0.227068 0.434024\nvt 0.225586 0.428168\nvt 0.222906 0.425628\nvt 0.224105 0.431371\nvt 0.220147 0.427209\nvt 0.218579 0.431572\nvt 0.219456 0.438201\nvt 0.219348 0.424886\nvt 0.221792 0.439593\nvt 0.224146 0.436917\nvt 0.225112 0.431342\nvt 0.224459 0.425824\nvt 0.221929 0.423321\nvt 0.300560 0.429721\nvt 0.302176 0.434672\nvt 0.300368 0.423386\nvt 0.304550 0.433877\nvt 0.306385 0.429619\nvt 0.306332 0.424179\nvt 0.304816 0.420125\nvt 0.302295 0.420023\nvt 0.630493 0.430660\nvt 0.627166 0.444600\nvt 0.624329 0.439291\nvt 0.628994 0.454738\nvt 0.628494 0.424592\nvt 0.640930 0.463437\nvt 0.642619 0.459887\nvt 0.653515 0.460331\nvt 0.658069 0.460089\nvt 0.671010 0.454564\nvt 0.673294 0.445198\nvt 0.679659 0.445906\nvt 0.673126 0.435738\nvt 0.678950 0.433502\nvt 0.679724 0.436460\nvt 0.681594 0.445707\nvt 0.677738 0.453639\nvt 0.675379 0.457404\nvt 0.653010 0.429922\nvt 0.640371 0.426511\nvt 0.641421 0.421685\nvt 0.657350 0.424630\nvt 0.676737 0.443356\nvt 0.675468 0.432014\nvt 0.674168 0.455066\nvt 0.670682 0.427490\nvt 0.666120 0.431266\nvt 0.665078 0.426066\nvt 0.663817 0.444830\nvt 0.664191 0.458026\nvt 0.655524 0.445985\nvt 0.668561 0.460696\nvt 0.770765 0.468391\nvt 0.776691 0.450513\nvt 0.774260 0.454219\nvt 0.770024 0.432883\nvt 0.764614 0.431937\nvt 0.765786 0.476647\nvt 0.751242 0.426147\nvt 0.739181 0.424025\nvt 0.718436 0.431356\nvt 0.725926 0.450759\nvt 0.713302 0.454804\nvt 0.733315 0.471458\nvt 0.720266 0.478106\nvt 0.752638 0.475871\nvt 0.741322 0.484998\nvt 0.777578 0.448865\nvt 0.772025 0.432775\nvt 0.772621 0.465216\nvt 0.756216 0.426398\nvt 0.740068 0.425503\nvt 0.746622 0.432670\nvt 0.744765 0.432926\nvt 0.744091 0.427998\nvt 0.744548 0.437945\nvt 0.742045 0.433273\nvt 0.757328 0.472373\nvt 0.741627 0.472138\nvt 0.721859 0.444436\nvt 0.725168 0.446925\nvt 0.726585 0.431545\nvt 0.725072 0.456447\nvt 0.728133 0.461885\nvt 0.731788 0.429580\nvt 0.732601 0.459856\nvt 0.734626 0.466311\nvt 0.740209 0.460624\nvt 0.739523 0.455109\nvt 0.741109 0.442652\nvt 0.741377 0.445243\nvt 0.738814 0.430283\nvt 0.743987 0.430767\nvt 0.730505 0.427231\nvt 0.732665 0.425857\nvt 0.723508 0.431978\nvt 0.725959 0.443870\nvt 0.722632 0.444425\nvt 0.724133 0.433584\nvt 0.728239 0.453242\nvt 0.725458 0.454836\nvt 0.733837 0.455851\nvt 0.732305 0.457734\nvt 0.739242 0.452084\nvt 0.738827 0.453549\nvt 0.740564 0.442325\nvt 0.740387 0.442709\nvt 0.737414 0.432297\nvt 0.738108 0.432953\nvt 0.732386 0.430344\nvt 0.730476 0.429399\nvt 0.735709 0.435254\nvt 0.730838 0.439750\nvt 0.731851 0.431311\nvt 0.737202 0.442046\nvt 0.732695 0.447855\nvt 0.727197 0.434112\nvt 0.740834 0.443937\nvt 0.737247 0.450112\nvt 0.741631 0.446853\nvt 0.744303 0.441206\nvt 0.745150 0.434134\nvt 0.742707 0.438414\nvt 0.740722 0.430308\nvt 0.743582 0.427342\nvt 0.739897 0.425451\nvt 0.736078 0.428052\nvt 0.736519 0.428182\nvt 0.058770 0.436875\nvt 0.055526 0.438739\nvt 0.057510 0.426697\nvt 0.060306 0.447120\nvt 0.057510 0.450781\nvt 0.064945 0.450535\nvt 0.063522 0.454795\nvt 0.069602 0.450781\nvt 0.069621 0.447120\nvt 0.071186 0.436875\nvt 0.071639 0.438739\nvt 0.069602 0.426697\nvt 0.069621 0.426631\nvt 0.064945 0.423216\nvt 0.063522 0.422683\nvt 0.060306 0.426631\nvt 0.063711 0.430932\nvt 0.060698 0.437248\nvt 0.062030 0.446894\nvt 0.062030 0.427603\nvt 0.066047 0.450109\nvt 0.070088 0.446894\nvt 0.071440 0.437248\nvt 0.069431 0.430932\nvt 0.070088 0.427603\nvt 0.066047 0.424388\nvt 0.367638 0.430698\nvt 0.367024 0.425421\nvt 0.366747 0.435757\nvt 0.364799 0.423920\nvt 0.362507 0.425591\nvt 0.361657 0.429830\nvt 0.362292 0.434286\nvt 0.364473 0.436607\nvt 0.460080 0.453644\nvt 0.455530 0.469323\nvt 0.454708 0.461839\nvt 0.459566 0.482294\nvt 0.460111 0.441608\nvt 0.458432 0.490020\nvt 0.473669 0.495593\nvt 0.475328 0.489411\nvt 0.485564 0.490553\nvt 0.490588 0.482966\nvt 0.488912 0.473604\nvt 0.494970 0.462735\nvt 0.484744 0.456197\nvt 0.489730 0.442280\nvt 0.473504 0.449784\nvt 0.475090 0.435163\nvt 0.458609 0.431785\nvt 0.433344 0.465428\nvt 0.432688 0.465075\nvt 0.436444 0.461010\nvt 0.432809 0.473587\nvt 0.431950 0.472167\nvt 0.432942 0.470355\nvt 0.433482 0.464843\nvt 0.436745 0.462379\nvt 0.462033 0.502549\nvt 0.472427 0.504370\nvt 0.473034 0.502903\nvt 0.478426 0.499265\nvt 0.482198 0.498312\nvt 0.460859 0.500748\nvt 0.479127 0.490507\nvt 0.484360 0.483448\nvt 0.476955 0.478040\nvt 0.480568 0.468311\nvt 0.470332 0.476615\nvt 0.471493 0.462901\nvt 0.461334 0.475823\nvt 0.461340 0.462943\nvt 0.126675 0.426939\nvt 0.125239 0.437476\nvt 0.125905 0.447558\nvt 0.591534 0.482441\nvt 0.592185 0.479216\nvt 0.586654 0.465189\nvt 0.585994 0.459124\nvt 0.573850 0.460037\nvt 0.569889 0.452460\nvt 0.561793 0.466536\nvt 0.554423 0.459199\nvt 0.559099 0.484237\nvt 0.549866 0.479317\nvt 0.582493 0.456350\nvt 0.584017 0.444794\nvt 0.587839 0.465438\nvt 0.584203 0.451430\nvt 0.585630 0.479952\nvt 0.581344 0.433641\nvt 0.574728 0.430462\nvt 0.574899 0.447437\nvt 0.568557 0.434852\nvt 0.566209 0.452951\nvt 0.567218 0.446408\nvt 0.564396 0.467465\nvt 0.570060 0.457561\nvt 0.568400 0.481473\nvt 0.576500 0.460740\nvt 0.577322 0.485466\nvt 0.588361 0.500142\nvt 0.580240 0.436794\nvt 0.581813 0.437099\nvt 0.580687 0.446926\nvt 0.578492 0.428581\nvt 0.579676 0.427615\nvt 0.574416 0.426239\nvt 0.574561 0.424911\nvt 0.569802 0.428644\nvt 0.570633 0.429472\nvt 0.569779 0.437984\nvt 0.568741 0.438472\nvt 0.570944 0.447955\nvt 0.571542 0.446197\nvt 0.575614 0.448539\nvt 0.575998 0.450659\nvt 0.579378 0.445306\nvt 0.576705 0.444235\nvt 0.578656 0.438324\nvt 0.576273 0.435464\nvt 0.577345 0.431768\nvt 0.578024 0.445117\nvt 0.574312 0.429899\nvt 0.572562 0.435931\nvt 0.571501 0.432480\nvt 0.570865 0.439273\nvt 0.572993 0.444702\nvt 0.572179 0.445829\nvt 0.575217 0.447698\nvt 0.107195 0.428467\nvt 0.105366 0.431485\nvt 0.107081 0.434182\nvt 0.105932 0.435754\nvt 0.106105 0.427286\nvt 0.107873 0.436828\nvt 0.109737 0.434033\nvt 0.109877 0.435300\nvt 0.110611 0.431768\nvt 0.109842 0.428910\nvt 0.110028 0.428164\nvt 0.108090 0.426425\nvt 0.144977 0.440891\nvt 0.144073 0.447048\nvt 0.142380 0.441368\nvt 0.144181 0.447970\nvt 0.143884 0.434328\nvt 0.145829 0.452297\nvt 0.150018 0.452836\nvt 0.149196 0.449587\nvt 0.153930 0.449572\nvt 0.154052 0.446656\nvt 0.155008 0.444853\nvt 0.154800 0.443415\nvt 0.155479 0.439615\nvt 0.152971 0.438166\nvt 0.153669 0.433013\nvt 0.148809 0.437628\nvt 0.148741 0.431397\nvt 0.147868 0.446460\nvt 0.147579 0.450944\nvt 0.146008 0.451251\nvt 0.147654 0.455576\nvt 0.146508 0.445952\nvt 0.151897 0.454805\nvt 0.151103 0.455721\nvt 0.154385 0.452251\nvt 0.154198 0.452658\nvt 0.154705 0.447767\nvt 0.154701 0.447360\nvt 0.153219 0.444078\nvt 0.153005 0.443034\nvt 0.150333 0.443907\nvt 0.149543 0.442889\nvt 0.148958 0.434293\nvt 0.148263 0.443098\nvt 0.148521 0.439363\nvt 0.149989 0.437180\nvt 0.149517 0.446296\nvt 0.152135 0.437725\nvt 0.151959 0.446648\nvt 0.154101 0.444688\nvt 0.149047 0.454634\nvt 0.154070 0.436241\nvt 0.154424 0.433042\nvt 0.154400 0.440954\nvt 0.153143 0.437755\nvt 0.153395 0.430155\nvt 0.151210 0.429609\nvt 0.150656 0.437404\nvt 0.149276 0.431093\nvt 0.152498 0.427756\nvt 0.152076 0.430714\nvt 0.150435 0.431109\nvt 0.151260 0.433899\nvt 0.150826 0.428081\nvt 0.152742 0.433439\nvt 0.154420 0.434036\nvt 0.153160 0.434510\nvt 0.154930 0.433182\nvt 0.156450 0.429781\nvt 0.155339 0.430153\nvt 0.154511 0.427364\nvt 0.154093 0.426459\nvt 0.152591 0.426752\nvt 0.154651 0.427996\nvt 0.153355 0.430275\nvt 0.154771 0.431692\nvt 0.153914 0.432783\nvt 0.153743 0.427551\nvt 0.155370 0.433333\nvt 0.156749 0.431236\nvt 0.156773 0.432139\nvt 0.156025 0.432738\nvt 0.157160 0.429415\nvt 0.156625 0.427541\nvt 0.156593 0.426907\nvt 0.155136 0.426357\nvt 0.633927 0.438324\nvt 0.631725 0.451970\nvt 0.629236 0.449846\nvt 0.632335 0.463899\nvt 0.630506 0.458728\nvt 0.631974 0.435794\nvt 0.634694 0.465615\nvt 0.642692 0.470164\nvt 0.641429 0.468584\nvt 0.650532 0.465615\nvt 0.650479 0.463899\nvt 0.652802 0.451970\nvt 0.653116 0.449846\nvt 0.649735 0.438324\nvt 0.649699 0.435794\nvt 0.641645 0.433776\nvt 0.640669 0.431109\nvt 0.638463 0.441566\nvt 0.635288 0.447910\nvt 0.636890 0.436583\nvt 0.640186 0.450238\nvt 0.637653 0.459238\nvt 0.644559 0.453129\nvt 0.643754 0.463014\nvt 0.649674 0.459238\nvt 0.648776 0.450238\nvt 0.649970 0.441566\nvt 0.651371 0.447910\nvt 0.649007 0.436583\nvt 0.648265 0.432895\nvt 0.642798 0.432807\nvt 0.639590 0.432895\nvt 0.640288 0.436836\nvt 0.641639 0.444087\nvt 0.645099 0.446504\nvt 0.648436 0.444087\nvt 0.649379 0.436836\nvt 0.471087 0.493023\nvt 0.461851 0.491633\nvt 0.447801 0.465286\nvt 0.444381 0.472057\nvt 0.451141 0.472559\nvt 0.449228 0.485952\nvt 0.453655 0.462770\nvt 0.441503 0.488059\nvt 0.450132 0.495375\nvt 0.452035 0.496994\nvt 0.442786 0.490150\nvt 0.447565 0.480831\nvt 0.453545 0.486977\nvt 0.454496 0.473354\nvt 0.448818 0.467917\nvt 0.449083 0.464568\nvt 0.454640 0.464160\nvt 0.437892 0.468327\nvt 0.435175 0.474659\nvt 0.441406 0.480955\nvt 0.441070 0.463395\nvt 0.435172 0.480076\nvt 0.436116 0.482394\nvt 0.436816 0.435520\nvt 0.436861 0.432263\nvt 0.438510 0.434300\nvt 0.437823 0.430227\nvt 0.439298 0.430404\nvt 0.440439 0.432767\nvt 0.440224 0.436497\nvt 0.439205 0.439253\nvt 0.437791 0.438355\nvt 0.442428 0.462179\nvt 0.442684 0.461415\nvt 0.441036 0.475182\nvt 0.444769 0.467828\nvt 0.445338 0.474973\nvt 0.446573 0.463926\nvt 0.441215 0.482853\nvt 0.445895 0.457617\nvt 0.442199 0.452094\nvt 0.442553 0.457842\nvt 0.436952 0.457157\nvt 0.436900 0.460508\nvt 0.433972 0.456398\nvt 0.433569 0.462304\nvt 0.433664 0.464073\nvt 0.433170 0.470619\nvt 0.436707 0.472132\nvt 0.436456 0.479435\nvt 0.440517 0.453001\nvt 0.442842 0.448171\nvt 0.443860 0.457825\nvt 0.444781 0.449364\nvt 0.440821 0.463931\nvt 0.443534 0.441478\nvt 0.441234 0.437873\nvt 0.441775 0.444806\nvt 0.438043 0.437554\nvt 0.437584 0.444402\nvt 0.436133 0.440721\nvt 0.435081 0.448405\nvt 0.435934 0.445750\nvt 0.434829 0.454763\nvt 0.437048 0.451581\nvt 0.437820 0.451019\nvt 0.437302 0.461424\nvt 0.439697 0.442769\nvt 0.441268 0.438758\nvt 0.441934 0.442075\nvt 0.442432 0.436272\nvt 0.440090 0.446295\nvt 0.441639 0.433294\nvt 0.439967 0.430445\nvt 0.440548 0.433191\nvt 0.437981 0.432969\nvt 0.436438 0.432942\nvt 0.436464 0.435741\nvt 0.436356 0.437038\nvt 0.436330 0.440097\nvt 0.437753 0.441255\nvt 0.437887 0.444625\nvt 0.440926 0.437441\nvt 0.441253 0.432089\nvt 0.439463 0.441382\nvt 0.439672 0.429318\nvt 0.437575 0.429197\nvt 0.437734 0.430299\nvt 0.436367 0.431796\nvt 0.436302 0.435806\nvt 0.437630 0.439919\nvt 0.741973 0.429336\nvt 0.739252 0.433572\nvt 0.742332 0.437152\nvt 0.740425 0.439186\nvt 0.739886 0.427727\nvt 0.743261 0.440749\nvt 0.745975 0.436679\nvt 0.745954 0.438492\nvt 0.746609 0.432646\nvt 0.745607 0.428862\nvt 0.745393 0.427033\nvt 0.742527 0.425470\nvt 0.884599 0.496573\nvt 0.358264 0.428924\nvt 0.532070 0.672062\nvt 0.099233 0.755781\nvt 0.683775 0.808948\nvt 0.104309 0.816718\nvt 0.183113 0.461527\nvt 0.144731 0.446805\nvt 0.145430 0.424001\nvt 0.737953 0.430640\nvn -0.000300 -0.000400 -1.000000\nvn 0.000500 0.254400 -0.967100\nvn -0.654500 0.281200 -0.701800\nvn 0.705800 0.226900 -0.671100\nvn 0.095700 0.595700 -0.797500\nvn 0.026500 0.531900 -0.846400\nvn -0.069300 0.704700 -0.706200\nvn 0.006600 0.005600 1.000000\nvn -0.007400 0.158000 0.987400\nvn 0.704800 0.213300 0.676500\nvn -0.592300 0.249400 0.766200\nvn -0.146600 0.141200 0.979100\nvn -0.402400 0.345300 0.847900\nvn 0.062200 0.565700 0.822300\nvn 1.000000 0.000300 0.000000\nvn 0.986800 0.161700 0.004400\nvn 0.873600 0.472500 0.116200\nvn 0.940000 0.339000 -0.037300\nvn 0.932600 0.352800 -0.075800\nvn -0.999600 0.026500 0.005200\nvn -0.918200 0.396000 0.012600\nvn -0.756300 0.624600 -0.194600\nvn -0.858400 0.507100 -0.077200\nvn -0.610100 0.756500 0.235600\nvn 0.107700 0.551100 -0.827500\nvn -0.033900 -0.057800 -0.997800\nvn 0.089700 -0.066700 -0.993700\nvn -0.020800 -0.637500 -0.770200\nvn -0.263800 -0.551200 -0.791500\nvn -0.345400 -0.057300 -0.936700\nvn -0.272900 0.453900 -0.848200\nvn -0.039400 0.570000 -0.820700\nvn 0.138500 0.107500 0.984500\nvn 0.928400 0.042800 0.369100\nvn 0.441400 0.876400 0.192700\nvn 0.544400 -0.791700 0.277300\nvn 0.790700 -0.059500 -0.609300\nvn -0.204400 0.447900 0.870400\nvn 0.181100 0.108900 0.977400\nvn -0.066600 0.072400 0.995100\nvn -0.085300 -0.098800 0.991400\nvn -0.547400 0.026800 0.836400\nvn -0.754500 0.633400 -0.172000\nvn -0.936200 0.006900 -0.351400\nvn -0.990000 -0.009800 0.140700\nvn -0.749400 -0.597200 -0.286000\nvn -0.738000 -0.674300 -0.026400\nvn -0.938100 -0.057800 -0.341600\nvn -0.551100 0.811100 -0.195700\nvn -0.693000 0.704500 0.153100\nvn 0.049900 0.997500 -0.050500\nvn -0.001700 0.997100 -0.075700\nvn -0.000100 1.000000 0.000100\nvn -0.049300 0.997600 -0.048900\nvn -0.076500 0.997100 0.000400\nvn -0.049000 0.997600 0.049300\nvn 0.000400 0.997100 0.076500\nvn 0.049300 0.997600 0.048900\nvn 0.079100 0.996900 0.004900\nvn -0.129200 -0.983100 -0.129800\nvn -0.049000 -0.983700 -0.172700\nvn -0.009600 -0.999900 -0.009400\nvn -0.000300 -0.987100 -0.160100\nvn 0.000000 -1.000000 -0.001300\nvn -0.001900 -1.000000 0.001400\nvn -0.166000 -0.986100 0.001400\nvn -0.183600 -0.982200 -0.038800\nvn -0.707100 0.000000 -0.707100\nvn 0.000000 0.000000 -1.000000\nvn -0.004300 -0.008000 -1.000000\nvn 0.707100 -0.000000 -0.707100\nvn 0.700900 -0.009500 -0.713200\nvn -0.708600 0.012700 -0.705500\nvn -0.713200 -0.007400 -0.700900\nvn 1.000000 0.000000 0.000000\nvn 1.000000 0.000100 0.000100\nvn 0.707100 0.000300 0.707100\nvn 0.720900 0.001000 0.693100\nvn -0.000000 0.000000 1.000000\nvn 0.000800 0.001300 1.000000\nvn -0.707100 0.002600 0.707100\nvn -0.693200 0.006200 0.720800\nvn -0.700500 0.009300 0.713600\nvn -1.000000 0.000000 -0.000000\nvn -1.000000 -0.002400 0.004800\nvn 0.000100 0.000000 -1.000000\nvn -0.003900 -0.007600 -1.000000\nvn -0.713600 -0.009400 -0.700500\nvn 1.000000 0.000000 0.000100\nvn 0.707100 0.000000 0.707200\nvn 0.693800 -0.000000 -0.720200\nvn -0.707100 0.000000 0.707100\nvn -0.700500 0.009400 0.713600\nvn -1.000000 0.000000 -0.000100\nvn -1.000000 -0.007600 0.003900\nvn -0.706900 0.000000 -0.707300\nvn 0.000300 0.000000 -1.000000\nvn -0.003200 -0.007100 -1.000000\nvn 0.707200 -0.000000 -0.707000\nvn 1.000000 0.000000 0.000300\nvn 0.707000 -0.000000 0.707200\nvn -0.000300 0.000000 1.000000\nvn -0.000100 -0.000000 1.000000\nvn -0.707200 -0.000000 0.707000\nvn -1.000000 0.000000 -0.000200\nvn -1.000000 -0.007100 0.003200\nvn 0.000500 0.993300 0.115500\nvn -0.060400 0.996300 0.060800\nvn 0.707300 0.000000 -0.706900\nvn -0.707000 0.000000 -0.707200\nvn -0.115500 0.993300 0.000500\nvn -0.060800 0.996300 -0.060400\nvn 0.706900 0.000000 0.707300\nvn -0.001400 0.993300 -0.115500\nvn 0.062900 0.996000 -0.063300\nvn -0.707300 0.000000 0.706900\nvn 0.115500 0.993300 0.000200\nvn 0.060800 0.996300 0.060400\nvn -0.044500 -0.996600 -0.069500\nvn -0.119300 -0.909800 -0.397500\nvn -0.054700 -0.979000 -0.196200\nvn -0.331400 -0.682400 0.651600\nvn -0.032500 -0.987000 0.157400\nvn -0.132100 -0.982700 0.129600\nvn -0.098600 -0.992800 0.067800\nvn -0.494900 -0.861200 0.115700\nvn -0.357600 -0.932800 0.043900\nvn 0.523200 -0.852200 0.007400\nvn -0.064500 -0.997900 -0.003100\nvn -0.186500 -0.980400 0.063700\nvn 0.106600 -0.985200 0.133900\nvn -0.000100 -0.999100 0.042800\nvn 0.030100 -0.986200 0.162700\nvn -0.022600 -0.976100 -0.216000\nvn -0.034300 -0.867200 -0.496800\nvn 0.000300 -0.999300 0.037500\nvn -0.000100 -0.985800 0.167700\nvn 0.064400 -0.980300 0.186700\nvn 0.099700 -0.986400 -0.130400\nvn 0.010800 -0.988400 0.151700\nvn 0.105200 -0.993000 -0.053700\nvn -0.135300 -0.986100 0.096400\nvn -0.522700 -0.852200 0.021600\nvn 0.078700 -0.996500 0.027400\nvn 0.116400 -0.991600 -0.056700\nvn -0.990400 0.037300 -0.133200\nvn -0.416700 0.045500 -0.907900\nvn -0.292700 -0.804600 -0.516600\nvn -0.775100 0.630200 -0.044600\nvn -0.225500 0.841800 -0.490500\nvn 0.472000 0.630100 -0.616600\nvn 0.544400 0.037000 -0.838000\nvn 0.146100 0.634200 0.759300\nvn 0.343800 0.935000 0.087100\nvn -0.172000 0.983000 -0.064100\nvn 0.021900 0.643900 -0.764800\nvn 0.015600 0.653500 -0.756700\nvn -0.237600 0.969400 -0.061600\nvn -0.140500 0.744700 0.652400\nvn 0.008800 -0.999900 -0.009400\nvn 0.098200 -0.995200 0.003500\nvn 0.001400 -1.000000 0.001300\nvn 0.035600 -0.983900 -0.175200\nvn -0.062500 -0.800800 0.595600\nvn -0.084300 -0.954100 0.287300\nvn -0.036300 -0.998700 0.034800\nvn -0.050600 -0.982900 0.177100\nvn 0.193300 -0.655700 -0.729900\nvn 0.326700 -0.056900 -0.943400\nvn 0.954500 -0.053800 -0.293400\nvn 0.255200 0.578100 -0.775000\nvn 0.611300 0.766600 -0.196500\nvn 0.624100 0.615500 0.481300\nvn 0.800100 0.001600 0.599800\nvn 0.589700 -0.619500 0.518100\nvn 0.575200 -0.788100 -0.219100\nvn -0.285000 -0.907300 0.309200\nvn -0.625100 -0.370200 0.687200\nvn -0.379800 -0.380700 0.843100\nvn -0.620600 0.366100 0.693400\nvn -0.382100 0.370700 0.846500\nvn -0.186800 -0.349700 0.918100\nvn -0.057100 -0.902000 0.427900\nvn -0.176900 -0.920500 0.348400\nvn -0.009300 -0.999900 0.009300\nvn 0.000000 -1.000000 0.002400\nvn 0.000400 -1.000000 0.000800\nvn -0.288400 -0.955300 -0.064700\nvn 0.082900 -0.996500 -0.010500\nvn -0.372200 -0.824500 -0.426300\nvn 0.214400 -0.976300 0.030200\nvn 0.117000 -0.992400 0.037100\nvn 0.058400 -0.998200 -0.011400\nvn 0.008800 -0.999900 0.009200\nvn 0.551300 -0.650400 -0.522600\nvn 0.867200 -0.002400 -0.497900\nvn 0.834700 -0.051500 -0.548300\nvn 0.632300 0.636000 -0.442400\nvn 0.774900 -0.070900 -0.628100\nvn 0.519900 -0.660600 -0.541600\nvn 0.657000 0.676900 -0.331900\nvn 0.008900 0.999700 0.021900\nvn -0.030400 0.978300 -0.204800\nvn -0.692500 0.683900 0.229600\nvn -0.744000 0.661700 -0.093400\nvn -0.641100 0.541000 -0.544300\nvn -0.980400 0.005500 0.196800\nvn -0.993600 -0.002900 -0.113000\nvn -0.768400 -0.637300 -0.058700\nvn -0.711300 -0.646200 -0.276600\nvn -0.745700 -0.027600 -0.665700\nvn -0.117300 -0.930700 -0.346400\nvn -0.069300 -0.939700 -0.334800\nvn 0.876700 0.000500 -0.481000\nvn 0.821700 -0.000200 -0.569900\nvn 0.546000 -0.663400 -0.511600\nvn 0.518700 0.639000 -0.568000\nvn 0.597000 0.677600 -0.429500\nvn -0.163600 0.918400 -0.360100\nvn -0.038500 0.996100 -0.080000\nvn -0.695800 0.680700 0.229100\nvn -0.744900 0.666600 -0.027200\nvn -0.944700 -0.004000 0.327900\nvn -0.938200 0.001900 0.346000\nvn -0.730900 -0.668900 0.135500\nvn 0.094500 -0.972700 0.212000\nvn -0.088300 -0.975100 -0.203300\nvn 0.718700 -0.657300 -0.226700\nvn 0.912400 0.001800 -0.409300\nvn 0.931500 0.003200 -0.363700\nvn 0.794700 -0.597800 0.105200\nvn 0.507200 0.628400 -0.589800\nvn 0.477800 0.606000 -0.636000\nvn -0.200500 0.873000 -0.444700\nvn -0.238600 0.813100 -0.531000\nvn -0.802000 0.572800 -0.169300\nvn -0.803600 0.592800 -0.052300\nvn -0.955800 -0.005300 0.294000\nvn -0.953500 -0.007700 0.301300\nvn -0.519100 -0.627300 0.580500\nvn -0.636100 -0.608300 0.474800\nvn -0.516200 -0.663100 0.542000\nvn 0.156600 -0.924000 0.348800\nvn 0.222600 -0.846500 0.483500\nvn 0.759700 -0.650200 -0.007000\nvn -0.593300 0.558700 0.579500\nvn -0.482200 0.010100 0.876000\nvn -0.817400 -0.011200 0.576000\nvn -0.539500 -0.689600 0.483200\nvn -0.988100 -0.036800 -0.149100\nvn -0.743100 0.585100 0.324700\nvn -0.219800 -0.687500 0.692100\nvn -0.052700 -0.992400 0.111600\nvn -0.014100 -0.999400 0.031200\nvn 0.332100 -0.840700 -0.427700\nvn -0.080400 -0.741900 -0.665600\nvn 0.411900 -0.078800 -0.907800\nvn -0.213200 -0.053000 -0.975600\nvn -0.050700 0.758800 -0.649400\nvn -0.376000 0.610800 -0.696800\nvn -0.480700 0.876900 0.003300\nvn -0.547400 0.828200 -0.120000\nvn 0.168200 0.855600 0.489500\nvn 0.677700 0.029200 0.734700\nvn -0.120600 0.035300 0.992100\nvn 0.046100 -0.712300 0.700400\nvn -0.398100 0.620700 0.675500\nvn 0.365700 -0.880800 0.300900\nvn -0.124400 -0.992100 -0.013000\nvn -0.063400 -0.988400 0.138200\nvn -0.068000 -0.997500 -0.016800\nvn -0.284200 -0.704800 -0.650000\nvn -0.127200 -0.990900 0.044300\nvn -0.177300 -0.982300 0.060300\nvn 0.144600 -0.670900 0.727300\nvn 0.129600 -0.669500 0.731500\nvn -0.171000 -0.983600 0.057700\nvn -0.301700 -0.713900 -0.631900\nvn -0.288100 0.942600 0.168800\nvn -0.467600 0.869600 0.158900\nvn -0.350500 0.901100 -0.255200\nvn 0.972800 -0.006500 0.231600\nvn 0.930500 -0.055800 -0.362100\nvn 0.728400 -0.683400 0.049800\nvn 0.780200 0.618600 -0.092500\nvn 0.557400 0.613400 -0.559500\nvn 0.005100 0.947100 -0.320800\nvn -0.161800 0.870600 -0.464700\nvn -0.729000 0.578600 -0.365700\nvn -0.625200 0.779900 -0.029300\nvn -0.984700 0.016400 0.173400\nvn -0.976400 -0.017900 -0.215200\nvn -0.751300 -0.659200 -0.029900\nvn -0.547100 -0.794500 -0.263600\nvn -0.798600 -0.033200 -0.600900\nvn -0.063100 -0.996100 0.061500\nvn -0.106100 -0.984800 0.137400\nvn 0.617900 -0.705300 0.347500\nvn 0.888000 -0.439600 -0.134800\nvn 0.999900 -0.006900 -0.009400\nvn 0.921500 -0.006500 0.388300\nvn 0.891200 0.447500 -0.074200\nvn 0.798100 0.596800 0.082700\nvn 0.741900 -0.652000 0.156700\nvn 0.691800 0.621600 -0.367500\nvn 0.270000 0.930900 -0.245900\nvn -0.054700 0.980300 -0.189900\nvn -0.921100 -0.010500 -0.389200\nvn -0.641500 -0.022000 -0.766800\nvn -0.534600 0.689500 -0.488600\nvn -0.595100 -0.675700 -0.435000\nvn -0.439200 -0.699600 -0.563700\nvn -0.106500 0.006300 -0.994300\nvn 0.690400 -0.597800 -0.407500\nvn 0.250500 -0.941900 -0.223900\nvn 0.011200 -0.978700 -0.205200\nvn 0.418100 0.027300 0.908000\nvn 0.408400 0.687800 0.600100\nvn 0.340200 0.038300 0.939600\nvn 0.310700 -0.680900 0.663200\nvn 0.212400 0.974300 -0.074600\nvn -0.001100 0.652400 -0.757900\nvn -0.048900 0.656700 -0.752600\nvn 0.218600 0.973200 -0.072100\nvn 0.371100 0.679300 0.633200\nvn -0.163400 -0.061300 -0.984700\nvn -0.104000 -0.708300 -0.698200\nvn -0.256800 -0.045800 -0.965400\nvn 0.063700 -0.997900 -0.010800\nvn -0.005300 0.032600 0.999500\nvn -0.700900 0.018400 0.713000\nvn -0.457200 0.780700 0.426000\nvn -0.433400 -0.774700 0.460400\nvn -0.599200 -0.794000 0.102600\nvn 0.074500 -0.996000 -0.049100\nvn 0.035800 -0.997400 -0.063200\nvn 0.539700 -0.722900 -0.431300\nvn 0.540000 -0.809200 -0.231600\nvn 0.971900 -0.004900 -0.235500\nvn 0.809300 -0.037000 -0.586200\nvn 0.523800 0.755300 -0.393800\nvn 0.774500 -0.027500 -0.632000\nvn -0.040000 0.998400 0.040100\nvn -0.061000 0.998100 0.003300\nvn -0.745700 0.018100 0.666000\nvn -0.648000 0.035500 0.760800\nvn -0.368700 0.733200 0.571400\nvn -0.636800 -0.707500 0.306400\nvn -0.455300 -0.686300 0.567200\nvn -0.113700 -0.550800 0.826800\nvn -0.105500 0.039800 0.993600\nvn -0.086600 0.612500 0.785700\nvn -0.092300 -0.971900 -0.216600\nvn -0.091700 -0.992200 -0.084300\nvn -0.026900 -0.972400 -0.231700\nvn -0.928900 0.024500 0.369400\nvn -0.709400 -0.030700 -0.704100\nvn -0.421400 -0.883700 -0.203700\nvn -0.316800 0.896900 -0.308600\nvn 0.344500 -0.049600 -0.937500\nvn -0.785900 0.611600 -0.090500\nvn -0.233100 0.966800 -0.104700\nvn -0.084300 0.995100 -0.051900\nvn 0.530100 0.847200 -0.034200\nvn -0.231400 0.956200 0.179000\nvn 0.305100 -0.035900 -0.951600\nvn 0.538400 -0.036100 -0.841900\nvn 0.525600 0.656200 -0.541500\nvn 0.559300 -0.607500 -0.564100\nvn 0.602100 -0.595900 -0.531400\nvn 0.997800 -0.006900 -0.066500\nvn 0.615400 -0.773500 0.151300\nvn 0.373200 -0.925500 0.064500\nvn 0.379200 -0.574900 0.725100\nvn 0.409900 -0.483700 0.773300\nvn 0.021100 0.101600 0.994600\nvn 0.166900 0.092200 0.981600\nvn 0.262800 0.656500 0.707100\nvn 0.735500 0.082700 0.672400\nvn -0.255600 0.673600 0.693500\nvn 0.319500 0.945200 0.067800\nvn 0.598300 0.550200 0.582500\nvn 0.531200 0.669300 0.519400\nvn 0.971300 0.171900 0.164600\nvn 0.976400 0.196500 -0.089400\nvn 0.717400 -0.386300 -0.579800\nvn 0.671500 -0.427700 -0.605200\nvn 0.942800 0.252400 -0.217800\nvn 0.736400 0.519200 0.433700\nvn 0.823200 -0.514800 -0.239300\nvn 0.138000 -0.882200 -0.450300\nvn -0.043500 -0.631400 -0.774300\nvn -0.583500 -0.673400 -0.453900\nvn -0.686600 -0.366900 -0.627600\nvn -0.761200 -0.566800 -0.315100\nvn -0.215000 -0.799700 -0.560600\nvn -0.974400 -0.161100 -0.157100\nvn -0.988300 -0.151900 -0.012500\nvn -0.852700 0.383300 0.355000\nvn -0.593200 0.229200 0.771800\nvn -0.952400 -0.213300 0.217900\nvn -0.275200 0.713100 0.644800\nvn 0.103100 0.521200 0.847200\nvn 0.171500 0.724800 0.667300\nvn -0.559100 0.263000 0.786300\nvn 0.534500 0.741100 0.406300\nvn 0.930200 0.067400 0.360800\nvn 0.937100 0.087000 0.338200\nvn 0.760200 -0.635700 0.133700\nvn 0.521100 0.722900 0.453700\nvn 0.725100 -0.666300 0.174200\nvn 0.016700 -0.996700 -0.079300\nvn 0.073000 -0.990700 -0.114600\nvn -0.637000 -0.746500 -0.192200\nvn -0.601000 -0.773700 -0.200600\nvn -0.984200 -0.096700 -0.148300\nvn -0.979900 -0.145300 -0.136600\nvn -0.835700 0.543100 0.081200\nvn -0.793100 0.607300 0.045300\nvn -0.221500 0.943200 0.247500\nvn -0.281400 0.906400 0.314900\nvn 0.341000 0.022700 0.939800\nvn 0.135100 -0.683200 0.717700\nvn 0.185100 0.723300 0.665300\nvn -0.127700 -0.983700 0.126400\nvn -0.272900 -0.943300 -0.189100\nvn -0.196400 -0.833500 -0.516400\nvn -0.707200 -0.066000 0.703900\nvn -0.625200 -0.712100 -0.319500\nvn -0.377500 -0.915000 -0.142200\nvn -0.378000 0.924500 -0.048600\nvn -0.617600 0.770400 -0.158300\nvn -0.064200 0.116900 -0.991100\nvn -0.034200 0.986900 0.157900\nvn 0.034800 0.994800 0.096000\nvn -0.009200 0.840100 -0.542400\nvn 0.680400 -0.716800 -0.152400\nvn 0.973700 -0.026500 -0.226400\nvn 1.000000 -0.000500 -0.005900\nvn 0.670800 0.725200 0.155600\nvn 0.718800 -0.680500 -0.142100\nvn 0.682500 0.695100 -0.226000\nvn -0.110700 0.983900 -0.140300\nvn -0.094600 0.963600 0.250100\nvn -0.760500 0.635100 0.135500\nvn -0.994500 0.013300 0.103900\nvn -0.999500 0.002200 -0.032200\nvn -0.746800 -0.659700 0.084300\nvn -0.717900 -0.662100 -0.214900\nvn -0.081400 -0.996400 -0.022500\nvn -0.027300 -0.979400 -0.199900\nvn 0.760500 -0.039000 -0.648200\nvn 0.859200 -0.035400 -0.510400\nvn 0.635000 -0.731200 -0.249300\nvn 0.409600 0.653500 -0.636500\nvn 0.499200 0.619000 -0.606300\nvn -0.258200 0.922200 -0.288000\nvn -0.215200 0.879400 -0.424700\nvn -0.778100 0.614700 -0.129100\nvn -0.687400 0.724000 0.056900\nvn -0.775800 0.623800 0.095000\nvn -0.916400 0.020600 0.399800\nvn -0.983200 0.017800 0.181900\nvn -0.699200 -0.652700 0.291600\nvn -0.583300 -0.672600 0.455400\nvn -0.039100 -0.991400 0.124600\nvn -0.045800 -0.992000 0.117600\nvn 0.565700 -0.736800 -0.370400\nvn -0.820000 -0.013900 -0.572200\nvn -0.776500 -0.031200 -0.629300\nvn -0.679800 -0.662400 -0.314900\nvn -0.456700 0.650000 -0.607400\nvn -0.622300 0.622800 -0.474100\nvn 0.185000 0.952800 -0.240800\nvn -0.147100 0.986900 0.066100\nvn 0.633300 0.688100 0.354300\nvn 0.239400 0.698700 0.674200\nvn 0.565400 0.137800 0.813200\nvn 0.453400 0.130900 0.881600\nvn 0.125100 -0.401300 0.907400\nvn 0.175000 -0.595000 0.784400\nvn -0.441300 -0.754600 0.485500\nvn -0.239000 -0.948300 0.208800\nvn -0.783800 -0.606300 -0.134200\nvn -0.795800 -0.605100 0.024500\nvn -0.962200 0.004100 -0.272200\nvn -0.929900 0.034400 -0.366100\nvn -0.526000 0.666200 -0.528700\nvn -0.891200 -0.432300 0.137500\nvn -0.622800 0.723200 -0.298500\nvn 0.002200 0.999900 0.015200\nvn 0.179700 0.934700 -0.306800\nvn 0.611600 0.688900 0.389000\nvn 0.710000 0.659000 0.248300\nvn 0.779300 0.045800 0.625000\nvn 0.654900 0.104500 0.748500\nvn 0.432200 -0.558300 0.708200\nvn 0.119400 -0.307100 0.944100\nvn -0.252500 -0.865100 0.433400\nvn -0.478900 -0.562000 0.674400\nvn -0.177800 0.690400 0.701200\nvn -0.131500 0.044500 0.990300\nvn 0.076900 0.043900 0.996100\nvn -0.126300 -0.630200 0.766100\nvn 0.154200 -0.630400 0.760800\nvn 0.543500 -0.510900 0.666000\nvn 0.642700 0.042600 0.764900\nvn 0.539100 0.583600 0.607200\nvn 0.143500 0.725100 0.673500\nvn -0.292400 -0.949200 -0.115900\nvn -0.088900 -0.996000 -0.013200\nvn -0.555600 -0.699300 -0.449800\nvn -0.664700 -0.010800 -0.747100\nvn -0.564500 0.734800 -0.376100\nvn -0.284400 0.951700 0.115800\nvn -0.110000 0.987100 0.116600\nvn 0.930200 0.011200 0.366800\nvn 0.652300 -0.673300 0.348200\nvn 0.646800 0.701400 0.299500\nvn 0.093500 -0.984300 0.149400\nvn -0.127800 -0.990700 0.046500\nvn -0.399900 -0.868900 -0.291800\nvn -0.551300 -0.607300 0.572100\nvn -0.752600 -0.023300 0.658100\nvn -0.983000 -0.061500 -0.172800\nvn -0.600600 0.561900 0.568800\nvn -0.685100 0.723000 -0.089300\nvn -0.393700 0.567100 -0.723500\nvn -0.462900 -0.020400 -0.886200\nvn -0.337100 -0.611400 -0.715900\nvn -0.629000 -0.775600 -0.053200\nvn 0.070700 0.992000 0.105000\nvn -0.213500 0.976900 0.008500\nvn -0.822900 0.043700 -0.566500\nvn -0.951500 -0.038800 -0.305200\nvn -0.650800 -0.710700 -0.267200\nvn -0.751400 0.655800 -0.073000\nvn -0.146800 0.928200 0.341800\nvn -0.121100 0.949200 0.290400\nvn 0.536100 0.671000 0.512200\nvn 0.960300 0.119600 0.252000\nvn 0.855500 0.020700 0.517400\nvn 0.655400 -0.689200 0.309100\nvn 0.081100 -0.978800 -0.188100\nvn 0.003700 -0.999800 -0.017800\nvn 0.103400 -0.825500 0.554800\nvn 0.349600 -0.289700 0.891000\nvn -0.487400 -0.002900 0.873200\nvn 0.518000 0.356500 0.777600\nvn -0.008200 0.737400 0.675400\nvn -0.519200 0.827800 0.212500\nvn -0.942400 0.300500 0.147200\nvn -0.933600 -0.358200 -0.014000\nvn -0.499500 -0.729000 0.468100\nvn -0.221800 0.842700 0.490700\nvn -0.161100 0.868100 0.469500\nvn -0.294100 0.872900 0.389300\nvn 0.435400 0.807200 0.398600\nvn -0.585700 0.802900 -0.110500\nvn -0.713100 0.352600 0.605900\nvn -0.779800 -0.141900 0.609800\nvn -0.259300 -0.141000 0.955400\nvn -0.598300 -0.673900 0.433600\nvn -0.138400 -0.757500 0.638000\nvn 0.371600 -0.567800 0.734500\nvn 0.399900 -0.055100 0.914900\nvn 0.210800 0.414500 0.885300\nvn -0.268200 0.478600 0.836100\nvn 0.060800 -0.989000 -0.134800\nvn 0.174100 -0.970000 -0.169800\nvn 0.012500 -0.971600 -0.236100\nvn -0.409200 -0.755600 -0.511500\nvn 0.689000 -0.723800 -0.036700\nvn 0.026200 0.292000 -0.956100\nvn -0.406700 0.225200 -0.885400\nvn -0.184700 -0.524300 -0.831300\nvn -0.340200 0.819200 -0.461700\nvn -0.187000 0.872100 -0.452200\nvn -0.434800 0.860000 0.267000\nvn -0.200900 0.891700 0.405700\nvn -0.192500 0.381400 0.904100\nvn -0.186400 0.396700 0.898800\nvn -0.473500 0.370600 0.799000\nvn -0.237300 -0.347700 0.907100\nvn -0.094200 -0.316300 0.944000\nvn 0.051900 -0.872900 0.485100\nvn 0.026300 -0.879200 0.475700\nvn 0.246400 -0.944800 -0.216000\nvn 0.071200 -0.954100 -0.291000\nvn 0.206700 -0.462300 -0.862300\nvn 0.961000 0.260800 -0.091500\nvn 0.587200 0.808400 0.040600\nvn 0.817100 -0.482600 -0.315300\nvn 0.104500 -0.937200 -0.332800\nvn -0.542300 -0.835200 0.090800\nvn -0.850600 -0.312600 0.422700\nvn -0.734800 0.364800 0.571900\nvn -0.196500 0.889500 0.412500\nvn 0.656100 0.737500 0.160000\nvn -0.068400 0.403800 -0.912300\nvn 0.098900 0.358000 -0.928500\nvn 0.448000 -0.337400 -0.827900\nvn -0.587700 0.706900 -0.393600\nvn -0.345300 0.779200 -0.523100\nvn -0.797300 0.498500 0.340300\nvn -0.656100 0.721500 0.221100\nvn -0.674900 0.196600 0.711300\nvn -0.648200 0.009800 0.761400\nvn -0.071000 -0.478000 0.875500\nvn -0.319800 -0.398100 0.859800\nvn 0.182500 -0.851800 0.491100\nvn 0.535700 -0.720200 0.440800\nvn 0.703100 -0.655800 -0.274800\nvn 0.498700 -0.854800 -0.143300\nvn -0.651600 0.332600 -0.681800\nvn -0.371900 0.392800 -0.841100\nvn 0.151600 -0.139700 -0.978500\nvn -0.660900 0.750500 0.002100\nvn -0.720200 0.679100 -0.142300\nvn 0.411500 -0.089700 -0.907000\nvn -0.287200 0.651500 0.702200\nvn -0.649900 0.466700 0.599900\nvn -0.237300 0.002600 0.971400\nvn 0.286900 0.154000 0.945500\nvn 0.581400 -0.373200 0.723000\nvn 0.381500 -0.448500 0.808300\nvn 0.755400 -0.624100 0.200000\nvn 0.651100 -0.757700 -0.044600\nvn 0.262400 -0.798900 -0.541200\nvn 0.614900 -0.589200 -0.524100\nvn -0.098000 -0.662100 -0.743000\nvn 0.033600 0.024000 -0.999100\nvn -0.291300 -0.076500 -0.953600\nvn -0.245700 0.653800 -0.715700\nvn -0.128200 -0.699900 -0.702600\nvn 0.018100 0.686100 -0.727200\nvn 0.006300 0.997500 0.070800\nvn -0.173100 0.984300 -0.033300\nvn -0.177600 0.668400 0.722300\nvn -0.004700 0.643100 0.765800\nvn -0.093800 0.034500 0.995000\nvn -0.118200 0.033100 0.992400\nvn -0.220700 -0.640000 0.736000\nvn -0.159300 -0.628400 0.761400\nvn -0.240800 -0.970300 0.021400\nvn -0.148800 -0.988900 0.001600\nvn 0.168400 0.034900 -0.985100\nvn 0.068700 0.060000 -0.995800\nvn -0.146000 -0.652000 -0.744000\nvn 0.366500 0.680700 -0.634300\nvn 0.208400 0.723700 -0.657900\nvn 0.358300 0.917800 0.171000\nvn 0.218500 0.964800 0.146600\nvn 0.101900 0.632700 0.767600\nvn 0.181900 0.594200 0.783400\nvn -0.076500 0.000200 0.997100\nvn -0.088000 0.002700 0.996100\nvn -0.268600 -0.651000 0.709900\nvn -0.303100 -0.952900 0.012300\nvn -0.306300 -0.951800 0.015400\nvn -0.098100 -0.671800 -0.734200\nvn 0.051700 -0.696100 -0.716100\nvn 0.268400 0.010900 -0.963200\nvn 0.261500 -0.003600 -0.965200\nvn 0.455900 0.646200 -0.612000\nvn -0.017900 -0.696300 -0.717600\nvn 0.409500 0.683600 -0.604200\nvn 0.328900 0.929100 0.169400\nvn 0.407900 0.894700 0.182100\nvn 0.120800 0.625800 0.770600\nvn 0.191300 0.595600 0.780200\nvn -0.092600 0.029700 0.995300\nvn -0.061500 0.009200 0.998100\nvn -0.220600 -0.651000 0.726300\nvn -0.254100 -0.654700 0.711900\nvn -0.176100 -0.983500 0.041100\nvn -0.255600 -0.966500 0.023200\nvn -0.263100 -0.658300 0.705300\nvn -0.736900 -0.446400 -0.507700\nvn -0.831700 0.334900 -0.442900\nvn -0.803000 0.263800 -0.534500\nvn -0.549200 0.835300 0.024200\nvn -0.601300 -0.477400 -0.640800\nvn -0.441300 0.896400 -0.040500\nvn 0.247500 0.880800 0.403600\nvn 0.042900 0.816100 0.576400\nvn 0.658800 0.386800 0.645300\nvn 0.480300 0.338200 0.809300\nvn 0.723500 -0.356600 0.591100\nvn 0.654500 -0.367200 0.660900\nvn 0.355800 -0.907800 0.221900\nvn 0.404400 -0.895200 0.187500\nvn -0.201000 -0.955800 -0.214400\nvn -0.092700 -0.943100 -0.319400\nvn -0.290200 -0.365500 -0.884400\nvn 0.293300 0.032200 0.955500\nvn 0.579500 0.039800 0.814000\nvn 0.239900 -0.736200 0.632900\nvn 0.394500 0.670000 0.628800\nvn 0.766100 0.438800 0.469700\nvn 0.372100 0.926300 0.059000\nvn 0.773500 0.633600 -0.014700\nvn 0.618600 0.525800 -0.583900\nvn 0.671300 0.519500 -0.528700\nvn -0.114000 0.803500 -0.584300\nvn -0.526400 -0.031800 -0.849700\nvn 0.292200 -0.025300 -0.956000\nvn 0.085800 -0.757700 -0.646900\nvn 0.463800 -0.817600 -0.341000\nvn 0.804900 -0.033900 -0.592500\nvn 0.021500 -0.997200 -0.071500\nvn -0.129800 -0.988500 -0.077400\nvn 0.124300 -0.764700 0.632300\nvn -0.949300 0.009200 0.314200\nvn -0.923200 0.015700 0.384100\nvn -0.661400 -0.702200 0.263400\nvn -0.704800 0.686800 0.177800\nvn -0.650800 0.627200 0.427800\nvn -0.001800 0.999900 0.010000\nvn 0.058900 0.903800 0.423800\nvn 0.720500 0.691900 -0.046800\nvn 0.716200 0.600000 0.356500\nvn 0.999700 0.020100 -0.010800\nvn 0.972200 0.024200 0.233000\nvn 0.701000 -0.681000 0.212000\nvn 0.499800 -0.770400 0.395800\nvn 0.721300 -0.687400 0.084400\nvn 0.076100 -0.952700 0.294200\nvn -0.004200 -0.999100 0.041800\nvn 0.076100 0.019200 -0.996900\nvn 0.177600 0.022200 -0.983900\nvn 0.083200 -0.680000 -0.728500\nvn -0.167500 0.670700 -0.722500\nvn 0.135500 0.708300 -0.692800\nvn -0.372200 0.927800 0.025900\nvn -0.007500 0.994300 0.106300\nvn -0.134700 0.658300 0.740600\nvn -0.410800 0.647200 0.642200\nvn -0.293600 0.032600 0.955400\nvn -0.165400 0.040500 0.985400\nvn -0.149400 -0.652300 0.743100\nvn -0.089800 -0.633200 0.768800\nvn 0.179000 -0.978200 0.105500\nvn -0.019200 -0.997300 0.070400\nvn 0.176500 -0.686900 -0.705000\nvn 0.078600 -0.005500 -0.996900\nvn 0.047900 0.002400 -0.998800\nvn 0.303200 -0.676400 -0.671200\nvn -0.326000 0.576500 -0.749200\nvn -0.287100 0.615900 -0.733600\nvn -0.590000 0.807200 -0.015300\nvn -0.517900 0.855400 -0.004700\nvn -0.518700 0.579900 0.628300\nvn -0.555100 0.549000 0.624900\nvn -0.293900 0.006300 0.955800\nvn -0.313400 0.018200 0.949500\nvn 0.032300 -0.635300 0.771600\nvn 0.120200 -0.599100 0.791600\nvn 0.423400 -0.893400 0.150100\nvn 0.330100 -0.935200 0.128600\nvn 0.390800 -0.645800 -0.655900\nvn 0.144900 0.019600 -0.989300\nvn 0.113500 0.008000 -0.993500\nvn 0.423400 -0.633600 -0.647500\nvn -0.160000 0.659800 -0.734200\nvn -0.305300 0.588600 -0.748500\nvn -0.401500 0.915500 0.024800\nvn -0.580800 0.813900 -0.013500\nvn -0.533700 0.556900 0.636400\nvn -0.364400 0.611600 0.702200\nvn -0.227400 0.034700 0.973200\nvn -0.254800 0.002000 0.967000\nvn 0.136600 -0.597000 0.790500\nvn 0.103400 -0.631900 0.768100\nvn 0.294800 -0.947200 0.125800\nvn 0.423800 -0.893800 0.146300\nvn 0.359000 -0.672000 -0.647700\nvn 0.045600 0.007100 -0.998900\nvn -0.157800 0.673300 -0.722300\nvn 0.053300 -0.708700 -0.703500\nvn -0.310400 0.949700 0.040700\nvn -0.378800 0.632800 0.675300\nvn -0.334800 0.008300 0.942200\nvn -0.220500 -0.651900 0.725500\nvn -0.037700 -0.997600 0.058700\nvn -0.867000 0.355800 -0.348800\nvn -0.415700 0.909500 -0.007400\nvn -0.844600 -0.424000 -0.327100\nvn 0.289600 0.883200 0.368800\nvn 0.689500 0.371600 0.621700\nvn 0.702600 -0.375000 0.604800\nvn 0.284500 -0.898100 0.335400\nvn -0.319700 -0.946200 -0.050500\nvn 0.341500 -0.676100 0.652900\nvn 0.218300 0.029900 0.975400\nvn 0.209200 0.011100 0.977800\nvn 0.093000 0.717000 0.690800\nvn 0.245500 -0.744100 0.621300\nvn -0.188700 0.976600 0.102700\nvn 0.004700 0.989500 0.144200\nvn 0.080800 0.889000 -0.450700\nvn -0.019600 0.998300 -0.055700\nvn 0.437300 0.601100 -0.668900\nvn 0.533500 0.192400 -0.823700\nvn -0.006100 0.182300 -0.983200\nvn 0.484100 -0.295800 -0.823500\nvn -0.012100 -0.478400 -0.878000\nvn -0.580000 -0.396300 -0.711700\nvn -0.654900 0.123200 -0.745600\nvn -0.558400 0.593500 -0.579700\nvn -0.061200 0.735300 -0.675000\nvn 0.411100 -0.712800 -0.568200\nvn 0.280700 -0.959000 -0.040100\nvn 0.171400 -0.985100 -0.013100\nvn 0.131900 -0.962300 -0.237800\nvn -0.874200 0.016800 -0.485400\nvn -0.620300 -0.608400 -0.495100\nvn -0.662500 0.628400 -0.407700\nvn 0.031200 -0.903700 -0.427100\nvn 0.584400 -0.483500 -0.651600\nvn -0.207000 -0.833200 -0.512700\nvn 0.729200 0.174100 -0.661800\nvn 0.459300 0.734200 -0.500000\nvn 0.408400 0.115800 -0.905400\nvn -0.038800 0.975000 -0.218900\nvn -0.661500 0.681000 -0.314200\nvn -0.966200 -0.016000 -0.257300\nvn -0.926000 -0.034000 -0.375900\nvn -0.678400 -0.715100 -0.168700\nvn -0.648300 -0.702900 -0.292700\nvn -0.834200 -0.039300 -0.550100\nvn -0.600400 0.598500 -0.530400\nvn -0.099100 -0.995000 -0.013500\nvn -0.002500 -0.997400 -0.071600\nvn 0.683300 -0.726600 -0.071900\nvn 0.892900 0.013700 0.450000\nvn 0.996600 -0.005300 -0.082000\nvn 0.577500 0.800400 0.160800\nvn 0.713500 0.638700 -0.288000\nvn -0.056500 0.972000 -0.228200\nvn 0.043200 0.878600 -0.475500\nvn -0.847500 -0.048700 -0.528600\nvn -0.543400 -0.704900 -0.455900\nvn -0.579000 0.598200 -0.554000\nvn -0.130900 -0.957000 -0.258700\nvn 0.052300 -0.993400 -0.102200\nvn -0.687100 -0.077400 -0.722400\nvn 0.274700 0.012800 -0.961500\nvn 0.120400 -0.867700 -0.482300\nvn 0.072700 0.880800 -0.467900\nvn 0.983500 0.091000 -0.156400\nvn -0.102100 0.917800 -0.383700\nvn 0.214800 0.953600 -0.210800\nvn 0.693100 0.094300 0.714700\nvn 0.637900 0.052100 0.768300\nvn 0.465900 -0.575600 0.672000\nvn 0.574400 0.720200 0.389100\nvn 0.633000 0.631400 0.447800\nvn 0.511400 -0.791800 0.334000\nvn 0.209900 0.948500 -0.237500\nvn 0.414300 0.891200 -0.184800\nvn -0.114800 0.545900 -0.829900\nvn -0.526100 0.538300 -0.658400\nvn -0.755900 -0.067500 -0.651200\nvn -0.547000 -0.054900 -0.835300\nvn -0.375500 -0.691500 -0.617200\nvn -0.030000 -0.687200 -0.725900\nvn 0.123100 -0.990400 -0.062800\nvn 0.049200 -0.998300 0.032100\nvn 0.608800 -0.596900 0.522500\nvn 0.972400 0.100800 0.210400\nvn 0.869200 0.100200 0.484100\nvn 0.701500 -0.635400 0.322700\nvn 0.668500 0.743700 -0.006100\nvn 0.602000 0.756900 0.254400\nvn 0.024400 0.961000 -0.275300\nvn 0.029200 0.983800 -0.176800\nvn -0.630900 0.621600 -0.464300\nvn -0.561400 0.623700 -0.544000\nvn -0.902200 -0.099900 -0.419700\nvn -0.790800 -0.099400 -0.603900\nvn -0.490100 -0.743300 -0.455300\nvn -0.619700 -0.759200 -0.199100\nvn 0.053000 -0.996500 0.064000\nvn 0.137400 -0.987500 -0.076700\nvn 0.987700 0.100600 0.119600\nvn 0.992900 0.099400 0.064900\nvn 0.734900 -0.646700 0.204100\nvn 0.669400 0.738300 -0.082200\nvn 0.676600 0.715400 -0.174400\nvn 0.767500 -0.582300 0.268200\nvn 0.007700 0.958600 -0.284800\nvn 0.034500 0.927900 -0.371100\nvn -0.656400 0.614000 -0.438400\nvn -0.705100 0.609700 -0.361900\nvn -0.941000 -0.101200 -0.323000\nvn -0.955000 -0.113900 -0.273900\nvn -0.654800 -0.754900 -0.036300\nvn -0.638100 -0.761500 -0.113900\nvn 0.043400 -0.994600 0.094000\nvn 0.026700 -0.985900 0.165000\nvn 0.719600 -0.659400 0.217700\nvn 0.881500 -0.000300 -0.472100\nvn 0.875700 -0.001000 -0.482900\nvn 0.680300 -0.684200 -0.262600\nvn 0.623500 0.681100 -0.383900\nvn 0.564700 0.663900 -0.490300\nvn -0.040700 0.995200 -0.088600\nvn -0.113800 0.961700 -0.249400\nvn -0.745600 0.660700 0.086700\nvn -0.695000 0.677600 0.240600\nvn -0.932400 -0.000300 0.361300\nvn -0.949900 -0.029000 0.311200\nvn -0.657500 -0.683400 0.317300\nvn -0.673500 -0.686100 0.275100\nvn -0.015800 -0.999300 -0.033700\nvn 0.034000 -0.996800 0.072100\nvn 0.633500 -0.695200 -0.339800\nvn 0.482600 -0.608800 -0.629700\nvn 0.849300 -0.008600 -0.527900\nvn 0.604300 0.679300 -0.416300\nvn 0.575400 -0.679000 -0.455900\nvn -0.043300 0.995000 -0.089600\nvn -0.709800 0.679000 0.187300\nvn -0.954200 -0.010200 0.299000\nvn -0.796600 -0.604300 -0.012000\nvn -0.727600 -0.672500 0.135300\nvn -0.070800 -0.984200 -0.162000\nvn 0.849600 0.027000 0.526800\nvn 0.618800 -0.674500 0.402500\nvn 0.467800 0.628000 0.621900\nvn -0.018000 -0.997400 0.069900\nvn -0.656200 -0.754600 -0.009500\nvn -0.994700 -0.098400 0.028500\nvn -0.802300 0.535200 0.264200\nvn -0.244400 0.859500 0.449000\nvn -0.465200 -0.774100 0.429300\nvn -0.988100 -0.153600 0.001100\nvn -0.931600 -0.025200 0.362600\nvn -0.767300 0.639800 0.043500\nvn -0.538200 -0.654600 0.530900\nvn -0.700900 0.637500 -0.319900\nvn -0.080400 0.925600 -0.370000\nvn -0.087800 0.964800 -0.247900\nvn 0.611600 0.678200 -0.407400\nvn 0.650400 0.697800 -0.300100\nvn 0.983200 0.069800 -0.168700\nvn 0.990300 0.052000 -0.128800\nvn 0.825100 -0.520400 0.220000\nvn 0.800500 -0.568300 0.190200\nvn 0.254100 -0.820200 0.512500\nvn 0.165600 -0.863100 0.477100\nvn -0.618800 -0.684200 0.386000\nvn -0.878500 -0.473900 0.061100\nvn -0.831600 -0.550000 0.077300\nvn -0.271400 -0.959700 -0.072700\nvn -0.949000 0.219400 0.226600\nvn -0.983900 0.178400 0.013900\nvn -0.942700 0.048900 -0.330000\nvn -0.740500 -0.551700 -0.383800\nvn -0.278500 -0.911600 -0.302300\nvn -0.053300 0.897200 0.438300\nvn 0.334500 0.892800 0.301900\nvn 0.044000 0.964200 -0.261600\nvn 0.702800 0.697900 0.137800\nvn 0.668200 0.703700 -0.241300\nvn -0.354600 0.919300 -0.170800\nvn 0.866300 0.221200 0.447800\nvn 0.997000 0.073500 -0.024600\nvn 0.757800 -0.244500 0.605000\nvn 0.819900 -0.420000 0.389000\nvn 0.483600 -0.299100 0.822600\nvn 0.359400 -0.706100 0.610100\nvn 0.287200 -0.335200 0.897300\nvn 0.075200 -0.808600 0.583500\nvn 0.548400 -0.663500 -0.509000\nvn 0.871100 -0.040200 -0.489500\nvn 0.524700 0.690000 -0.498600\nvn 0.319300 -0.081400 -0.944100\nvn 0.257800 -0.059600 -0.964300\nvn 0.145900 -0.707700 -0.691300\nvn 0.517400 -0.657900 -0.547300\nvn -0.027200 -0.999400 0.019300\nvn 0.587600 -0.785800 0.192800\nvn -0.078800 -0.630700 0.772000\nvn 0.413600 -0.469300 0.780200\nvn -0.019900 0.025600 0.999500\nvn 0.092100 0.105000 0.990200\nvn 0.733300 0.454100 -0.506100\nvn 0.429700 -0.123600 -0.894500\nvn 0.395400 -0.104200 -0.912600\nvn -0.132400 -0.632600 -0.763100\nvn 0.749700 0.430900 -0.502200\nvn -0.100800 -0.677900 -0.728200\nvn -0.440800 -0.891200 -0.107000\nvn -0.544100 -0.826400 -0.144700\nvn -0.499600 -0.577900 0.645300\nvn -0.534000 -0.565300 0.628700\nvn -0.183700 0.049600 0.981700\nvn -0.131700 0.024300 0.991000\nvn 0.284800 0.549700 0.785400\nvn 0.354700 0.496300 0.792400\nvn 0.653800 0.730400 0.197900\nvn 0.702500 0.677400 0.218300\nvn 0.376200 0.678600 -0.630800\nvn 0.481700 -0.104500 -0.870100\nvn 0.453700 -0.116400 -0.883500\nvn 0.565200 0.558700 -0.606900\nvn 0.378600 -0.739000 -0.557300\nvn 0.203800 -0.743600 -0.636800\nvn 0.116800 -0.986700 0.113400\nvn -0.119200 -0.992700 0.021100\nvn -0.298200 -0.626400 0.720200\nvn -0.120900 -0.626400 0.770100\nvn -0.251200 0.097300 0.963000\nvn -0.264600 0.060100 0.962500\nvn 0.039600 0.688400 0.724300\nvn -0.123600 0.736500 0.665100\nvn 0.121600 0.992400 -0.020100\nvn 0.368400 0.925900 0.084100\nvn 0.412300 0.602800 -0.683100\nvn 0.545400 0.562600 -0.621300\nvn 0.558000 -0.104300 -0.823300\nvn 0.610400 -0.643800 -0.461500\nvn 0.515500 -0.706100 -0.485600\nvn 0.439800 0.608500 -0.660500\nvn 0.269700 -0.946700 0.176400\nvn 0.116100 -0.569100 0.814000\nvn 0.000300 -0.590100 0.807300\nvn -0.180000 0.092100 0.979300\nvn -0.013300 0.713200 0.700800\nvn -0.105600 0.737400 0.667200\nvn 0.132600 0.991000 -0.018100\nvn 0.311200 -0.689200 -0.654300\nvn 0.641200 -0.056400 -0.765300\nvn 0.332400 0.642600 -0.690300\nvn 0.362100 0.677200 -0.640500\nvn 0.410600 -0.744900 -0.525800\nvn -0.192200 0.971300 -0.140200\nvn -0.613800 0.716200 0.332200\nvn -0.664300 0.680300 0.309700\nvn -0.835600 0.016600 0.549100\nvn -0.652700 -0.629000 0.422300\nvn -0.623000 -0.652500 0.431500\nvn -0.126300 -0.992000 0.006600\nvn 0.056400 -0.561100 -0.825900\nvn 0.403200 0.172100 -0.898800\nvn 0.343200 0.069300 -0.936700\nvn 0.401500 0.794100 -0.456400\nvn 0.038500 -0.638500 -0.768600\nvn 0.401900 0.830200 -0.386400\nvn 0.155800 0.946900 0.281200\nvn 0.165600 0.960600 0.223100\nvn -0.233000 0.609400 0.757900\nvn -0.297000 0.673800 0.676600\nvn -0.419300 0.731300 0.537900\nvn -0.603300 -0.118300 0.788700\nvn -0.672500 -0.028200 0.739600\nvn -0.628800 -0.732200 0.261700\nvn -0.656000 -0.708900 0.259100\nvn -0.334600 -0.874300 -0.351600\nvn -0.289000 -0.898000 -0.331800\nvn 0.488900 -0.526400 -0.695600\nvn 0.232600 0.204100 -0.950900\nvn 0.274700 0.252500 -0.927800\nvn 0.219700 0.882400 -0.416200\nvn 0.078900 -0.521900 -0.849300\nvn -0.497000 0.866600 0.043800\nvn -0.023100 0.971100 0.237700\nvn -0.627300 0.487100 0.607600\nvn -0.329100 0.583200 0.742700\nvn -0.428300 -0.241000 0.870900\nvn -0.539100 -0.210400 0.815600\nvn 0.034700 -0.838800 0.543300\nvn -0.464700 -0.828100 0.313700\nvn 0.404700 -0.910700 -0.082300\nvn -0.198600 -0.930200 -0.308600\nvn 0.223100 0.073400 -0.972000\nvn 0.216400 0.130000 -0.967600\nvn 0.743000 -0.415900 -0.524300\nvn -0.301500 0.637500 -0.709000\nvn -0.452800 0.556400 -0.696700\nvn -0.610300 0.785400 -0.103800\nvn -0.777300 0.614800 -0.133700\nvn -0.804100 0.365200 0.469100\nvn -0.268500 0.836400 -0.477800\nvn -0.610300 0.514300 0.602500\nvn -0.313300 -0.123400 0.941600\nvn -0.337300 -0.196800 0.920600\nvn 0.383200 -0.620100 0.684600\nvn 0.314700 -0.692900 0.648800\nvn 0.544600 -0.834600 0.083100\nvn 0.750400 -0.649900 0.120500\nvn 0.622700 -0.562000 -0.544400\nvn 0.205300 -0.687000 -0.697000\nvn 0.244700 0.105700 -0.963800\nvn 0.244800 0.035800 -0.968900\nvn -0.023300 0.786700 -0.616900\nvn 0.299400 -0.711100 -0.636200\nvn 0.105800 0.825100 -0.555000\nvn -0.084000 0.993900 0.071100\nvn -0.222100 0.974800 0.021800\nvn -0.347200 0.684700 0.640800\nvn -0.324700 -0.101300 0.940400\nvn -0.295700 -0.095200 0.950500\nvn -0.170800 -0.817100 0.550600\nvn -0.057000 -0.806700 0.588200\nvn 0.043800 -0.995200 -0.088000\nvn 0.162900 -0.985800 -0.040200\nvn 0.072900 -0.645500 -0.760300\nvn 0.212400 0.107700 -0.971200\nvn -0.002000 0.796000 -0.605300\nvn 0.057300 0.811400 -0.581700\nvn 0.155100 -0.677100 -0.719400\nvn -0.155700 0.986500 0.050100\nvn -0.408200 0.709500 0.574500\nvn -0.374000 0.685200 0.624900\nvn -0.268300 0.676700 0.685600\nvn -0.433700 -0.099200 0.895600\nvn -0.351500 -0.791100 0.500600\nvn -0.281700 -0.806500 0.519900\nvn -0.020300 -0.993700 -0.110400\nvn 0.303900 -0.672600 0.674700\nvn 0.391100 0.040300 0.919400\nvn 0.300100 0.042300 0.953000\nvn 0.073900 0.699100 0.711200\nvn -0.011700 0.665500 0.746300\nvn 0.399800 -0.640800 0.655400\nvn 0.286300 0.733900 0.615900\nvn 0.126500 0.991000 -0.043900\nvn -0.178900 0.982700 0.048000\nvn -0.087500 0.665900 -0.740900\nvn -0.223600 0.693200 -0.685200\nvn -0.191700 -0.044600 -0.980400\nvn -0.112300 -0.013400 -0.993600\nvn -0.121700 -0.707500 -0.696200\nvn 0.098300 -0.669200 -0.736600\nvn 0.071900 -0.997300 -0.016400\nvn 0.247600 -0.966100 -0.073600\nvn 0.445700 0.016400 0.895000\nvn 0.458100 0.022800 0.888600\nvn 0.216500 -0.681400 0.699100\nvn 0.565800 0.626100 0.536500\nvn 0.536800 0.644600 0.544400\nvn 0.448100 0.882700 -0.141600\nvn 0.400700 0.907400 -0.127100\nvn 0.130600 0.609800 -0.781700\nvn 0.148000 0.595200 -0.789800\nvn -0.157600 -0.064600 -0.985400\nvn -0.153400 -0.054000 -0.986700\nvn -0.226500 -0.711400 -0.665300\nvn -0.282900 -0.712400 -0.642200\nvn -0.102800 -0.994000 0.037700\nvn 0.167200 -0.671600 0.721800\nvn 0.393700 0.027600 0.918800\nvn 0.494600 0.655300 0.570900\nvn 0.369200 0.921900 -0.117800\nvn 0.077200 0.621200 -0.779800\nvn -0.210300 -0.070500 -0.975100\nvn 0.477500 0.323900 0.816800\nvn 0.227500 0.317500 0.920500\nvn -0.228200 -0.895400 -0.382400\nvn -0.489000 -0.384500 -0.783000\nvn -0.736400 -0.364100 -0.570200\nvn -0.713000 0.351500 -0.606700\nvn -0.448100 -0.893700 -0.022800\nvn -0.621600 0.766500 -0.161400\nvn -0.514900 0.836200 -0.189000\nvn -0.146200 0.851300 0.503800\nvn -0.201500 0.860300 0.468300\nvn 0.597900 0.261600 0.757700\nvn 0.282700 0.269700 0.920500\nvn 0.384800 -0.416700 0.823600\nvn 0.673500 -0.574300 0.465500\nvn 0.102500 -0.993500 0.049500\nvn 0.070600 -0.944500 0.321000\nvn -0.546700 -0.420600 -0.724000\nvn -0.825200 0.215200 -0.522200\nvn -0.717800 0.332000 -0.611900\nvn -0.142500 -0.886900 -0.439400\nvn -0.797800 0.594700 -0.099100\nvn -0.683700 0.691500 0.233000\nvn -0.772100 0.238100 0.589200\nvn -0.593200 -0.331600 0.733600\nvn 0.084100 -0.062700 0.994500\nvn -0.242100 -0.815900 0.525200\nvn 0.351600 -0.695200 0.626900\nvn 0.824000 -0.310600 0.473900\nvn 0.728600 0.253900 0.636100\nvn 0.300400 0.724500 0.620400\nvn -0.309700 0.548800 0.776400\nvn 0.114100 -0.979700 -0.164800\nvn 0.272900 -0.923100 -0.270800\nvn 0.293300 0.638600 0.711400\nvn 0.903300 0.297600 0.308900\nvn 0.859000 0.324900 0.395700\nvn 0.921200 -0.356100 -0.156900\nvn 0.197100 0.797500 0.570200\nvn 0.913400 -0.287000 -0.288500\nvn 0.273900 -0.665100 -0.694700\nvn 0.271100 -0.734600 -0.621900\nvn -0.366600 -0.750200 -0.550200\nvn -0.360100 -0.900900 -0.242400\nvn -0.916700 -0.377300 -0.131500\nvn -0.904800 -0.403300 -0.136800\nvn -0.894000 0.141100 0.425300\nvn -0.930600 0.194800 0.309900\nvn -0.493300 0.514700 0.701200\nvn -0.555100 0.665200 0.499400\nvn 0.298900 0.709700 0.637900\nvn 0.918400 0.306100 0.250500\nvn 0.911800 0.241300 0.332200\nvn 0.920300 -0.309500 -0.239500\nvn 0.301600 0.558900 0.772500\nvn 0.901900 -0.338500 -0.268200\nvn 0.326800 -0.826600 -0.458200\nvn 0.293700 -0.729600 -0.617600\nvn -0.384000 -0.843400 -0.375800\nvn -0.394000 -0.748500 -0.533400\nvn -0.911000 -0.411900 -0.018700\nvn -0.915700 -0.397800 -0.056700\nvn -0.915400 0.163300 0.367900\nvn -0.880200 0.110500 0.461600\nvn -0.413900 -0.688300 -0.595700\nvn -0.543300 0.573500 0.613200\nvn -0.485600 0.457200 0.745100\nvn 0.282900 0.932300 0.225300\nvn 0.923000 0.382600 0.040700\nvn 0.929600 0.344000 0.132100\nvn 0.924200 -0.356700 -0.136100\nvn 0.294900 0.859600 0.417200\nvn 0.916400 -0.371200 -0.149400\nvn 0.333200 -0.923200 -0.191400\nvn 0.327400 -0.901600 -0.282800\nvn -0.375400 -0.900400 -0.220000\nvn -0.916200 -0.399600 0.029100\nvn -0.912400 -0.409200 0.006300\nvn -0.946500 0.271600 0.174400\nvn -0.940100 0.237200 0.244800\nvn -0.589300 0.766600 0.255200\nvn -0.579600 0.709600 0.400600\nvn 0.921700 0.359500 0.145700\nvn 0.919800 -0.371600 0.125800\nvn 0.310000 0.893500 0.324900\nvn 0.355200 -0.932800 0.060300\nvn -0.354500 -0.924400 0.140800\nvn -0.373200 -0.919100 -0.126600\nvn -0.889000 -0.405900 0.212000\nvn -0.917300 0.267300 0.295100\nvn -0.558400 0.764400 0.322300\nvn 0.803200 -0.594400 -0.039200\nvn 0.984000 0.090900 0.153100\nvn 0.686400 0.725100 -0.055600\nvn 0.658700 0.752400 -0.006900\nvn 0.748100 -0.655900 0.100600\nvn -0.006300 0.971200 -0.238100\nvn -0.595300 0.640200 -0.485500\nvn -0.643000 0.623800 -0.444200\nvn -0.879100 -0.113800 -0.462900\nvn -0.541900 -0.709700 -0.450100\nvn -0.569100 -0.749500 -0.338300\nvn 0.085400 -0.992700 -0.084700\nvn -0.063700 -0.640600 0.765200\nvn 0.707100 0.000000 0.707100\nvn -0.571100 0.745800 -0.342900\nvn -0.040500 0.855500 0.516300\nvn -0.011100 -0.856200 0.516600\nvn -0.457800 -0.725200 -0.514400\nusemtl None\ns 1\nf 2/1/1 3/2/2 4/3/3\nf 6/4/4 3/2/2 2/1/1\nf 8/5/5 3/2/2 6/4/4\nf 9/6/6 10/7/7 3/2/2\nf 4/3/3 3/2/2 10/7/7\nf 13/8/8 14/9/9 15/10/10\nf 17/11/11 14/9/9 13/8/8\nf 19/12/12 14/9/9 17/11/11\nf 20/13/13 21/14/14 14/9/9\nf 15/10/10 14/9/9 21/14/14\nf 23/15/15 24/16/16 6/4/4\nf 15/10/10 24/16/16 23/15/15\nf 25/17/17 24/16/16 15/10/10\nf 26/18/18 27/19/19 24/16/16\nf 6/4/4 24/16/16 27/19/19\nf 28/20/20 29/21/21 17/11/11\nf 4/22/3 29/21/21 28/20/20\nf 30/23/22 29/21/21 4/22/3\nf 31/24/23 32/25/24 29/21/21\nf 17/11/11 29/21/21 32/25/24\nf 33/26/25 34/27/26 35/28/27\nf 38/29/28 35/28/27 34/27/26\nf 39/30/29 40/31/30 35/28/27\nf 41/32/31 36/33/32 35/28/27\nf 43/34/33 44/35/34 45/36/35\nf 47/37/36 44/35/34 43/34/33\nf 49/38/37 44/35/34 47/37/36\nf 45/36/35 44/35/34 49/38/37\nf 18/39/38 51/40/39 52/41/40\nf 54/42/41 52/41/40 51/40/39\nf 56/43/42 52/41/40 54/42/41\nf 19/12/12 52/41/40 56/43/42\nf 57/44/43 58/45/44 59/46/45\nf 61/47/46 62/48/47 59/46/45\nf 64/49/48 59/46/45 62/48/47\nf 65/50/49 60/51/50 59/46/45\nf 66/52/51 67/53/52 68/54/53\nf 70/55/54 71/56/55 68/54/53\nf 72/57/56 73/58/57 68/54/53\nf 74/59/58 69/60/59 68/54/53\nf 75/61/60 76/62/61 77/63/62\nf 79/64/63 80/65/64 77/63/62\nf 82/66/65 77/63/62 80/65/64\nf 83/67/66 78/68/67 77/63/62\nf 84/69/68 85/70/69 86/71/70\nf 89/72/71 86/71/70 85/70/69\nf 5/73/72 2/1/1 86/71/70\nf 1/74/73 87/75/74 86/71/70\nf 90/76/75 91/77/76 89/72/71\nf 93/78/77 91/77/76 90/76/75\nf 12/79/78 23/15/15 91/77/76\nf 5/73/72 89/72/71 91/77/76\nf 94/80/79 95/81/80 93/78/77\nf 97/82/81 95/81/80 94/80/79\nf 16/83/82 13/8/8 95/81/80\nf 12/79/78 93/78/77 95/81/80\nf 96/84/83 98/85/84 99/86/85\nf 87/87/74 99/86/85 98/85/84\nf 1/88/73 28/20/20 99/86/85\nf 16/83/82 97/82/81 99/86/85\nf 100/89/68 101/90/86 102/91/87\nf 104/92/71 105/93/71 102/91/87\nf 85/70/69 102/91/87 105/93/71\nf 103/94/88 102/91/87 85/70/69\nf 106/95/89 107/96/75 105/93/71\nf 108/97/90 109/98/90 107/96/75\nf 90/76/75 107/96/75 109/98/90\nf 88/99/91 105/93/71 107/96/75\nf 110/100/79 111/101/79 109/98/90\nf 113/102/92 111/101/79 110/100/79\nf 96/84/83 94/80/79 111/101/79\nf 109/98/90 111/101/79 94/80/79\nf 112/103/93 114/104/94 115/105/95\nf 103/106/88 115/105/95 114/104/94\nf 84/107/68 98/85/84 115/105/95\nf 113/102/92 115/105/95 98/85/84\nf 116/108/96 117/109/97 118/110/98\nf 121/111/99 118/110/98 117/109/97\nf 101/90/86 118/110/98 121/111/99\nf 119/112/88 118/110/98 101/90/86\nf 122/113/100 123/114/75 121/111/99\nf 124/115/90 125/116/101 123/114/75\nf 106/95/89 123/114/75 125/116/101\nf 121/111/99 123/114/75 106/95/89\nf 126/117/102 127/118/103 125/116/101\nf 129/119/104 127/118/103 126/117/102\nf 112/103/93 110/100/79 127/118/103\nf 108/97/90 125/116/101 127/118/103\nf 128/120/93 130/121/105 131/122/106\nf 119/123/88 131/122/106 130/121/105\nf 114/104/94 131/122/106 119/123/88\nf 129/119/104 131/122/106 114/104/94\nf 74/59/58 73/58/57 132/124/107\nf 72/57/56 134/125/108 132/124/107\nf 117/109/97 132/126/69 134/127/109\nf 133/128/110 132/126/69 117/109/97\nf 72/57/56 71/56/55 135/129/111\nf 70/55/54 136/130/112 135/129/111\nf 122/113/100 135/131/75 136/132/113\nf 134/127/109 135/131/75 122/113/100\nf 70/55/54 67/53/52 137/133/114\nf 66/52/51 138/134/115 137/133/114\nf 126/117/102 137/135/79 138/136/116\nf 136/132/113 137/135/79 126/117/102\nf 66/52/51 69/60/59 139/137/117\nf 74/59/58 133/138/118 139/137/117\nf 116/139/96 130/121/105 139/140/94\nf 138/136/116 139/140/94 130/121/105\nf 75/61/60 140/141/119 141/142/120\nf 143/143/121 141/142/120 140/141/119\nf 55/144/122 144/145/123 141/142/120\nf 76/62/61 141/142/120 144/145/123\nf 145/146/124 146/147/125 147/148/126\nf 150/149/127 147/148/126 146/147/125\nf 151/150/128 152/151/129 147/148/126\nf 83/67/66 148/152/130 147/148/126\nf 153/153/131 154/154/132 155/155/133\nf 158/156/134 155/155/133 154/154/132\nf 159/157/135 160/158/136 155/159/133\nf 161/160/137 156/161/138 155/159/133\nf 162/162/139 163/163/140 164/164/141\nf 166/165/142 164/164/141 163/163/140\nf 167/166/143 168/167/144 164/164/141\nf 165/168/145 164/164/141 168/167/144\nf 171/169/146 172/170/147 173/171/148\nf 174/172/149 175/173/150 172/170/147\nf 176/174/151 177/175/152 172/170/147\nf 173/171/148 172/170/147 177/175/152\nf 179/176/153 180/177/154 181/178/155\nf 184/179/156 181/178/155 180/177/154\nf 185/180/157 186/181/158 181/178/155\nf 182/182/159 181/178/155 186/181/158\nf 162/162/139 165/168/145 188/183/160\nf 169/184/161 190/185/162 188/183/160\nf 80/65/64 188/183/160 190/185/162\nf 79/64/63 189/186/163 188/183/160\nf 163/163/140 191/187/164 54/42/41\nf 162/162/139 189/186/163 191/187/164\nf 79/64/63 144/145/123 191/187/164\nf 54/42/41 191/187/164 144/145/123\nf 146/147/125 192/188/165 193/189/166\nf 145/146/124 194/190/167 192/188/165\nf 161/160/137 160/158/136 192/188/165\nf 159/157/135 193/189/166 192/188/165\nf 195/191/168 196/192/169 197/193/170\nf 199/194/171 200/195/172 197/193/170\nf 201/196/173 202/197/174 197/193/170\nf 203/198/175 198/199/176 197/193/170\nf 204/200/177 205/201/178 206/202/179\nf 208/203/180 209/204/181 206/202/179\nf 211/205/182 206/202/179 209/204/181\nf 212/206/183 207/207/184 206/202/179\nf 194/190/167 213/208/185 214/209/186\nf 145/146/124 148/152/130 213/208/185\nf 82/66/65 213/208/185 148/152/130\nf 81/210/187 214/209/186 213/208/185\nf 140/141/119 215/211/188 216/212/189\nf 75/61/60 78/68/67 215/211/188\nf 152/151/129 215/211/188 78/68/67\nf 151/150/128 216/212/189 215/211/188\nf 157/213/190 154/154/132 217/214/191\nf 219/215/192 217/214/191 154/154/132\nf 169/184/161 168/167/144 217/214/191\nf 218/216/193 217/214/191 168/167/144\nf 153/153/131 156/217/138 220/218/194\nf 214/209/186 220/219/194 156/161/138\nf 81/210/187 190/185/162 220/219/194\nf 219/215/192 220/218/194 190/185/162\nf 221/220/195 222/221/196 223/222/197\nf 226/223/198 223/222/197 222/221/196\nf 9/6/6 227/224/199 223/222/197\nf 224/225/200 223/222/197 227/224/199\nf 225/226/201 228/227/202 229/228/203\nf 230/229/204 231/230/205 229/231/203\nf 11/232/206 10/233/7 229/231/203\nf 226/223/198 229/228/203 10/7/7\nf 232/234/207 233/235/208 231/230/205\nf 234/236/209 235/237/210 233/235/208\nf 236/238/211 233/235/208 235/237/210\nf 11/232/206 231/230/205 233/235/208\nf 237/239/212 238/240/213 235/237/210\nf 224/225/200 238/241/213 237/242/212\nf 158/243/134 238/241/213 224/225/200\nf 235/237/210 238/240/213 158/156/134\nf 240/244/214 241/245/215 242/246/216\nf 243/247/217 244/248/218 241/245/215\nf 225/249/201 222/250/196 241/245/215\nf 242/246/216 241/245/215 222/250/196\nf 243/247/217 245/251/219 246/252/220\nf 248/253/221 246/252/220 245/251/219\nf 228/254/202 246/252/220 248/253/221\nf 244/248/218 246/252/220 228/254/202\nf 247/255/222 249/256/223 250/257/224\nf 252/258/225 250/257/224 249/256/223\nf 232/234/207 250/257/224 252/258/225\nf 230/229/204 248/253/221 250/257/224\nf 253/259/226 254/260/227 252/258/225\nf 239/261/228 242/246/216 254/260/227\nf 221/262/195 237/239/212 254/260/227\nf 234/236/209 252/258/225 254/260/227\nf 256/263/229 257/264/230 258/265/231\nf 259/266/232 260/267/233 257/264/230\nf 240/244/214 257/264/230 260/267/233\nf 239/261/228 258/265/231 257/264/230\nf 261/268/234 262/269/235 260/267/233\nf 264/270/236 262/269/235 261/268/234\nf 247/255/222 245/251/219 262/269/235\nf 243/247/217 260/267/233 262/269/235\nf 263/271/237 265/272/238 266/273/239\nf 268/274/240 266/273/239 265/272/238\nf 251/275/241 249/256/223 266/273/239\nf 264/270/236 266/273/239 249/256/223\nf 267/276/242 269/277/243 270/278/244\nf 255/279/245 258/265/231 270/278/244\nf 253/259/226 270/278/244 258/265/231\nf 268/274/240 270/278/244 253/259/226\nf 271/280/246 272/281/247 273/282/248\nf 276/283/249 273/282/248 272/281/247\nf 167/166/143 277/284/250 273/282/248\nf 274/285/251 273/282/248 277/284/250\nf 275/286/252 278/287/253 279/288/254\nf 280/289/255 281/290/256 279/288/254\nf 218/216/193 279/288/254 281/290/256\nf 276/283/249 279/288/254 218/216/193\nf 282/291/257 283/292/258 281/290/256\nf 284/293/259 285/294/260 283/292/258\nf 236/238/211 283/292/258 285/294/260\nf 157/213/190 281/290/256 283/292/258\nf 286/295/261 287/296/262 285/294/260\nf 274/285/251 287/296/262 286/295/261\nf 30/23/22 287/296/262 274/285/251\nf 285/294/260 287/296/262 30/23/22\nf 288/297/263 289/298/264 290/299/265\nf 293/300/266 290/299/265 289/298/264\nf 275/286/252 272/281/247 290/299/265\nf 291/301/267 290/299/265 272/281/247\nf 292/302/268 294/303/269 295/304/270\nf 297/305/271 295/304/270 294/303/269\nf 278/287/253 295/304/270 297/305/271\nf 293/300/266 295/304/270 278/287/253\nf 298/306/272 299/307/273 300/308/274\nf 302/309/275 303/310/276 300/308/274\nf 305/311/277 300/308/274 303/310/276\nf 301/312/278 300/308/274 305/311/277\nf 308/313/279 309/314/280 310/315/281\nf 291/301/267 309/314/280 308/313/279\nf 271/280/246 286/295/261 309/314/280\nf 284/293/259 310/315/281 309/314/280\nf 312/316/282 313/317/283 314/318/284\nf 315/319/285 316/320/286 313/317/283\nf 282/291/257 313/317/283 316/320/286\nf 280/289/255 314/318/284 313/317/283\nf 317/321/287 318/322/288 316/320/286\nf 320/323/289 318/322/288 317/321/287\nf 310/315/281 318/322/288 320/323/289\nf 316/320/286 318/322/288 310/315/281\nf 319/324/290 321/325/291 322/326/292\nf 324/327/293 322/326/292 321/325/291\nf 296/328/294 325/329/295 322/326/292\nf 320/323/289 322/326/292 325/329/295\nf 326/330/296 327/331/297 324/327/293\nf 311/332/298 314/318/284 327/331/297\nf 297/305/271 327/331/297 314/318/284\nf 324/327/293 327/331/297 297/305/271\nf 328/333/299 329/334/300 330/335/301\nf 332/336/302 333/337/303 330/335/301\nf 312/316/282 330/335/301 333/337/303\nf 311/332/298 331/338/304 330/335/301\nf 332/336/302 334/339/305 335/340/306\nf 337/341/307 335/340/306 334/339/305\nf 317/321/287 335/340/306 337/341/307\nf 315/319/285 333/337/303 335/340/306\nf 339/342/308 340/343/309 341/344/310\nf 342/345/311 343/346/312 340/343/309\nf 345/347/313 340/343/309 343/346/312\nf 341/344/310 340/343/309 345/347/313\nf 348/348/314 349/349/315 350/350/316\nf 328/333/299 331/338/304 349/349/315\nf 311/332/298 326/330/296 349/349/315\nf 350/350/316 349/349/315 326/330/296\nf 203/198/175 202/197/174 351/351/317\nf 201/196/173 353/352/318 351/351/317\nf 355/353/319 351/351/317 353/352/318\nf 302/309/275 352/354/320 351/351/317\nf 201/196/173 200/195/172 356/355/321\nf 199/194/171 357/356/322 356/355/321\nf 358/357/323 359/358/324 356/355/321\nf 354/359/325 353/352/318 356/355/321\nf 199/194/171 196/192/169 360/360/326\nf 195/191/168 361/361/327 360/360/326\nf 298/306/272 362/362/328 360/360/326\nf 357/356/322 360/360/326 362/362/328\nf 195/191/168 198/199/176 363/363/329\nf 203/198/175 352/354/320 363/363/329\nf 299/307/273 363/363/329 352/354/320\nf 361/361/327 363/363/329 299/307/273\nf 365/364/330 366/365/331 367/366/332\nf 369/367/333 366/365/331 365/364/330\nf 323/368/334 321/325/291 366/365/331\nf 367/366/332 366/365/331 321/325/291\nf 370/369/335 371/370/336 369/367/333\nf 373/371/337 371/370/336 370/369/335\nf 350/350/316 371/370/336 373/371/337\nf 369/367/333 371/370/336 350/350/316\nf 372/372/338 374/373/339 375/374/340\nf 377/375/341 375/374/340 374/373/339\nf 378/376/342 375/374/340 377/375/341\nf 373/371/337 375/374/340 378/376/342\nf 379/377/343 380/378/344 377/375/341\nf 367/366/332 380/378/344 379/377/343\nf 337/341/307 380/378/344 367/366/332\nf 377/375/341 380/378/344 337/341/307\nf 382/379/345 383/380/346 384/381/347\nf 385/382/348 386/383/349 383/380/346\nf 387/384/350 388/385/351 383/380/346\nf 389/386/352 384/381/347 383/380/346\nf 343/346/312 390/387/353 391/388/354\nf 392/389/355 390/387/353 343/346/312\nf 372/372/338 370/369/335 390/387/353\nf 391/388/354 390/387/353 370/369/335\nf 394/390/356 395/391/357 396/392/358\nf 398/393/359 395/391/357 394/390/356\nf 400/394/360 395/391/357 398/393/359\nf 396/392/358 395/391/357 400/394/360\nf 338/395/361 341/344/310 402/396/362\nf 404/397/363 402/396/362 341/344/310\nf 379/377/343 402/396/362 404/397/363\nf 376/398/364 403/399/365 402/396/362\nf 405/400/366 406/401/367 407/402/368\nf 408/403/369 409/404/370 406/401/367\nf 410/405/371 406/401/367 409/404/370\nf 26/18/18 407/402/368 406/401/367\nf 411/406/372 412/407/373 409/404/370\nf 414/408/374 412/407/373 411/406/372\nf 216/212/189 412/407/373 414/408/374\nf 409/404/370 412/407/373 216/212/189\nf 413/409/375 415/410/376 416/411/377\nf 417/412/378 416/411/377 415/410/376\nf 418/413/379 416/411/377 417/412/378\nf 414/408/374 416/411/377 418/413/379\nf 187/414/380 186/181/158 419/415/381\nf 185/180/157 407/402/368 419/415/381\nf 25/17/17 419/415/381 407/402/368\nf 22/416/382 417/412/378 419/415/381\nf 420/417/383 421/418/384 422/419/385\nf 425/420/386 422/419/385 421/418/384\nf 426/421/387 427/422/388 422/419/385\nf 423/423/389 422/419/385 427/422/388\nf 424/424/390 429/425/391 430/426/392\nf 431/427/393 432/428/394 430/426/392\nf 433/429/395 434/430/396 430/426/392\nf 425/420/386 430/426/392 434/430/396\nf 435/431/397 436/432/398 432/428/394\nf 437/433/399 438/434/400 436/432/398\nf 440/435/401 436/432/398 438/434/400\nf 433/429/395 432/428/394 436/432/398\nf 441/436/402 442/437/403 438/434/400\nf 420/417/383 423/423/389 442/437/403\nf 443/438/404 442/437/403 423/423/389\nf 439/439/405 438/434/400 442/437/403\nf 444/440/406 445/441/407 446/442/408\nf 449/443/409 446/442/408 445/441/407\nf 424/424/390 421/418/384 446/442/408\nf 447/444/410 446/442/408 421/418/384\nf 448/445/411 450/446/412 451/447/413\nf 452/448/414 453/449/415 451/447/413\nf 431/427/393 429/425/391 451/447/413\nf 449/443/409 451/447/413 429/425/391\nf 454/450/416 455/451/417 453/449/415\nf 457/452/418 455/451/417 454/450/416\nf 435/431/397 455/451/417 457/452/418\nf 431/427/393 453/449/415 455/451/417\nf 456/453/419 458/454/420 459/455/421\nf 447/444/410 459/455/421 458/454/420\nf 420/417/383 441/436/402 459/455/421\nf 437/433/399 457/452/418 459/455/421\nf 389/386/352 388/385/351 460/456/422\nf 387/384/350 462/457/423 460/456/422\nf 365/364/330 460/456/422 462/457/423\nf 461/458/424 460/456/422 365/364/330\nf 387/384/350 386/383/349 463/459/425\nf 464/460/426 463/459/425 386/383/349\nf 344/461/427 391/388/354 463/459/425\nf 462/457/423 463/459/425 391/388/354\nf 466/462/428 467/463/429 468/464/430\nf 470/465/431 467/463/432 466/462/428\nf 472/466/433 467/463/432 470/465/431\nf 468/464/430 467/463/429 472/466/433\nf 384/381/347 474/467/434 475/468/435\nf 389/386/352 461/458/424 474/467/434\nf 404/397/363 474/467/434 461/458/424\nf 346/469/436 475/468/435 474/467/434\nf 476/470/437 477/471/438 478/472/439\nf 481/473/440 478/472/439 477/471/438\nf 376/398/364 374/373/339 478/472/439\nf 479/474/441 478/472/439 374/373/339\nf 480/475/442 482/476/443 483/477/444\nf 485/478/445 483/477/444 482/476/443\nf 403/399/365 483/477/444 485/478/445\nf 481/473/440 483/477/444 403/399/365\nf 486/479/446 487/480/447 485/478/445\nf 488/481/448 489/482/449 487/480/447\nf 339/342/308 487/480/447 489/482/449\nf 338/395/361 485/478/445 487/480/447\nf 490/483/450 491/484/451 489/482/449\nf 476/470/437 479/474/441 491/484/451\nf 392/389/355 491/484/451 479/474/441\nf 342/345/311 489/482/449 491/484/451\nf 493/485/452 494/486/453 495/487/454\nf 496/488/455 497/489/456 494/486/453\nf 477/471/438 494/486/453 497/489/456\nf 476/470/437 495/487/454 494/486/453\nf 498/490/457 499/491/458 497/489/456\nf 501/492/459 499/491/458 498/490/457\nf 484/493/460 482/476/443 499/491/458\nf 497/489/456 499/491/458 482/476/443\nf 500/494/461 502/495/462 503/496/463\nf 505/497/464 503/496/463 502/495/462\nf 488/481/448 486/479/446 503/496/463\nf 501/492/459 503/496/463 486/479/446\nf 504/498/465 506/499/466 507/500/467\nf 492/501/468 495/487/454 507/500/467\nf 490/483/450 507/500/467 495/487/454\nf 505/497/464 507/500/467 490/483/450\nf 509/502/469 510/503/470 511/504/471\nf 512/505/472 513/506/473 510/503/470\nf 277/284/250 510/503/470 513/506/473\nf 511/504/471 510/503/470 277/284/250\nf 514/507/474 515/508/475 513/506/473\nf 516/509/476 517/510/477 515/508/475\nf 18/39/38 32/25/24 515/508/475\nf 513/506/473 515/508/475 32/25/24\nf 518/511/478 519/512/479 517/510/477\nf 520/513/480 521/514/481 519/512/479\nf 51/40/39 519/512/479 521/514/481\nf 517/510/477 519/512/479 51/40/39\nf 522/515/482 523/516/483 521/514/481\nf 508/517/484 511/504/471 523/516/483\nf 166/165/142 523/516/483 511/504/471\nf 521/514/481 523/516/483 166/165/142\nf 524/518/485 525/519/486 526/520/487\nf 529/521/488 526/520/487 525/519/486\nf 512/505/472 509/502/469 526/520/487\nf 527/522/489 526/520/487 509/502/469\nf 528/523/490 530/524/491 531/525/492\nf 532/526/493 533/527/494 531/525/492\nf 514/507/474 531/525/492 533/527/494\nf 529/521/488 531/525/492 514/507/474\nf 534/528/495 535/529/496 533/527/494\nf 536/530/497 537/531/498 535/529/496\nf 518/511/478 535/529/496 537/531/498\nf 516/509/476 533/527/494 535/529/496\nf 538/532/499 539/533/500 537/531/498\nf 527/522/489 539/533/500 538/532/499\nf 508/517/484 522/515/482 539/533/500\nf 520/513/480 537/531/498 539/533/500\nf 540/534/501 541/535/502 542/536/503\nf 544/537/504 545/538/505 542/536/503\nf 546/539/506 547/540/507 542/536/503\nf 548/541/508 543/542/509 542/536/503\nf 62/48/47 549/543/510 550/544/511\nf 61/47/46 551/545/512 549/543/510\nf 294/303/269 549/543/510 551/545/512\nf 550/544/511 549/543/510 294/303/269\nf 61/47/46 58/45/44 552/546/513\nf 57/44/43 553/547/514 552/546/513\nf 325/329/295 552/546/513 553/547/514\nf 551/545/512 552/546/513 325/329/295\nf 57/44/43 60/51/50 554/548/515\nf 555/549/516 554/548/515 60/51/50\nf 308/313/279 554/548/515 555/549/516\nf 553/547/514 554/548/515 308/313/279\nf 548/541/508 547/540/507 556/550/517\nf 546/539/506 558/551/518 556/550/517\nf 289/298/264 556/550/517 558/551/518\nf 557/552/519 556/550/517 289/298/264\nf 546/539/506 545/538/505 559/553/520\nf 560/554/521 559/553/520 545/538/505\nf 63/555/522 550/544/511 559/553/520\nf 558/551/518 559/553/520 550/544/511\nf 561/556/523 562/557/524 563/558/525\nf 565/559/526 566/560/527 563/558/525\nf 567/561/528 568/562/529 563/558/525\nf 569/563/530 564/564/531 563/558/525\nf 543/542/509 570/565/532 571/566/533\nf 548/541/508 557/552/519 570/565/532\nf 555/549/516 570/565/532 557/552/519\nf 65/50/49 571/566/533 570/565/532\nf 573/567/534 574/568/535 575/569/536\nf 577/570/537 574/568/535 573/567/534\nf 525/519/486 574/568/535 577/570/537\nf 524/518/485 575/569/536 574/568/535\nf 578/571/538 579/572/539 577/570/537\nf 581/573/540 579/572/539 578/571/538\nf 532/526/493 530/524/491 579/572/539\nf 528/523/490 577/570/537 579/572/539\nf 582/574/541 583/575/542 581/573/540\nf 585/576/543 583/575/542 582/574/541\nf 536/530/497 534/528/495 583/575/542\nf 581/573/540 583/575/542 534/528/495\nf 586/577/544 587/578/545 585/576/543\nf 575/569/536 587/578/545 586/577/544\nf 538/532/499 587/578/545 575/569/536\nf 585/576/543 587/578/545 538/532/499\nf 588/579/546 589/580/547 590/581/548\nf 592/582/549 593/583/550 590/581/548\nf 594/584/551 595/585/552 590/581/548\nf 596/586/553 591/587/554 590/581/548\nf 209/204/181 597/588/555 598/589/556\nf 599/590/557 597/588/555 209/204/181\nf 580/591/558 578/571/538 597/588/555\nf 576/592/559 598/589/556 597/588/555\nf 600/593/560 601/594/561 602/595/562\nf 604/596/563 605/597/564 602/595/562\nf 606/598/565 607/599/566 602/595/562\nf 608/600/567 603/601/568 602/595/562\nf 207/207/184 609/602/569 610/603/570\nf 611/604/571 609/602/569 207/207/184\nf 572/605/572 586/577/544 609/602/569\nf 584/606/573 610/603/570 609/602/569\nf 613/607/574 614/608/575 615/609/576\nf 617/610/577 614/608/575 613/607/574\nf 576/592/559 573/567/534 614/608/575\nf 615/609/576 614/608/575 573/567/534\nf 616/611/578 618/612/579 619/613/580\nf 621/614/581 619/613/580 618/612/579\nf 210/615/582 598/589/556 619/613/580\nf 617/610/577 619/613/580 598/589/556\nf 620/616/583 622/617/584 623/618/585\nf 624/619/586 625/620/587 623/618/585\nf 211/205/182 623/618/585 625/620/587\nf 210/615/582 621/614/581 623/618/585\nf 626/621/588 627/622/589 625/620/587\nf 612/623/590 615/609/576 627/622/589\nf 611/604/571 627/622/589 615/609/576\nf 212/206/183 625/620/587 627/622/589\nf 427/422/388 628/624/591 629/625/592\nf 630/626/593 628/624/591 427/422/388\nf 582/574/541 628/624/591 630/626/593\nf 580/591/558 629/625/592 628/624/591\nf 426/421/387 434/430/396 631/627/594\nf 632/628/595 631/627/594 434/430/396\nf 204/200/177 610/603/570 631/627/594\nf 630/626/593 631/627/594 610/603/570\nf 440/435/401 633/629/596 632/628/595\nf 439/439/405 634/630/597 633/629/596\nf 208/203/180 205/201/178 633/629/596\nf 632/628/595 633/629/596 205/201/178\nf 443/438/404 635/631/598 634/630/597\nf 428/632/599 629/625/592 635/631/598\nf 599/590/557 635/631/598 629/625/592\nf 208/203/180 634/630/597 635/631/598\nf 637/633/600 638/634/601 639/635/602\nf 640/636/603 641/637/604 638/634/601\nf 613/607/574 638/634/601 641/637/604\nf 612/623/590 639/635/602 638/634/601\nf 640/636/603 642/638/605 643/639/606\nf 645/640/607 643/639/606 642/638/605\nf 620/616/583 618/612/579 643/639/606\nf 641/637/604 643/639/606 618/612/579\nf 644/641/608 646/642/609 647/643/610\nf 649/644/611 647/643/610 646/642/609\nf 624/619/586 622/617/584 647/643/610\nf 645/640/607 647/643/610 622/617/584\nf 648/645/612 650/646/613 651/647/614\nf 639/635/602 651/647/614 650/646/613\nf 626/621/588 651/647/614 639/635/602\nf 624/619/586 649/644/611 651/647/614\nf 653/648/615 654/649/616 655/650/617\nf 656/651/618 657/652/619 654/649/616\nf 637/633/600 654/649/616 657/652/619\nf 636/653/620 655/650/617 654/649/616\nf 658/654/621 659/655/622 657/652/619\nf 661/656/623 659/655/622 658/654/621\nf 644/641/608 642/638/605 659/655/622\nf 640/636/603 657/652/619 659/655/622\nf 660/657/624 662/658/625 663/659/626\nf 665/660/627 663/659/626 662/658/625\nf 648/645/612 646/642/609 663/659/626\nf 661/656/623 663/659/626 646/642/609\nf 664/661/628 666/662/629 667/663/630\nf 655/650/617 667/663/630 666/662/629\nf 636/653/620 650/646/613 667/663/630\nf 665/660/627 667/663/630 650/646/613\nf 668/664/631 669/665/632 670/666/633\nf 673/667/634 670/666/633 669/665/632\nf 64/49/48 670/666/633 673/667/634\nf 63/555/522 671/668/635 670/666/633\nf 672/669/636 674/670/637 675/671/638\nf 677/672/639 675/671/638 674/670/637\nf 540/534/501 571/566/533 675/671/638\nf 673/667/634 675/671/638 571/566/533\nf 676/673/640 678/674/641 679/675/642\nf 680/676/643 681/677/644 679/675/642\nf 541/535/502 679/675/642 681/677/644\nf 677/672/639 679/675/642 541/535/502\nf 682/678/645 683/679/646 681/677/644\nf 668/664/631 671/668/635 683/679/646\nf 560/554/521 683/679/646 671/668/635\nf 544/537/504 681/677/644 683/679/646\nf 685/680/647 686/681/648 687/682/649\nf 688/683/650 689/684/651 686/681/648\nf 672/669/636 669/665/632 686/681/648\nf 687/682/649 686/681/648 669/665/632\nf 688/683/650 690/685/652 691/686/653\nf 693/687/654 691/686/653 690/685/652\nf 676/673/640 674/670/637 691/686/653\nf 689/684/651 691/686/653 674/670/637\nf 692/688/655 694/689/656 695/690/657\nf 697/691/658 695/690/657 694/689/656\nf 678/674/641 695/690/657 697/691/658\nf 693/687/654 695/690/657 678/674/641\nf 698/692/659 699/693/660 697/691/658\nf 684/694/661 687/682/649 699/693/660\nf 682/678/645 699/693/660 687/682/649\nf 680/676/643 697/691/658 699/693/660\nf 700/695/662 701/696/663 702/697/664\nf 705/698/665 702/697/664 701/696/663\nf 685/680/647 702/697/664 705/698/665\nf 684/694/661 703/699/666 702/697/664\nf 704/700/667 706/701/668 707/702/669\nf 708/703/670 709/704/671 707/702/669\nf 692/688/655 690/685/652 707/702/669\nf 705/698/665 707/702/669 690/685/652\nf 710/705/672 711/706/673 709/704/671\nf 712/707/674 713/708/675 711/706/673\nf 694/689/656 711/706/673 713/708/675\nf 709/704/671 711/706/673 694/689/656\nf 714/709/676 715/710/677 713/708/675\nf 703/699/666 715/710/677 714/709/676\nf 698/692/659 715/710/677 703/699/666\nf 696/711/678 713/708/675 715/710/677\nf 716/712/679 717/713/680 718/714/681\nf 721/715/682 718/714/681 717/713/680\nf 656/651/618 653/648/615 718/714/681\nf 719/716/683 718/714/681 653/648/615\nf 720/717/684 722/718/685 723/719/686\nf 724/720/687 725/721/688 723/719/686\nf 660/657/624 658/654/621 723/719/686\nf 721/715/682 723/719/686 658/654/621\nf 726/722/689 727/723/690 725/721/688\nf 728/724/691 729/725/692 727/723/690\nf 662/658/625 727/723/690 729/725/692\nf 660/657/624 725/721/688 727/723/690\nf 728/724/691 730/726/693 731/727/694\nf 719/716/683 731/727/694 730/726/693\nf 652/728/695 666/662/629 731/727/694\nf 729/725/692 731/727/694 666/662/629\nf 733/729/696 734/730/697 735/731/698\nf 736/732/699 737/733/700 734/730/697\nf 410/405/371 734/730/697 737/733/700\nf 151/150/128 735/731/698 734/730/697\nf 738/734/701 739/735/702 737/733/700\nf 741/736/703 739/735/702 738/734/701\nf 7/737/704 27/19/19 739/735/702\nf 737/733/700 739/735/702 27/19/19\nf 740/738/705 742/739/706 743/740/707\nf 745/741/708 743/740/707 742/739/706\nf 149/742/709 746/743/710 743/740/707\nf 741/736/703 743/740/707 746/743/710\nf 747/744/711 748/745/712 745/741/708\nf 732/746/713 735/731/698 748/745/712\nf 150/149/127 748/745/712 735/731/698\nf 745/741/708 748/745/712 150/149/127\nf 750/747/714 751/748/715 752/749/716\nf 753/750/717 754/751/718 751/748/715\nf 56/43/42 751/748/715 754/751/718\nf 55/144/122 752/749/716 751/748/715\nf 755/752/719 756/753/720 754/751/718\nf 757/754/721 758/755/722 756/753/720\nf 22/416/382 21/14/14 756/753/720\nf 754/751/718 756/753/720 21/14/14\nf 757/754/721 759/756/723 760/757/724\nf 762/758/725 760/757/724 759/756/723\nf 142/759/726 418/413/379 760/757/724\nf 758/755/722 760/757/724 418/413/379\nf 761/760/727 763/761/728 764/762/729\nf 752/749/716 764/762/729 763/761/728\nf 143/143/121 764/762/729 752/749/716\nf 762/758/725 764/762/729 143/143/121\nf 766/763/730 767/764/731 768/765/732\nf 769/766/733 770/767/734 767/764/731\nf 704/700/667 701/696/663 767/764/731\nf 768/765/732 767/764/731 701/696/663\nf 771/768/735 772/769/736 770/767/734\nf 774/770/737 772/769/736 771/768/735\nf 706/701/668 772/769/736 774/770/737\nf 770/767/734 772/769/736 706/701/668\nf 773/771/738 775/772/739 776/773/740\nf 778/774/741 776/773/740 775/772/739\nf 710/705/672 776/773/740 778/774/741\nf 708/703/670 774/770/737 776/773/740\nf 777/775/742 779/776/743 780/777/744\nf 765/778/745 768/765/732 780/777/744\nf 700/695/662 714/709/676 780/777/744\nf 712/707/674 778/774/741 780/777/744\nf 782/779/746 783/780/747 784/781/748\nf 785/782/749 786/783/750 783/780/747\nf 766/763/730 783/780/747 786/783/750\nf 765/778/745 784/781/748 783/780/747\nf 787/784/751 788/785/752 786/783/750\nf 790/786/753 788/785/752 787/784/751\nf 773/771/738 771/768/735 788/785/752\nf 769/766/733 786/783/750 788/785/752\nf 789/787/754 791/788/755 792/789/756\nf 794/790/757 792/789/756 791/788/755\nf 777/775/742 775/772/739 792/789/756\nf 790/786/753 792/789/756 775/772/739\nf 793/791/758 795/792/759 796/793/760\nf 781/794/761 784/781/748 796/793/760\nf 779/776/743 796/793/760 784/781/748\nf 794/790/757 796/793/760 779/776/743\nf 798/795/762 799/796/763 800/797/764\nf 801/798/765 802/799/766 799/796/763\nf 782/779/746 799/796/763 802/799/766\nf 781/794/761 800/797/764 799/796/763\nf 803/800/767 804/801/768 802/799/766\nf 806/802/769 804/801/768 803/800/767\nf 789/787/754 787/784/751 804/801/768\nf 785/782/749 802/799/766 804/801/768\nf 805/803/770 807/804/771 808/805/772\nf 810/806/773 808/805/772 807/804/771\nf 793/791/758 791/788/755 808/805/772\nf 806/802/769 808/805/772 791/788/755\nf 809/807/774 811/808/775 812/809/776\nf 797/810/777 800/797/764 812/809/776\nf 795/792/759 812/809/776 800/797/764\nf 810/806/773 812/809/776 795/792/759\nf 569/563/530 568/562/529 813/811/778\nf 567/561/528 815/812/779 813/811/778\nf 798/795/762 813/811/778 815/812/779\nf 797/810/777 814/813/780 813/811/778\nf 567/561/528 566/560/527 816/814/781\nf 565/559/526 817/815/782 816/814/781\nf 805/803/770 803/800/767 816/814/781\nf 801/798/765 815/812/779 816/814/781\nf 565/559/526 562/557/524 818/816/783\nf 561/556/523 819/817/784 818/816/783\nf 809/807/774 807/804/771 818/816/783\nf 817/815/782 818/816/783 807/804/771\nf 561/556/523 564/564/531 820/818/785\nf 569/563/530 814/813/780 820/818/785\nf 811/808/775 820/818/785 814/813/780\nf 819/817/784 820/818/785 811/808/775\nf 596/586/553 595/585/552 821/819/786\nf 594/584/551 823/820/787 821/819/786\nf 717/713/680 821/819/786 823/820/787\nf 716/712/679 822/821/788 821/819/786\nf 594/584/551 593/583/550 824/822/789\nf 592/582/549 825/823/790 824/822/789\nf 722/718/685 824/822/789 825/823/790\nf 823/820/787 824/822/789 722/718/685\nf 592/582/549 589/580/547 826/824/791\nf 588/579/546 827/825/792 826/824/791\nf 728/724/691 726/722/689 826/824/791\nf 825/823/790 826/824/791 726/722/689\nf 588/579/546 591/587/554 828/826/793\nf 596/586/553 822/821/788 828/826/793\nf 716/712/679 730/726/693 828/826/793\nf 827/825/792 828/826/793 730/726/693\nf 829/827/794 830/828/795 831/829/796\nf 834/830/797 831/829/796 830/828/795\nf 733/729/696 831/829/796 834/830/797\nf 732/746/713 832/831/798 831/829/796\nf 835/832/799 836/833/800 834/830/797\nf 837/834/801 838/835/802 836/833/800\nf 738/734/701 836/833/800 838/835/802\nf 736/732/699 834/830/797 836/833/800\nf 839/836/803 840/837/804 841/838/805\nf 843/839/806 844/840/807 841/838/805\nf 845/841/808 846/842/809 841/838/805\nf 847/843/810 842/844/811 841/838/805\nf 848/845/812 849/846/813 850/847/814\nf 832/831/798 850/847/814 849/846/813\nf 747/744/711 850/847/814 832/831/798\nf 851/848/815 850/847/814 747/744/711\nf 847/843/810 846/842/809 852/849/816\nf 845/841/808 854/850/817 852/849/816\nf 742/739/706 852/849/816 854/850/817\nf 853/851/818 852/849/816 742/739/706\nf 844/840/807 855/852/819 854/850/817\nf 843/839/806 856/853/820 855/852/819\nf 851/848/815 855/852/819 856/853/820\nf 744/854/821 854/850/817 855/852/819\nf 843/839/806 840/837/804 857/855/822\nf 839/836/803 858/856/823 857/855/822\nf 859/857/824 857/855/822 858/856/823\nf 848/845/812 856/853/820 857/855/822\nf 839/836/803 842/844/811 860/858/825\nf 853/851/818 860/858/825 842/844/811\nf 740/738/705 838/835/802 860/858/825\nf 858/856/823 860/858/825 838/835/802\nf 861/859/826 862/860/827 863/861/828\nf 865/862/829 866/863/830 863/861/828\nf 227/224/831 863/861/828 866/863/830\nf 864/864/832 863/861/828 227/224/831\nf 867/865/833 868/866/834 866/863/830\nf 870/867/835 868/866/834 867/865/833\nf 193/189/166 868/866/834 870/867/835\nf 866/863/830 868/866/834 193/189/166\nf 871/868/836 872/869/837 870/867/835\nf 873/870/838 874/871/839 872/869/837\nf 746/743/710 872/869/837 874/871/839\nf 870/867/835 872/869/837 746/743/710\nf 875/872/840 876/873/841 874/871/839\nf 864/864/832 876/873/841 875/872/840\nf 8/5/5 876/873/841 864/864/832\nf 7/737/704 874/871/839 876/873/841\nf 41/32/31 40/31/30 877/874/842\nf 39/30/29 879/875/843 877/874/842\nf 862/860/827 877/874/842 879/875/843\nf 878/876/844 877/874/842 862/860/827\nf 39/30/29 38/29/28 880/877/845\nf 881/878/846 880/877/845 38/29/28\nf 867/865/833 880/877/845 881/878/846\nf 865/862/829 879/875/843 880/877/845\nf 883/879/847 884/880/848 885/881/849\nf 887/882/850 884/880/848 883/879/847\nf 889/883/851 884/880/848 887/882/850\nf 885/881/849 884/880/848 889/883/851\nf 36/33/32 891/884/852 892/885/853\nf 41/32/31 878/876/844 891/884/852\nf 861/859/826 875/872/840 891/884/852\nf 892/885/853 891/884/852 875/872/840\nf 894/886/854 895/887/855 896/888/856\nf 897/889/857 898/890/858 895/887/855\nf 871/868/836 895/887/855 898/890/858\nf 869/891/859 896/888/856 895/887/855\nf 899/892/860 900/893/861 898/890/858\nf 902/894/862 900/893/861 899/892/860\nf 33/26/25 892/885/853 900/893/861\nf 898/890/858 900/893/861 892/885/853\nf 901/895/863 903/896/864 904/897/865\nf 906/898/866 904/897/865 903/896/864\nf 37/899/867 34/27/26 904/897/865\nf 902/894/862 904/897/865 34/27/26\nf 907/900/868 908/901/869 906/898/866\nf 893/902/870 896/888/856 908/901/869\nf 881/878/846 908/901/869 896/888/856\nf 37/899/867 906/898/866 908/901/869\nf 910/903/871 911/904/872 912/905/873\nf 913/906/874 914/907/875 911/904/872\nf 894/886/854 911/904/872 914/907/875\nf 912/905/873 911/904/872 894/886/854\nf 915/908/876 916/909/877 914/907/875\nf 917/910/878 918/911/879 916/909/877\nf 899/892/860 916/909/877 918/911/879\nf 897/889/857 914/907/875 916/909/877\nf 917/910/878 919/912/880 920/913/881\nf 922/914/882 920/913/881 919/912/880\nf 903/896/864 920/913/881 922/914/882\nf 918/911/879 920/913/881 903/896/864\nf 921/915/883 923/916/884 924/917/885\nf 912/905/873 924/917/885 923/916/884\nf 893/902/870 907/900/868 924/917/885\nf 922/914/882 924/917/885 907/900/868\nf 926/918/886 927/919/887 928/920/888\nf 929/921/889 930/922/890 927/919/887\nf 910/903/871 927/919/887 930/922/890\nf 909/923/891 928/920/888 927/919/887\nf 931/924/892 932/925/893 930/922/890\nf 934/926/894 932/925/893 931/924/892\nf 917/910/878 915/908/876 932/925/893\nf 913/906/874 930/922/890 932/925/893\nf 933/927/895 935/928/896 936/929/897\nf 938/930/898 936/929/897 935/928/896\nf 921/915/883 919/912/880 936/929/897\nf 934/926/894 936/929/897 919/912/880\nf 937/931/899 939/932/900 940/933/901\nf 925/934/902 928/920/888 940/933/901\nf 923/916/884 940/933/901 928/920/888\nf 938/930/898 940/933/901 923/916/884\nf 942/935/903 943/936/904 944/937/905\nf 945/938/906 946/939/907 943/936/904\nf 256/263/229 943/936/904 946/939/907\nf 255/279/245 944/937/905 943/936/904\nf 947/940/908 948/941/909 946/939/907\nf 950/942/910 948/941/909 947/940/908\nf 263/271/237 261/268/234 948/941/909\nf 259/266/232 946/939/907 948/941/909\nf 949/943/911 951/944/912 952/945/913\nf 954/946/914 952/945/913 951/944/912\nf 267/276/242 265/272/238 952/945/913\nf 950/942/910 952/945/913 265/272/238\nf 953/947/915 955/948/916 956/949/917\nf 941/950/918 944/937/905 956/949/917\nf 269/277/243 956/949/917 944/937/905\nf 954/946/914 956/949/917 269/277/243\nf 178/951/919 177/175/152 957/952/920\nf 176/174/151 959/953/921 957/952/920\nf 945/938/906 942/935/903 957/952/920\nf 958/954/922 957/952/920 942/935/903\nf 176/174/151 175/173/150 960/955/923\nf 174/172/149 961/956/924 960/955/923\nf 947/940/908 960/955/923 961/956/924\nf 959/953/921 960/955/923 947/940/908\nf 174/172/149 171/169/146 962/957/925\nf 170/958/926 963/959/927 962/957/925\nf 951/944/912 962/957/925 963/959/927\nf 949/943/911 961/956/924 962/957/925\nf 170/958/926 173/171/148 964/960/928\nf 178/951/919 958/954/922 964/960/928\nf 941/950/918 955/948/916 964/960/928\nf 953/947/915 963/959/927 964/960/928\nf 608/600/567 607/599/566 965/961/929\nf 606/598/565 967/962/930 965/961/929\nf 448/445/411 445/441/407 965/961/929\nf 966/963/931 965/961/929 445/441/407\nf 606/598/565 605/597/564 968/964/932\nf 604/596/563 969/965/933 968/964/932\nf 450/446/412 968/964/932 969/965/933\nf 967/962/930 968/964/932 450/446/412\nf 601/594/561 970/966/934 969/965/933\nf 600/593/560 971/967/935 970/966/934\nf 454/450/416 970/966/934 971/967/935\nf 452/448/414 969/965/933 970/966/934\nf 600/593/560 603/601/568 972/968/936\nf 608/600/567 966/963/931 972/968/936\nf 444/440/406 458/454/420 972/968/936\nf 456/453/419 971/967/935 972/968/936\nf 973/969/937 974/970/938 975/971/939\nf 978/972/940 975/971/939 974/970/938\nf 753/750/717 750/747/714 975/971/939\nf 976/973/941 975/971/939 750/747/714\nf 977/974/942 979/975/943 980/976/944\nf 981/977/945 982/978/946 980/976/944\nf 755/752/719 980/976/944 982/978/946\nf 753/750/717 978/972/940 980/976/944\nf 983/979/947 984/980/948 982/978/946\nf 985/981/949 986/982/950 984/980/948\nf 759/756/723 984/980/948 986/982/950\nf 757/754/721 982/978/946 984/980/948\nf 987/983/951 988/984/952 986/982/950\nf 976/973/941 988/984/952 987/983/951\nf 749/985/953 763/761/728 988/984/952\nf 761/760/727 986/982/950 988/984/952\nf 990/986/954 991/987/955 992/988/956\nf 993/989/957 994/990/958 991/987/955\nf 995/991/959 996/992/960 991/987/955\nf 997/993/961 992/988/956 991/987/955\nf 998/994/962 999/995/963 1000/996/964\nf 1002/997/965 1003/998/966 1000/996/964\nf 979/975/943 1000/996/964 1003/998/966\nf 1001/999/967 1000/996/964 979/975/943\nf 1002/997/965 1004/1000/968 1005/1001/969\nf 1006/1002/970 1007/1003/971 1005/1001/969\nf 983/979/947 1005/1001/969 1007/1003/971\nf 981/977/945 1003/998/966 1005/1001/969\nf 1006/1002/970 1008/1004/972 1009/1005/973\nf 1010/1006/974 1011/1007/975 1009/1005/973\nf 973/969/937 987/983/951 1009/1005/973\nf 985/981/949 1007/1003/971 1009/1005/973\nf 347/1008/976 378/376/342 1012/1009/977\nf 336/1010/978 334/339/305 1012/1009/977\nf 332/336/302 329/334/300 1012/1009/977\nf 328/333/299 348/348/314 1012/1009/977\nf 1013/1011/979 1014/1012/980 184/179/156\nf 1015/1013/981 1016/1014/982 1014/1012/980\nf 405/400/366 1014/1012/980 1016/1014/982\nf 185/180/157 184/179/156 1014/1012/980\nf 1017/1015/983 1018/1016/984 1016/1014/982\nf 1019/1017/985 1020/1018/986 1018/1016/984\nf 411/406/372 1018/1016/984 1020/1018/986\nf 408/403/369 1016/1014/982 1018/1016/984\nf 1019/1017/985 1021/1019/987 1022/1020/988\nf 182/182/159 1022/1020/988 1021/1019/987\nf 187/414/380 415/410/376 1022/1020/988\nf 1020/1018/986 1022/1020/988 415/410/376\nf 1023/1021/989 1024/1022/990 1025/1023/991\nf 1028/1024/992 1025/1023/991 1024/1022/990\nf 1015/1013/981 1013/1011/979 1025/1023/991\nf 1026/1025/993 1025/1023/991 1013/1011/979\nf 1027/1026/994 1029/1027/995 1030/1028/996\nf 1031/1029/997 1032/1030/998 1030/1028/996\nf 1017/1015/983 1030/1028/996 1032/1030/998\nf 1028/1024/992 1030/1028/996 1017/1015/983\nf 1033/1031/999 1034/1032/1000 1032/1030/998\nf 1035/1033/1001 1036/1034/1002 1034/1032/1000\nf 1021/1019/987 1034/1032/1000 1036/1034/1002\nf 1019/1017/985 1032/1030/998 1034/1032/1000\nf 1037/1035/1003 1038/1036/1004 1036/1034/1002\nf 1026/1025/993 1038/1036/1004 1037/1035/1003\nf 183/1037/1005 180/177/154 1038/1036/1004\nf 179/176/153 1036/1034/1002 1038/1036/1004\nf 1040/1038/1006 1041/1039/1007 1042/1040/1008\nf 1043/1041/1009 1044/1042/1010 1041/1039/1007\nf 1027/1026/994 1024/1022/990 1041/1039/1007\nf 1042/1040/1008 1041/1039/1007 1024/1022/990\nf 1045/1043/1011 1046/1044/1012 1044/1042/1010\nf 1048/1045/1013 1046/1044/1012 1045/1043/1011\nf 1029/1027/995 1046/1044/1012 1048/1045/1013\nf 1044/1042/1010 1046/1044/1012 1029/1027/995\nf 1047/1046/1014 1049/1047/1015 1050/1048/1016\nf 1052/1049/1017 1050/1048/1016 1049/1047/1015\nf 1033/1031/999 1050/1048/1016 1052/1049/1017\nf 1031/1029/997 1048/1045/1013 1050/1048/1016\nf 1051/1050/1018 1053/1051/1019 1054/1052/1020\nf 1039/1053/1021 1042/1040/1008 1054/1052/1020\nf 1023/1021/989 1037/1035/1003 1054/1052/1020\nf 1035/1033/1001 1052/1049/1017 1054/1052/1020\nf 50/1054/1022 49/38/37 1055/1055/1023\nf 48/1056/1024 1057/1057/1025 1055/1055/1023\nf 1040/1038/1006 1055/1055/1023 1057/1057/1025\nf 1039/1053/1021 1056/1058/1026 1055/1055/1023\nf 48/1056/1024 47/37/36 1058/1059/1027\nf 46/1060/1028 1059/1061/1029 1058/1059/1027\nf 1047/1046/1014 1045/1043/1011 1058/1059/1027\nf 1043/1041/1009 1057/1057/1025 1058/1059/1027\nf 46/1060/1028 43/34/33 1060/1062/1030\nf 42/1063/1031 1061/1064/1032 1060/1062/1030\nf 1051/1050/1018 1049/1047/1015 1060/1062/1030\nf 1059/1061/1029 1060/1062/1030 1049/1047/1015\nf 42/1063/1031 45/36/35 1062/1065/1033\nf 50/1054/1022 1056/1058/1026 1062/1065/1033\nf 1053/1051/1019 1062/1065/1033 1056/1058/1026\nf 1061/1064/1032 1062/1065/1033 1053/1051/1019\nf 401/1066/1034 400/394/360 1063/1067/1035\nf 399/1068/1036 1065/1069/1037 1063/1067/1035\nf 493/485/452 1063/1067/1035 1065/1069/1037\nf 492/501/468 1064/1070/1038 1063/1067/1035\nf 399/1068/1036 398/393/359 1066/1071/1039\nf 397/1072/1040 1067/1073/1041 1066/1071/1039\nf 500/494/461 498/490/457 1066/1071/1039\nf 496/488/455 1065/1069/1037 1066/1071/1039\nf 397/1072/1040 394/390/356 1068/1074/1042\nf 393/1075/1043 1069/1076/1044 1068/1074/1042\nf 504/498/465 502/495/462 1068/1074/1042\nf 1067/1073/1041 1068/1074/1042 502/495/462\nf 393/1075/1043 396/392/358 1070/1077/1045\nf 401/1066/1034 1064/1070/1038 1070/1077/1045\nf 506/499/466 1070/1077/1045 1064/1070/1038\nf 1069/1076/1044 1070/1077/1045 506/499/466\nf 1071/1078/1046 1072/1079/1047 1073/1080/1048\nf 1076/1081/1049 1073/1080/1048 1072/1079/1047\nf 346/469/436 345/347/313 1073/1080/1048\nf 1074/1082/1050 1073/1080/1048 345/347/313\nf 1075/1083/1051 1077/1084/1052 1078/1085/1053\nf 1079/1086/1054 1080/1087/1055 1078/1085/1053\nf 381/1088/1056 475/468/435 1078/1085/1053\nf 1076/1081/1049 1078/1085/1053 475/468/435\nf 1081/1089/1057 1082/1090/1058 1080/1087/1055\nf 1083/1091/1059 1084/1092/1060 1082/1090/1058\nf 382/379/345 1082/1090/1058 1084/1092/1060\nf 381/1088/1056 1080/1087/1055 1082/1090/1058\nf 1083/1091/1059 1085/1093/1061 1086/1094/1062\nf 1074/1082/1050 1086/1094/1062 1085/1093/1061\nf 464/460/426 1086/1094/1062 1074/1082/1050\nf 385/382/348 1084/1092/1060 1086/1094/1062\nf 1087/1095/1063 1088/1096/1064 1089/1097/1065\nf 1092/1098/1066 1089/1097/1065 1088/1096/1064\nf 1075/1083/1051 1072/1079/1047 1089/1097/1065\nf 1090/1099/1067 1089/1097/1065 1072/1079/1047\nf 1093/1100/1068 1094/1101/1069 1092/1098/1066\nf 1095/1102/1070 1096/1103/1071 1094/1101/1069\nf 1077/1084/1052 1094/1101/1069 1096/1103/1071\nf 1092/1098/1066 1094/1101/1069 1077/1084/1052\nf 1097/1104/1072 1098/1105/1073 1096/1103/1071\nf 1099/1106/1074 1100/1107/1075 1098/1105/1073\nf 1081/1089/1057 1098/1105/1073 1100/1107/1075\nf 1079/1086/1054 1096/1103/1071 1098/1105/1073\nf 1099/1106/1074 1101/1108/1076 1102/1109/1077\nf 1090/1099/1067 1102/1109/1077 1101/1108/1076\nf 1071/1078/1046 1085/1093/1061 1102/1109/1077\nf 1100/1107/1075 1102/1109/1077 1085/1093/1061\nf 1104/1110/1078 1105/1111/1079 1106/1112/1080\nf 1107/1113/1081 1108/1114/1082 1105/1111/1079\nf 1088/1096/1064 1105/1111/1079 1108/1114/1082\nf 1087/1095/1063 1106/1112/1080 1105/1111/1079\nf 1109/1115/1083 1110/1116/1084 1108/1114/1082\nf 1112/1117/1085 1110/1116/1084 1109/1115/1083\nf 1095/1102/1070 1093/1100/1068 1110/1116/1084\nf 1091/1118/1086 1108/1114/1082 1110/1116/1084\nf 1111/1119/1087 1113/1120/1088 1114/1121/1089\nf 1116/1122/1090 1114/1121/1089 1113/1120/1088\nf 1099/1106/1074 1097/1104/1072 1114/1121/1089\nf 1112/1117/1085 1114/1121/1089 1097/1104/1072\nf 1115/1123/1091 1117/1124/1092 1118/1125/1093\nf 1103/1126/1094 1106/1112/1080 1118/1125/1093\nf 1101/1108/1076 1118/1125/1093 1106/1112/1080\nf 1116/1122/1090 1118/1125/1093 1101/1108/1076\nf 1119/1127/1095 1120/1128/1096 1121/1129/1097\nf 1124/1130/1098 1121/1129/1097 1120/1128/1096\nf 1107/1113/1081 1104/1110/1078 1121/1129/1097\nf 1103/1126/1094 1122/1131/1099 1121/1129/1097\nf 1123/1132/1100 1125/1133/1101 1126/1134/1102\nf 1128/1135/1103 1126/1134/1102 1125/1133/1101\nf 1111/1119/1087 1109/1115/1083 1126/1134/1102\nf 1124/1130/1098 1126/1134/1102 1109/1115/1083\nf 1129/1136/1104 1130/1137/1105 1128/1135/1103\nf 1131/464/1106 1132/1138/1107 1130/1137/1105\nf 1113/1120/1088 1130/1137/1105 1132/1138/1107\nf 1111/1119/1087 1128/1135/1103 1130/1137/1105\nf 1133/1139/1108 1134/1140/1109 1132/1138/1107\nf 1119/1127/1095 1122/1131/1099 1134/1140/1109\nf 1117/1124/1092 1134/1140/1109 1122/1131/1099\nf 1115/1123/1091 1132/1138/1107 1134/1140/1109\nf 473/1141/1110 472/466/433 1135/1142/1111\nf 471/1143/1112 1137/1144/1113 1135/1142/1111\nf 1120/1128/1096 1135/1142/1111 1137/1144/1113\nf 1119/1127/1095 1136/1145/1114 1135/1142/1111\nf 471/1143/1112 470/465/431 1138/1146/1115\nf 469/1147/1116 1139/1148/1117 1138/1146/1115\nf 1127/1149/1118 1125/1133/1101 1138/1146/1115\nf 1137/1144/1113 1138/1146/1115 1125/1133/1101\nf 469/1147/1116 466/462/428 1140/1150/1119\nf 465/1151/1120 1141/1152/1121 1140/1150/1119\nf 1131/464/1106 1129/1136/1104 1140/1150/1119\nf 1139/1148/1117 1140/1150/1119 1129/1136/1104\nf 465/1151/1120 468/464/430 1142/1153/1122\nf 473/1141/1110 1136/1145/1114 1142/1153/1122\nf 1133/1139/1108 1142/1153/1122 1136/1145/1114\nf 1131/464/1106 1141/1152/1121 1142/1153/1122\nf 1143/1154/1123 1144/1155/1124 1145/1156/1125\nf 1148/1157/1126 1145/1156/1125 1144/1155/1124\nf 833/1158/1127 830/828/795 1145/1156/1125\nf 1146/1159/1128 1145/1156/1125 830/828/795\nf 1147/1160/1129 1149/1161/1130 1150/1162/1131\nf 1151/1163/1132 1152/1164/1133 1150/1162/1131\nf 835/832/799 1150/1162/1131 1152/1164/1133\nf 833/1158/1127 1148/1157/1126 1150/1162/1131\nf 1153/1165/1134 1154/1166/1135 1152/1164/1133\nf 1155/1167/1136 1156/1168/1137 1154/1166/1135\nf 859/857/824 1154/1166/1135 1156/1168/1137\nf 1152/1164/1133 1154/1166/1135 859/857/824\nf 1157/1169/1138 1158/1170/1139 1156/1168/1137\nf 1146/1159/1128 1158/1170/1139 1157/1169/1138\nf 829/827/794 849/846/813 1158/1170/1139\nf 1156/1168/1137 1158/1170/1139 849/846/813\nf 1159/1171/1140 1160/1172/1141 1161/1173/1142\nf 1162/1174/1143 1163/1175/1144 1160/1172/1141\nf 1144/1155/1124 1160/1172/1141 1163/1175/1144\nf 1143/1154/1123 1161/1173/1142 1160/1172/1141\nf 1164/1176/1145 1165/1177/1146 1163/1175/1144\nf 1167/1178/1147 1165/1177/1146 1164/1176/1145\nf 1151/1163/1132 1149/1161/1130 1165/1177/1146\nf 1147/1160/1129 1163/1175/1144 1165/1177/1146\nf 1166/1179/1148 1168/1180/1149 1169/1181/1150\nf 1170/1182/1151 1169/1181/1150 1168/1180/1149\nf 1155/1167/1136 1153/1165/1134 1169/1181/1150\nf 1167/1178/1147 1169/1181/1150 1153/1165/1134\nf 306/1183/1152 305/311/277 1171/1184/1153\nf 304/1185/1154 1161/1173/1142 1171/1184/1153\nf 1157/1169/1138 1171/1184/1153 1161/1173/1142\nf 1170/1182/1151 1171/1184/1153 1157/1169/1138\nf 355/353/319 1172/1186/1155 303/310/276\nf 354/359/325 1173/1187/1156 1172/1186/1155\nf 1159/1171/1140 1172/1186/1155 1173/1187/1156\nf 304/1185/1154 303/310/276 1172/1186/1155\nf 359/358/324 1174/1188/1157 1173/1187/1156\nf 1175/1189/1158 1174/1188/1157 359/358/324\nf 1166/1179/1148 1164/1176/1145 1174/1188/1157\nf 1162/1174/1143 1173/1187/1156 1174/1188/1157\nf 358/357/323 362/362/328 1176/1190/1159\nf 301/312/278 1176/1190/1159 362/362/328\nf 306/1183/1152 1168/1180/1149 1176/1190/1159\nf 1175/1189/1158 1176/1190/1159 1168/1180/1149\nf 1006/1002/970 1004/1000/968 1177/1191/1160\nf 1002/997/965 999/995/963 1177/1191/1160\nf 998/994/962 1178/1192/1161 1177/1191/1160\nf 1008/1004/972 1177/1191/1160 1178/1192/1161\nf 1179/1193/1162 1180/1194/1163 1181/1195/1164\nf 1184/1196/1165 1181/1195/1164 1180/1194/1163\nf 977/974/942 974/970/938 1181/1195/1164\nf 1182/1197/1166 1181/1195/1164 974/970/938\nf 1185/1198/1167 1186/1199/1168 1184/1196/1165\nf 1188/1200/1169 1186/1199/1168 1185/1198/1167\nf 1001/999/967 1186/1199/1168 1188/1200/1169\nf 1184/1196/1165 1186/1199/1168 1001/999/967\nf 1187/1201/1170 1189/1202/1171 1190/1203/1172\nf 1192/1204/1173 1190/1203/1172 1189/1202/1171\nf 1010/1006/974 1178/1192/1161 1190/1203/1172\nf 1188/1200/1169 1190/1203/1172 1178/1192/1161\nf 1191/1205/1174 1193/1206/1175 1194/1207/1176\nf 1182/1197/1166 1194/1207/1176 1193/1206/1175\nf 1011/1007/975 1194/1207/1176 1182/1197/1166\nf 1192/1204/1173 1194/1207/1176 1011/1007/975\nf 997/993/961 996/992/960 1195/1208/1177\nf 995/991/959 1197/1209/1178 1195/1208/1177\nf 1183/1210/1179 1180/1194/1163 1195/1208/1177\nf 1196/1211/1180 1195/1208/1177 1180/1194/1163\nf 995/991/959 994/990/958 1198/1212/1181\nf 993/989/957 1199/1213/1182 1198/1212/1181\nf 1185/1198/1167 1198/1212/1181 1199/1213/1182\nf 1183/1210/1179 1197/1209/1178 1198/1212/1181\nf 1200/1214/1183 1201/1215/1184 1202/1216/1185\nf 1204/1217/1186 1205/1218/1187 1202/1216/1185\nf 1206/1219/1188 1207/1220/1189 1202/1216/1185\nf 1208/1221/1190 1203/1222/1191 1202/1216/1185\nf 992/988/956 1209/1223/1192 1210/1224/1193\nf 997/993/961 1196/1211/1180 1209/1223/1192\nf 1193/1206/1175 1209/1223/1192 1196/1211/1180\nf 1210/1224/1193 1209/1223/1192 1193/1206/1175\nf 1211/1225/1194 1212/1226/1195 1213/1227/1196\nf 1216/1228/1197 1213/1227/1196 1212/1226/1195\nf 1191/1205/1174 1189/1202/1171 1213/1227/1196\nf 1214/1229/1198 1213/1227/1196 1189/1202/1171\nf 1215/1230/1199 1217/1231/1200 1218/1232/1201\nf 1220/1233/1202 1218/1232/1201 1217/1231/1200\nf 989/1234/1203 1210/1224/1193 1218/1232/1201\nf 1216/1228/1197 1218/1232/1201 1210/1224/1193\nf 1221/1235/1204 1222/1236/1205 1220/1233/1202\nf 1223/1237/1206 1224/1238/1207 1222/1236/1205\nf 990/986/954 1222/1236/1205 1224/1238/1207\nf 989/1234/1203 1220/1233/1202 1222/1236/1205\nf 1225/1239/1208 1226/1240/1209 1224/1238/1207\nf 1211/1225/1194 1214/1229/1198 1226/1240/1209\nf 1199/1213/1182 1226/1240/1209 1214/1229/1198\nf 993/989/957 1224/1238/1207 1226/1240/1209\nf 1227/1241/1210 1228/1242/1211 1229/1243/1212\nf 1232/1244/1213 1229/1243/1212 1228/1242/1211\nf 1215/1230/1199 1212/1226/1195 1229/1243/1212\nf 1230/1245/1214 1229/1243/1212 1212/1226/1195\nf 1231/1246/1215 1233/1247/1216 1234/1248/1217\nf 1235/1249/1218 1236/1250/1219 1234/1248/1217\nf 1217/1231/1200 1234/1248/1217 1236/1250/1219\nf 1232/1244/1213 1234/1248/1217 1217/1231/1200\nf 1237/1251/1220 1238/1252/1221 1236/1250/1219\nf 1239/1253/1222 1240/1254/1223 1238/1252/1221\nf 1221/1235/1204 1238/1252/1221 1240/1254/1223\nf 1219/1255/1224 1236/1250/1219 1238/1252/1221\nf 1241/1256/1225 1242/1257/1226 1240/1254/1223\nf 1227/1241/1210 1230/1245/1214 1242/1257/1226\nf 1225/1239/1208 1242/1257/1226 1230/1245/1214\nf 1223/1237/1206 1240/1254/1223 1242/1257/1226\nf 1243/1258/1227 1244/1259/1228 1245/1260/1229\nf 1248/1261/1230 1245/1260/1229 1244/1259/1228\nf 1231/1246/1215 1228/1242/1211 1245/1260/1229\nf 1246/1262/1231 1245/1260/1229 1228/1242/1211\nf 1247/1263/1232 1249/1264/1233 1250/1265/1234\nf 1252/1266/1235 1250/1265/1234 1249/1264/1233\nf 1235/1249/1218 1233/1247/1216 1250/1265/1234\nf 1248/1261/1230 1250/1265/1234 1233/1247/1216\nf 1253/1267/1236 1254/1268/1237 1252/1266/1235\nf 1255/1269/1238 1256/1270/1239 1254/1268/1237\nf 1237/1251/1220 1254/1268/1237 1256/1270/1239\nf 1235/1249/1218 1252/1266/1235 1254/1268/1237\nf 1257/1271/1240 1258/1272/1241 1256/1270/1239\nf 1243/1258/1227 1246/1262/1231 1258/1272/1241\nf 1241/1256/1225 1258/1272/1241 1246/1262/1231\nf 1239/1253/1222 1256/1270/1239 1258/1272/1241\nf 1208/1221/1190 1207/1220/1189 1259/1273/1242\nf 1206/1219/1188 1261/1274/1243 1259/1273/1242\nf 1247/1263/1232 1244/1259/1228 1259/1273/1242\nf 1260/1275/1244 1259/1273/1242 1244/1259/1228\nf 1206/1219/1188 1205/1218/1187 1262/1276/1245\nf 1204/1217/1186 1263/1277/1246 1262/1276/1245\nf 1251/1278/1247 1249/1264/1233 1262/1276/1245\nf 1261/1274/1243 1262/1276/1245 1249/1264/1233\nf 1204/1217/1186 1201/1215/1184 1264/1279/1248\nf 1200/1214/1183 1265/1280/1249 1264/1279/1248\nf 1253/1267/1236 1264/1279/1248 1265/1280/1249\nf 1251/1278/1247 1263/1277/1246 1264/1279/1248\nf 1200/1214/1183 1203/1222/1191 1266/1281/1250\nf 1208/1221/1190 1260/1275/1244 1266/1281/1250\nf 1257/1271/1240 1266/1281/1250 1260/1275/1244\nf 1255/1269/1238 1265/1280/1249 1266/1281/1250\nf 890/1282/1251 889/883/851 1267/1283/1252\nf 888/1284/1253 1269/1285/1254 1267/1283/1252\nf 926/918/886 1267/1283/1252 1269/1285/1254\nf 925/934/902 1268/1286/1255 1267/1283/1252\nf 888/1284/1253 887/882/850 1270/1287/1256\nf 886/1288/1257 1271/1289/1258 1270/1287/1256\nf 931/924/892 1270/1287/1256 1271/1289/1258\nf 929/921/889 1269/1285/1254 1270/1287/1256\nf 886/1288/1257 883/879/847 1272/1290/1259\nf 882/1291/1260 1273/1292/1261 1272/1290/1259\nf 937/931/899 935/928/896 1272/1290/1259\nf 1271/1289/1258 1272/1290/1259 935/928/896\nf 882/1291/1260 885/881/849 1274/1293/1262\nf 890/1282/1251 1268/1286/1255 1274/1293/1262\nf 939/932/900 1274/1293/1262 1268/1286/1255\nf 1273/1292/1261 1274/1293/1262 939/932/900\nf 1/74/73 2/1/1 4/3/3\nf 5/73/72 6/4/4 2/1/1\nf 7/737/704 8/5/5 6/4/4\nf 8/5/5 9/6/6 3/2/2\nf 11/1294/206 4/3/3 10/7/7\nf 12/79/78 13/8/8 15/10/10\nf 16/83/82 17/11/11 13/8/8\nf 18/39/38 19/12/12 17/11/11\nf 19/12/12 20/13/13 14/9/9\nf 22/416/382 15/10/10 21/14/14\nf 5/73/72 23/15/15 6/4/4\nf 12/79/78 15/10/10 23/15/15\nf 22/416/382 25/17/17 15/10/10\nf 25/17/17 26/18/18 24/16/16\nf 7/737/704 6/4/4 27/19/19\nf 16/83/82 28/20/20 17/11/11\nf 1/88/73 4/22/3 28/20/20\nf 11/232/206 30/23/22 4/22/3\nf 30/23/22 31/24/23 29/21/21\nf 18/39/38 17/11/11 32/25/24\nf 36/33/32 33/26/25 35/28/27\nf 37/899/867 38/29/28 34/27/26\nf 38/29/28 39/30/29 35/28/27\nf 40/31/30 41/32/31 35/28/27\nf 42/1063/1031 43/34/33 45/36/35\nf 46/1060/1028 47/37/36 43/34/33\nf 48/1056/1024 49/38/37 47/37/36\nf 50/1054/1022 45/36/35 49/38/37\nf 19/12/12 18/39/38 52/41/40\nf 53/1295/1263 54/42/41 51/40/39\nf 55/144/122 56/43/42 54/42/41\nf 20/13/13 19/12/12 56/43/42\nf 60/51/50 57/44/43 59/46/45\nf 58/45/44 61/47/46 59/46/45\nf 63/555/522 64/49/48 62/48/47\nf 64/49/48 65/50/49 59/46/45\nf 69/60/59 66/52/51 68/54/53\nf 67/53/52 70/55/54 68/54/53\nf 71/56/55 72/57/56 68/54/53\nf 73/58/57 74/59/58 68/54/53\nf 78/68/67 75/61/60 77/63/62\nf 76/62/61 79/64/63 77/63/62\nf 81/210/187 82/66/65 80/65/64\nf 82/66/65 83/67/66 77/63/62\nf 87/75/74 84/69/68 86/71/70\nf 88/99/91 89/72/71 85/70/69\nf 89/72/71 5/73/72 86/71/70\nf 2/1/1 1/74/73 86/71/70\nf 88/99/91 90/76/75 89/72/71\nf 92/1296/1264 93/78/77 90/76/75\nf 93/78/77 12/79/78 91/77/76\nf 23/15/15 5/73/72 91/77/76\nf 92/1296/1264 94/80/79 93/78/77\nf 96/84/83 97/82/81 94/80/79\nf 97/82/81 16/83/82 95/81/80\nf 13/8/8 12/79/78 95/81/80\nf 97/82/81 96/84/83 99/86/85\nf 84/107/68 87/87/74 98/85/84\nf 87/87/74 1/88/73 99/86/85\nf 28/20/20 16/83/82 99/86/85\nf 103/94/88 100/89/68 102/91/87\nf 101/90/86 104/92/71 102/91/87\nf 88/99/91 85/70/69 105/93/71\nf 84/69/68 103/94/88 85/70/69\nf 104/92/71 106/95/89 105/93/71\nf 106/95/89 108/97/90 107/96/75\nf 92/1296/1264 90/76/75 109/98/90\nf 90/76/75 88/99/91 107/96/75\nf 108/97/90 110/100/79 109/98/90\nf 112/103/93 113/102/92 110/100/79\nf 113/102/92 96/84/83 111/101/79\nf 92/1296/1264 109/98/90 94/80/79\nf 113/102/92 112/103/93 115/105/95\nf 100/1297/68 103/106/88 114/104/94\nf 103/106/88 84/107/68 115/105/95\nf 96/84/83 113/102/92 98/85/84\nf 119/112/88 116/108/96 118/110/98\nf 120/1298/71 121/111/99 117/109/97\nf 104/92/71 101/90/86 121/111/99\nf 100/89/68 119/112/88 101/90/86\nf 120/1298/71 122/113/100 121/111/99\nf 122/113/100 124/115/90 123/114/75\nf 108/97/90 106/95/89 125/116/101\nf 104/92/71 121/111/99 106/95/89\nf 124/115/90 126/117/102 125/116/101\nf 128/120/93 129/119/104 126/117/102\nf 129/119/104 112/103/93 127/118/103\nf 110/100/79 108/97/90 127/118/103\nf 129/119/104 128/120/93 131/122/106\nf 116/139/96 119/123/88 130/121/105\nf 100/1297/68 114/104/94 119/123/88\nf 112/103/93 129/119/104 114/104/94\nf 133/138/118 74/59/58 132/124/107\nf 73/58/57 72/57/56 132/124/107\nf 120/1298/71 117/109/97 134/127/109\nf 116/108/96 133/128/110 117/109/97\nf 134/125/108 72/57/56 135/129/111\nf 71/56/55 70/55/54 135/129/111\nf 124/115/90 122/113/100 136/132/113\nf 120/1298/71 134/127/109 122/113/100\nf 136/130/112 70/55/54 137/133/114\nf 67/53/52 66/52/51 137/133/114\nf 128/120/93 126/117/102 138/136/116\nf 124/115/90 136/132/113 126/117/102\nf 138/134/115 66/52/51 139/137/117\nf 69/60/59 74/59/58 139/137/117\nf 133/1299/110 116/139/96 139/140/94\nf 128/120/93 138/136/116 130/121/105\nf 76/62/61 75/61/60 141/142/120\nf 142/759/726 143/143/121 140/141/119\nf 143/143/121 55/144/122 141/142/120\nf 79/64/63 76/62/61 144/145/123\nf 148/152/130 145/146/124 147/148/126\nf 149/742/709 150/149/127 146/147/125\nf 150/149/127 151/150/128 147/148/126\nf 152/151/129 83/67/66 147/148/126\nf 156/217/138 153/153/131 155/155/133\nf 157/213/190 158/156/134 154/154/132\nf 158/243/134 159/157/135 155/159/133\nf 160/158/136 161/160/137 155/159/133\nf 165/168/145 162/162/139 164/164/141\nf 53/1295/1263 166/165/142 163/163/140\nf 166/165/142 167/166/143 164/164/141\nf 169/184/161 165/168/145 168/167/144\nf 170/958/926 171/169/146 173/171/148\nf 171/169/146 174/172/149 172/170/147\nf 175/173/150 176/174/151 172/170/147\nf 178/951/919 173/171/148 177/175/152\nf 182/182/159 179/176/153 181/178/155\nf 183/1037/1005 184/179/156 180/177/154\nf 184/179/156 185/180/157 181/178/155\nf 187/414/380 182/182/159 186/181/158\nf 189/186/163 162/162/139 188/183/160\nf 165/168/145 169/184/161 188/183/160\nf 81/210/187 80/65/64 190/185/162\nf 80/65/64 79/64/63 188/183/160\nf 53/1295/1263 163/163/140 54/42/41\nf 163/163/140 162/162/139 191/187/164\nf 189/186/163 79/64/63 191/187/164\nf 55/144/122 54/42/41 144/145/123\nf 149/742/709 146/147/125 193/189/166\nf 146/147/125 145/146/124 192/188/165\nf 194/190/167 161/160/137 192/188/165\nf 160/158/136 159/157/135 192/188/165\nf 198/199/176 195/191/168 197/193/170\nf 196/192/169 199/194/171 197/193/170\nf 200/195/172 201/196/173 197/193/170\nf 202/197/174 203/198/175 197/193/170\nf 207/207/184 204/200/177 206/202/179\nf 205/201/178 208/203/180 206/202/179\nf 210/615/582 211/205/182 209/204/181\nf 211/205/182 212/206/183 206/202/179\nf 161/160/137 194/190/167 214/209/186\nf 194/190/167 145/146/124 213/208/185\nf 83/67/66 82/66/65 148/152/130\nf 82/66/65 81/210/187 213/208/185\nf 142/759/726 140/141/119 216/212/189\nf 140/141/119 75/61/60 215/211/188\nf 83/67/66 152/151/129 78/68/67\nf 152/151/129 151/150/128 215/211/188\nf 218/216/193 157/213/190 217/214/191\nf 153/153/131 219/215/192 154/154/132\nf 219/215/192 169/184/161 217/214/191\nf 167/166/143 218/216/193 168/167/144\nf 219/215/192 153/153/131 220/218/194\nf 161/160/137 214/209/186 156/161/138\nf 214/209/186 81/210/187 220/219/194\nf 169/184/161 219/215/192 190/185/162\nf 224/225/200 221/220/195 223/222/197\nf 225/226/201 226/223/198 222/221/196\nf 226/223/198 9/6/6 223/222/197\nf 159/157/135 224/225/200 227/224/199\nf 226/223/198 225/226/201 229/228/203\nf 228/254/202 230/229/204 229/231/203\nf 231/230/205 11/232/206 229/231/203\nf 9/6/6 226/223/198 10/7/7\nf 230/229/204 232/234/207 231/230/205\nf 232/234/207 234/236/209 233/235/208\nf 157/213/190 236/238/211 235/237/210\nf 236/238/211 11/232/206 233/235/208\nf 234/236/209 237/239/212 235/237/210\nf 221/220/195 224/225/200 237/242/212\nf 159/157/135 158/243/134 224/225/200\nf 157/213/190 235/237/210 158/156/134\nf 239/261/228 240/244/214 242/246/216\nf 240/244/214 243/247/217 241/245/215\nf 244/248/218 225/249/201 241/245/215\nf 221/262/195 242/246/216 222/250/196\nf 244/248/218 243/247/217 246/252/220\nf 247/255/222 248/253/221 245/251/219\nf 230/229/204 228/254/202 248/253/221\nf 225/249/201 244/248/218 228/254/202\nf 248/253/221 247/255/222 250/257/224\nf 251/275/241 252/258/225 249/256/223\nf 234/236/209 232/234/207 252/258/225\nf 232/234/207 230/229/204 250/257/224\nf 251/275/241 253/259/226 252/258/225\nf 253/259/226 239/261/228 254/260/227\nf 242/246/216 221/262/195 254/260/227\nf 237/239/212 234/236/209 254/260/227\nf 255/279/245 256/263/229 258/265/231\nf 256/263/229 259/266/232 257/264/230\nf 243/247/217 240/244/214 260/267/233\nf 240/244/214 239/261/228 257/264/230\nf 259/266/232 261/268/234 260/267/233\nf 263/271/237 264/270/236 261/268/234\nf 264/270/236 247/255/222 262/269/235\nf 245/251/219 243/247/217 262/269/235\nf 264/270/236 263/271/237 266/273/239\nf 267/276/242 268/274/240 265/272/238\nf 268/274/240 251/275/241 266/273/239\nf 247/255/222 264/270/236 249/256/223\nf 268/274/240 267/276/242 270/278/244\nf 269/277/243 255/279/245 270/278/244\nf 239/261/228 253/259/226 258/265/231\nf 251/275/241 268/274/240 253/259/226\nf 274/285/251 271/280/246 273/282/248\nf 275/286/252 276/283/249 272/281/247\nf 276/283/249 167/166/143 273/282/248\nf 31/24/23 274/285/251 277/284/250\nf 276/283/249 275/286/252 279/288/254\nf 278/287/253 280/289/255 279/288/254\nf 157/213/190 218/216/193 281/290/256\nf 167/166/143 276/283/249 218/216/193\nf 280/289/255 282/291/257 281/290/256\nf 282/291/257 284/293/259 283/292/258\nf 11/232/206 236/238/211 285/294/260\nf 236/238/211 157/213/190 283/292/258\nf 284/293/259 286/295/261 285/294/260\nf 271/280/246 274/285/251 286/295/261\nf 31/24/23 30/23/22 274/285/251\nf 11/232/206 285/294/260 30/23/22\nf 291/301/267 288/297/263 290/299/265\nf 292/302/268 293/300/266 289/298/264\nf 293/300/266 275/286/252 290/299/265\nf 271/280/246 291/301/267 272/281/247\nf 293/300/266 292/302/268 295/304/270\nf 296/328/294 297/305/271 294/303/269\nf 280/289/255 278/287/253 297/305/271\nf 275/286/252 293/300/266 278/287/253\nf 301/312/278 298/306/272 300/308/274\nf 299/307/273 302/309/275 300/308/274\nf 304/1185/1154 305/311/277 303/310/276\nf 306/1183/1152 301/312/278 305/311/277\nf 307/1300/1265 308/313/279 310/315/281\nf 288/297/263 291/301/267 308/313/279\nf 291/301/267 271/280/246 309/314/280\nf 286/295/261 284/293/259 309/314/280\nf 311/332/298 312/316/282 314/318/284\nf 312/316/282 315/319/285 313/317/283\nf 284/293/259 282/291/257 316/320/286\nf 282/291/257 280/289/255 313/317/283\nf 315/319/285 317/321/287 316/320/286\nf 319/324/290 320/323/289 317/321/287\nf 307/1300/1265 310/315/281 320/323/289\nf 284/293/259 316/320/286 310/315/281\nf 320/323/289 319/324/290 322/326/292\nf 323/368/334 324/327/293 321/325/291\nf 324/327/293 296/328/294 322/326/292\nf 307/1300/1265 320/323/289 325/329/295\nf 323/368/334 326/330/296 324/327/293\nf 326/330/296 311/332/298 327/331/297\nf 280/289/255 297/305/271 314/318/284\nf 296/328/294 324/327/293 297/305/271\nf 331/338/304 328/333/299 330/335/301\nf 329/334/300 332/336/302 330/335/301\nf 315/319/285 312/316/282 333/337/303\nf 312/316/282 311/332/298 330/335/301\nf 333/337/303 332/336/302 335/340/306\nf 336/1010/978 337/341/307 334/339/305\nf 319/324/290 317/321/287 337/341/307\nf 317/321/287 315/319/285 335/340/306\nf 338/395/361 339/342/308 341/344/310\nf 339/342/308 342/345/311 340/343/309\nf 344/461/427 345/347/313 343/346/312\nf 346/469/436 341/344/310 345/347/313\nf 347/1008/976 348/348/314 350/350/316\nf 348/348/314 328/333/299 349/349/315\nf 331/338/304 311/332/298 349/349/315\nf 323/368/334 350/350/316 326/330/296\nf 352/354/320 203/198/175 351/351/317\nf 202/197/174 201/196/173 351/351/317\nf 354/359/325 355/353/319 353/352/318\nf 355/353/319 302/309/275 351/351/317\nf 353/352/318 201/196/173 356/355/321\nf 200/195/172 199/194/171 356/355/321\nf 357/356/322 358/357/323 356/355/321\nf 359/358/324 354/359/325 356/355/321\nf 357/356/322 199/194/171 360/360/326\nf 196/192/169 195/191/168 360/360/326\nf 361/361/327 298/306/272 360/360/326\nf 358/357/323 357/356/322 362/362/328\nf 361/361/327 195/191/168 363/363/329\nf 198/199/176 203/198/175 363/363/329\nf 302/309/275 299/307/273 352/354/320\nf 298/306/272 361/361/327 299/307/273\nf 364/1301/1266 365/364/330 367/366/332\nf 368/1302/1267 369/367/333 365/364/330\nf 369/367/333 323/368/334 366/365/331\nf 319/324/290 367/366/332 321/325/291\nf 368/1302/1267 370/369/335 369/367/333\nf 372/372/338 373/371/337 370/369/335\nf 347/1008/976 350/350/316 373/371/337\nf 323/368/334 369/367/333 350/350/316\nf 373/371/337 372/372/338 375/374/340\nf 376/398/364 377/375/341 374/373/339\nf 336/1010/978 378/376/342 377/375/341\nf 347/1008/976 373/371/337 378/376/342\nf 376/398/364 379/377/343 377/375/341\nf 364/1301/1266 367/366/332 379/377/343\nf 319/324/290 337/341/307 367/366/332\nf 336/1010/978 377/375/341 337/341/307\nf 381/1088/1056 382/379/345 384/381/347\nf 382/379/345 385/382/348 383/380/346\nf 386/383/349 387/384/350 383/380/346\nf 388/385/351 389/386/352 383/380/346\nf 344/461/427 343/346/312 391/388/354\nf 342/345/311 392/389/355 343/346/312\nf 392/389/355 372/372/338 390/387/353\nf 368/1302/1267 391/388/354 370/369/335\nf 393/1075/1043 394/390/356 396/392/358\nf 397/1072/1040 398/393/359 394/390/356\nf 399/1068/1036 400/394/360 398/393/359\nf 401/1066/1034 396/392/358 400/394/360\nf 403/399/365 338/395/361 402/396/362\nf 346/469/436 404/397/363 341/344/310\nf 364/1301/1266 379/377/343 404/397/363\nf 379/377/343 376/398/364 402/396/362\nf 185/180/157 405/400/366 407/402/368\nf 405/400/366 408/403/369 406/401/367\nf 151/150/128 410/405/371 409/404/370\nf 410/405/371 26/18/18 406/401/367\nf 408/403/369 411/406/372 409/404/370\nf 413/409/375 414/408/374 411/406/372\nf 142/759/726 216/212/189 414/408/374\nf 151/150/128 409/404/370 216/212/189\nf 414/408/374 413/409/375 416/411/377\nf 187/414/380 417/412/378 415/410/376\nf 22/416/382 418/413/379 417/412/378\nf 142/759/726 414/408/374 418/413/379\nf 417/412/378 187/414/380 419/415/381\nf 186/181/158 185/180/157 419/415/381\nf 26/18/18 25/17/17 407/402/368\nf 25/17/17 22/416/382 419/415/381\nf 423/423/389 420/417/383 422/419/385\nf 424/424/390 425/420/386 421/418/384\nf 425/420/386 426/421/387 422/419/385\nf 428/632/599 423/423/389 427/422/388\nf 425/420/386 424/424/390 430/426/392\nf 429/425/391 431/427/393 430/426/392\nf 432/428/394 433/429/395 430/426/392\nf 426/421/387 425/420/386 434/430/396\nf 431/427/393 435/431/397 432/428/394\nf 435/431/397 437/433/399 436/432/398\nf 439/439/405 440/435/401 438/434/400\nf 440/435/401 433/429/395 436/432/398\nf 437/433/399 441/436/402 438/434/400\nf 441/436/402 420/417/383 442/437/403\nf 428/632/599 443/438/404 423/423/389\nf 443/438/404 439/439/405 442/437/403\nf 447/444/410 444/440/406 446/442/408\nf 448/445/411 449/443/409 445/441/407\nf 449/443/409 424/424/390 446/442/408\nf 420/417/383 447/444/410 421/418/384\nf 449/443/409 448/445/411 451/447/413\nf 450/446/412 452/448/414 451/447/413\nf 453/449/415 431/427/393 451/447/413\nf 424/424/390 449/443/409 429/425/391\nf 452/448/414 454/450/416 453/449/415\nf 456/453/419 457/452/418 454/450/416\nf 437/433/399 435/431/397 457/452/418\nf 435/431/397 431/427/393 455/451/417\nf 457/452/418 456/453/419 459/455/421\nf 444/440/406 447/444/410 458/454/420\nf 447/444/410 420/417/383 459/455/421\nf 441/436/402 437/433/399 459/455/421\nf 461/458/424 389/386/352 460/456/422\nf 388/385/351 387/384/350 460/456/422\nf 368/1302/1267 365/364/330 462/457/423\nf 364/1301/1266 461/458/424 365/364/330\nf 462/457/423 387/384/350 463/459/425\nf 385/382/348 464/460/426 386/383/349\nf 464/460/426 344/461/427 463/459/425\nf 368/1302/1267 462/457/423 391/388/354\nf 465/1151/1120 466/462/428 468/464/430\nf 469/1147/1116 470/465/431 466/462/428\nf 471/1143/1112 472/466/433 470/465/431\nf 473/1141/1110 468/464/430 472/466/433\nf 381/1088/1056 384/381/347 475/468/435\nf 384/381/347 389/386/352 474/467/434\nf 364/1301/1266 404/397/363 461/458/424\nf 404/397/363 346/469/436 474/467/434\nf 479/474/441 476/470/437 478/472/439\nf 480/475/442 481/473/440 477/471/438\nf 481/473/440 376/398/364 478/472/439\nf 372/372/338 479/474/441 374/373/339\nf 481/473/440 480/475/442 483/477/444\nf 484/493/460 485/478/445 482/476/443\nf 338/395/361 403/399/365 485/478/445\nf 376/398/364 481/473/440 403/399/365\nf 484/493/460 486/479/446 485/478/445\nf 486/479/446 488/481/448 487/480/447\nf 342/345/311 339/342/308 489/482/449\nf 339/342/308 338/395/361 487/480/447\nf 488/481/448 490/483/450 489/482/449\nf 490/483/450 476/470/437 491/484/451\nf 372/372/338 392/389/355 479/474/441\nf 392/389/355 342/345/311 491/484/451\nf 492/501/468 493/485/452 495/487/454\nf 493/485/452 496/488/455 494/486/453\nf 480/475/442 477/471/438 497/489/456\nf 477/471/438 476/470/437 494/486/453\nf 496/488/455 498/490/457 497/489/456\nf 500/494/461 501/492/459 498/490/457\nf 501/492/459 484/493/460 499/491/458\nf 480/475/442 497/489/456 482/476/443\nf 501/492/459 500/494/461 503/496/463\nf 504/498/465 505/497/464 502/495/462\nf 505/497/464 488/481/448 503/496/463\nf 484/493/460 501/492/459 486/479/446\nf 505/497/464 504/498/465 507/500/467\nf 506/499/466 492/501/468 507/500/467\nf 476/470/437 490/483/450 495/487/454\nf 488/481/448 505/497/464 490/483/450\nf 508/517/484 509/502/469 511/504/471\nf 509/502/469 512/505/472 510/503/470\nf 31/24/23 277/284/250 513/506/473\nf 167/166/143 511/504/471 277/284/250\nf 512/505/472 514/507/474 513/506/473\nf 514/507/474 516/509/476 515/508/475\nf 517/510/477 18/39/38 515/508/475\nf 31/24/23 513/506/473 32/25/24\nf 516/509/476 518/511/478 517/510/477\nf 518/511/478 520/513/480 519/512/479\nf 53/1295/1263 51/40/39 521/514/481\nf 18/39/38 517/510/477 51/40/39\nf 520/513/480 522/515/482 521/514/481\nf 522/515/482 508/517/484 523/516/483\nf 167/166/143 166/165/142 511/504/471\nf 53/1295/1263 521/514/481 166/165/142\nf 527/522/489 524/518/485 526/520/487\nf 528/523/490 529/521/488 525/519/486\nf 529/521/488 512/505/472 526/520/487\nf 508/517/484 527/522/489 509/502/469\nf 529/521/488 528/523/490 531/525/492\nf 530/524/491 532/526/493 531/525/492\nf 516/509/476 514/507/474 533/527/494\nf 512/505/472 529/521/488 514/507/474\nf 532/526/493 534/528/495 533/527/494\nf 534/528/495 536/530/497 535/529/496\nf 520/513/480 518/511/478 537/531/498\nf 518/511/478 516/509/476 535/529/496\nf 536/530/497 538/532/499 537/531/498\nf 524/518/485 527/522/489 538/532/499\nf 527/522/489 508/517/484 539/533/500\nf 522/515/482 520/513/480 539/533/500\nf 543/542/509 540/534/501 542/536/503\nf 541/535/502 544/537/504 542/536/503\nf 545/538/505 546/539/506 542/536/503\nf 547/540/507 548/541/508 542/536/503\nf 63/555/522 62/48/47 550/544/511\nf 62/48/47 61/47/46 549/543/510\nf 296/328/294 294/303/269 551/545/512\nf 292/302/268 550/544/511 294/303/269\nf 551/545/512 61/47/46 552/546/513\nf 58/45/44 57/44/43 552/546/513\nf 307/1300/1265 325/329/295 553/547/514\nf 296/328/294 551/545/512 325/329/295\nf 553/547/514 57/44/43 554/548/515\nf 65/50/49 555/549/516 60/51/50\nf 288/297/263 308/313/279 555/549/516\nf 307/1300/1265 553/547/514 308/313/279\nf 557/552/519 548/541/508 556/550/517\nf 547/540/507 546/539/506 556/550/517\nf 292/302/268 289/298/264 558/551/518\nf 288/297/263 557/552/519 289/298/264\nf 558/551/518 546/539/506 559/553/520\nf 544/537/504 560/554/521 545/538/505\nf 560/554/521 63/555/522 559/553/520\nf 292/302/268 558/551/518 550/544/511\nf 564/564/531 561/556/523 563/558/525\nf 562/557/524 565/559/526 563/558/525\nf 566/560/527 567/561/528 563/558/525\nf 568/562/529 569/563/530 563/558/525\nf 540/534/501 543/542/509 571/566/533\nf 543/542/509 548/541/508 570/565/532\nf 288/297/263 555/549/516 557/552/519\nf 555/549/516 65/50/49 570/565/532\nf 572/605/572 573/567/534 575/569/536\nf 576/592/559 577/570/537 573/567/534\nf 528/523/490 525/519/486 577/570/537\nf 525/519/486 524/518/485 574/568/535\nf 576/592/559 578/571/538 577/570/537\nf 580/591/558 581/573/540 578/571/538\nf 581/573/540 532/526/493 579/572/539\nf 530/524/491 528/523/490 579/572/539\nf 580/591/558 582/574/541 581/573/540\nf 584/606/573 585/576/543 582/574/541\nf 585/576/543 536/530/497 583/575/542\nf 532/526/493 581/573/540 534/528/495\nf 584/606/573 586/577/544 585/576/543\nf 572/605/572 575/569/536 586/577/544\nf 524/518/485 538/532/499 575/569/536\nf 536/530/497 585/576/543 538/532/499\nf 591/587/554 588/579/546 590/581/548\nf 589/580/547 592/582/549 590/581/548\nf 593/583/550 594/584/551 590/581/548\nf 595/585/552 596/586/553 590/581/548\nf 210/615/582 209/204/181 598/589/556\nf 208/203/180 599/590/557 209/204/181\nf 599/590/557 580/591/558 597/588/555\nf 578/571/538 576/592/559 597/588/555\nf 603/601/568 600/593/560 602/595/562\nf 601/594/561 604/596/563 602/595/562\nf 605/597/564 606/598/565 602/595/562\nf 607/599/566 608/600/567 602/595/562\nf 204/200/177 207/207/184 610/603/570\nf 212/206/183 611/604/571 207/207/184\nf 611/604/571 572/605/572 609/602/569\nf 586/577/544 584/606/573 609/602/569\nf 612/623/590 613/607/574 615/609/576\nf 616/611/578 617/610/577 613/607/574\nf 617/610/577 576/592/559 614/608/575\nf 572/605/572 615/609/576 573/567/534\nf 617/610/577 616/611/578 619/613/580\nf 620/616/583 621/614/581 618/612/579\nf 621/614/581 210/615/582 619/613/580\nf 576/592/559 617/610/577 598/589/556\nf 621/614/581 620/616/583 623/618/585\nf 622/617/584 624/619/586 623/618/585\nf 212/206/183 211/205/182 625/620/587\nf 211/205/182 210/615/582 623/618/585\nf 624/619/586 626/621/588 625/620/587\nf 626/621/588 612/623/590 627/622/589\nf 572/605/572 611/604/571 615/609/576\nf 611/604/571 212/206/183 627/622/589\nf 428/632/599 427/422/388 629/625/592\nf 426/421/387 630/626/593 427/422/388\nf 584/606/573 582/574/541 630/626/593\nf 582/574/541 580/591/558 628/624/591\nf 630/626/593 426/421/387 631/627/594\nf 433/429/395 632/628/595 434/430/396\nf 632/628/595 204/200/177 631/627/594\nf 584/606/573 630/626/593 610/603/570\nf 433/429/395 440/435/401 632/628/595\nf 440/435/401 439/439/405 633/629/596\nf 634/630/597 208/203/180 633/629/596\nf 204/200/177 632/628/595 205/201/178\nf 439/439/405 443/438/404 634/630/597\nf 443/438/404 428/632/599 635/631/598\nf 580/591/558 599/590/557 629/625/592\nf 599/590/557 208/203/180 635/631/598\nf 636/653/620 637/633/600 639/635/602\nf 637/633/600 640/636/603 638/634/601\nf 616/611/578 613/607/574 641/637/604\nf 613/607/574 612/623/590 638/634/601\nf 641/637/604 640/636/603 643/639/606\nf 644/641/608 645/640/607 642/638/605\nf 645/640/607 620/616/583 643/639/606\nf 616/611/578 641/637/604 618/612/579\nf 645/640/607 644/641/608 647/643/610\nf 648/645/612 649/644/611 646/642/609\nf 649/644/611 624/619/586 647/643/610\nf 620/616/583 645/640/607 622/617/584\nf 649/644/611 648/645/612 651/647/614\nf 636/653/620 639/635/602 650/646/613\nf 612/623/590 626/621/588 639/635/602\nf 626/621/588 624/619/586 651/647/614\nf 652/728/695 653/648/615 655/650/617\nf 653/648/615 656/651/618 654/649/616\nf 640/636/603 637/633/600 657/652/619\nf 637/633/600 636/653/620 654/649/616\nf 656/651/618 658/654/621 657/652/619\nf 660/657/624 661/656/623 658/654/621\nf 661/656/623 644/641/608 659/655/622\nf 642/638/605 640/636/603 659/655/622\nf 661/656/623 660/657/624 663/659/626\nf 664/661/628 665/660/627 662/658/625\nf 665/660/627 648/645/612 663/659/626\nf 644/641/608 661/656/623 646/642/609\nf 665/660/627 664/661/628 667/663/630\nf 652/728/695 655/650/617 666/662/629\nf 655/650/617 636/653/620 667/663/630\nf 648/645/612 665/660/627 650/646/613\nf 671/668/635 668/664/631 670/666/633\nf 672/669/636 673/667/634 669/665/632\nf 65/50/49 64/49/48 673/667/634\nf 64/49/48 63/555/522 670/666/633\nf 673/667/634 672/669/636 675/671/638\nf 676/673/640 677/672/639 674/670/637\nf 677/672/639 540/534/501 675/671/638\nf 65/50/49 673/667/634 571/566/533\nf 677/672/639 676/673/640 679/675/642\nf 678/674/641 680/676/643 679/675/642\nf 544/537/504 541/535/502 681/677/644\nf 540/534/501 677/672/639 541/535/502\nf 680/676/643 682/678/645 681/677/644\nf 682/678/645 668/664/631 683/679/646\nf 63/555/522 560/554/521 671/668/635\nf 560/554/521 544/537/504 683/679/646\nf 684/694/661 685/680/647 687/682/649\nf 685/680/647 688/683/650 686/681/648\nf 689/684/651 672/669/636 686/681/648\nf 668/664/631 687/682/649 669/665/632\nf 689/684/651 688/683/650 691/686/653\nf 692/688/655 693/687/654 690/685/652\nf 693/687/654 676/673/640 691/686/653\nf 672/669/636 689/684/651 674/670/637\nf 693/687/654 692/688/655 695/690/657\nf 696/711/678 697/691/658 694/689/656\nf 680/676/643 678/674/641 697/691/658\nf 676/673/640 693/687/654 678/674/641\nf 696/711/678 698/692/659 697/691/658\nf 698/692/659 684/694/661 699/693/660\nf 668/664/631 682/678/645 687/682/649\nf 682/678/645 680/676/643 699/693/660\nf 703/699/666 700/695/662 702/697/664\nf 704/700/667 705/698/665 701/696/663\nf 688/683/650 685/680/647 705/698/665\nf 685/680/647 684/694/661 702/697/664\nf 705/698/665 704/700/667 707/702/669\nf 706/701/668 708/703/670 707/702/669\nf 709/704/671 692/688/655 707/702/669\nf 688/683/650 705/698/665 690/685/652\nf 708/703/670 710/705/672 709/704/671\nf 710/705/672 712/707/674 711/706/673\nf 696/711/678 694/689/656 713/708/675\nf 692/688/655 709/704/671 694/689/656\nf 712/707/674 714/709/676 713/708/675\nf 700/695/662 703/699/666 714/709/676\nf 684/694/661 698/692/659 703/699/666\nf 698/692/659 696/711/678 715/710/677\nf 719/716/683 716/712/679 718/714/681\nf 720/717/684 721/715/682 717/713/680\nf 721/715/682 656/651/618 718/714/681\nf 652/728/695 719/716/683 653/648/615\nf 721/715/682 720/717/684 723/719/686\nf 722/718/685 724/720/687 723/719/686\nf 725/721/688 660/657/624 723/719/686\nf 656/651/618 721/715/682 658/654/621\nf 724/720/687 726/722/689 725/721/688\nf 726/722/689 728/724/691 727/723/690\nf 664/661/628 662/658/625 729/725/692\nf 662/658/625 660/657/624 727/723/690\nf 729/725/692 728/724/691 731/727/694\nf 716/712/679 719/716/683 730/726/693\nf 719/716/683 652/728/695 731/727/694\nf 664/661/628 729/725/692 666/662/629\nf 732/746/713 733/729/696 735/731/698\nf 733/729/696 736/732/699 734/730/697\nf 26/18/18 410/405/371 737/733/700\nf 410/405/371 151/150/128 734/730/697\nf 736/732/699 738/734/701 737/733/700\nf 740/738/705 741/736/703 738/734/701\nf 741/736/703 7/737/704 739/735/702\nf 26/18/18 737/733/700 27/19/19\nf 741/736/703 740/738/705 743/740/707\nf 744/854/821 745/741/708 742/739/706\nf 745/741/708 149/742/709 743/740/707\nf 7/737/704 741/736/703 746/743/710\nf 744/854/821 747/744/711 745/741/708\nf 747/744/711 732/746/713 748/745/712\nf 151/150/128 150/149/127 735/731/698\nf 149/742/709 745/741/708 150/149/127\nf 749/985/953 750/747/714 752/749/716\nf 750/747/714 753/750/717 751/748/715\nf 20/13/13 56/43/42 754/751/718\nf 56/43/42 55/144/122 751/748/715\nf 753/750/717 755/752/719 754/751/718\nf 755/752/719 757/754/721 756/753/720\nf 758/755/722 22/416/382 756/753/720\nf 20/13/13 754/751/718 21/14/14\nf 758/755/722 757/754/721 760/757/724\nf 761/760/727 762/758/725 759/756/723\nf 762/758/725 142/759/726 760/757/724\nf 22/416/382 758/755/722 418/413/379\nf 762/758/725 761/760/727 764/762/729\nf 749/985/953 752/749/716 763/761/728\nf 55/144/122 143/143/121 752/749/716\nf 142/759/726 762/758/725 143/143/121\nf 765/778/745 766/763/730 768/765/732\nf 766/763/730 769/766/733 767/764/731\nf 770/767/734 704/700/667 767/764/731\nf 700/695/662 768/765/732 701/696/663\nf 769/766/733 771/768/735 770/767/734\nf 773/771/738 774/770/737 771/768/735\nf 708/703/670 706/701/668 774/770/737\nf 704/700/667 770/767/734 706/701/668\nf 774/770/737 773/771/738 776/773/740\nf 777/775/742 778/774/741 775/772/739\nf 712/707/674 710/705/672 778/774/741\nf 710/705/672 708/703/670 776/773/740\nf 778/774/741 777/775/742 780/777/744\nf 779/776/743 765/778/745 780/777/744\nf 768/765/732 700/695/662 780/777/744\nf 714/709/676 712/707/674 780/777/744\nf 781/794/761 782/779/746 784/781/748\nf 782/779/746 785/782/749 783/780/747\nf 769/766/733 766/763/730 786/783/750\nf 766/763/730 765/778/745 783/780/747\nf 785/782/749 787/784/751 786/783/750\nf 789/787/754 790/786/753 787/784/751\nf 790/786/753 773/771/738 788/785/752\nf 771/768/735 769/766/733 788/785/752\nf 790/786/753 789/787/754 792/789/756\nf 793/791/758 794/790/757 791/788/755\nf 794/790/757 777/775/742 792/789/756\nf 773/771/738 790/786/753 775/772/739\nf 794/790/757 793/791/758 796/793/760\nf 795/792/759 781/794/761 796/793/760\nf 765/778/745 779/776/743 784/781/748\nf 777/775/742 794/790/757 779/776/743\nf 797/810/777 798/795/762 800/797/764\nf 798/795/762 801/798/765 799/796/763\nf 785/782/749 782/779/746 802/799/766\nf 782/779/746 781/794/761 799/796/763\nf 801/798/765 803/800/767 802/799/766\nf 805/803/770 806/802/769 803/800/767\nf 806/802/769 789/787/754 804/801/768\nf 787/784/751 785/782/749 804/801/768\nf 806/802/769 805/803/770 808/805/772\nf 809/807/774 810/806/773 807/804/771\nf 810/806/773 793/791/758 808/805/772\nf 789/787/754 806/802/769 791/788/755\nf 810/806/773 809/807/774 812/809/776\nf 811/808/775 797/810/777 812/809/776\nf 781/794/761 795/792/759 800/797/764\nf 793/791/758 810/806/773 795/792/759\nf 814/813/780 569/563/530 813/811/778\nf 568/562/529 567/561/528 813/811/778\nf 801/798/765 798/795/762 815/812/779\nf 798/795/762 797/810/777 813/811/778\nf 815/812/779 567/561/528 816/814/781\nf 566/560/527 565/559/526 816/814/781\nf 817/815/782 805/803/770 816/814/781\nf 803/800/767 801/798/765 816/814/781\nf 817/815/782 565/559/526 818/816/783\nf 562/557/524 561/556/523 818/816/783\nf 819/817/784 809/807/774 818/816/783\nf 805/803/770 817/815/782 807/804/771\nf 819/817/784 561/556/523 820/818/785\nf 564/564/531 569/563/530 820/818/785\nf 797/810/777 811/808/775 814/813/780\nf 809/807/774 819/817/784 811/808/775\nf 822/821/788 596/586/553 821/819/786\nf 595/585/552 594/584/551 821/819/786\nf 720/717/684 717/713/680 823/820/787\nf 717/713/680 716/712/679 821/819/786\nf 823/820/787 594/584/551 824/822/789\nf 593/583/550 592/582/549 824/822/789\nf 724/720/687 722/718/685 825/823/790\nf 720/717/684 823/820/787 722/718/685\nf 825/823/790 592/582/549 826/824/791\nf 589/580/547 588/579/546 826/824/791\nf 827/825/792 728/724/691 826/824/791\nf 724/720/687 825/823/790 726/722/689\nf 827/825/792 588/579/546 828/826/793\nf 591/587/554 596/586/553 828/826/793\nf 822/821/788 716/712/679 828/826/793\nf 728/724/691 827/825/792 730/726/693\nf 832/831/798 829/827/794 831/829/796\nf 833/1158/1127 834/830/797 830/828/795\nf 736/732/699 733/729/696 834/830/797\nf 733/729/696 732/746/713 831/829/796\nf 833/1158/1127 835/832/799 834/830/797\nf 835/832/799 837/834/801 836/833/800\nf 740/738/705 738/734/701 838/835/802\nf 738/734/701 736/732/699 836/833/800\nf 842/844/811 839/836/803 841/838/805\nf 840/837/804 843/839/806 841/838/805\nf 844/840/807 845/841/808 841/838/805\nf 846/842/809 847/843/810 841/838/805\nf 851/848/815 848/845/812 850/847/814\nf 829/827/794 832/831/798 849/846/813\nf 732/746/713 747/744/711 832/831/798\nf 744/854/821 851/848/815 747/744/711\nf 853/851/818 847/843/810 852/849/816\nf 846/842/809 845/841/808 852/849/816\nf 744/854/821 742/739/706 854/850/817\nf 740/738/705 853/851/818 742/739/706\nf 845/841/808 844/840/807 854/850/817\nf 844/840/807 843/839/806 855/852/819\nf 848/845/812 851/848/815 856/853/820\nf 851/848/815 744/854/821 855/852/819\nf 856/853/820 843/839/806 857/855/822\nf 840/837/804 839/836/803 857/855/822\nf 837/834/801 859/857/824 858/856/823\nf 859/857/824 848/845/812 857/855/822\nf 858/856/823 839/836/803 860/858/825\nf 847/843/810 853/851/818 842/844/811\nf 853/851/818 740/738/705 860/858/825\nf 837/834/801 858/856/823 838/835/802\nf 864/864/832 861/859/826 863/861/828\nf 862/860/827 865/862/829 863/861/828\nf 159/157/135 227/224/831 866/863/830\nf 9/6/6 864/864/832 227/224/831\nf 865/862/829 867/865/833 866/863/830\nf 869/891/859 870/867/835 867/865/833\nf 149/742/709 193/189/166 870/867/835\nf 159/157/135 866/863/830 193/189/166\nf 869/891/859 871/868/836 870/867/835\nf 871/868/836 873/870/838 872/869/837\nf 7/737/704 746/743/710 874/871/839\nf 149/742/709 870/867/835 746/743/710\nf 873/870/838 875/872/840 874/871/839\nf 861/859/826 864/864/832 875/872/840\nf 9/6/6 8/5/5 864/864/832\nf 8/5/5 7/737/704 876/873/841\nf 878/876/844 41/32/31 877/874/842\nf 40/31/30 39/30/29 877/874/842\nf 865/862/829 862/860/827 879/875/843\nf 861/859/826 878/876/844 862/860/827\nf 879/875/843 39/30/29 880/877/845\nf 37/899/867 881/878/846 38/29/28\nf 869/891/859 867/865/833 881/878/846\nf 867/865/833 865/862/829 880/877/845\nf 882/1291/1260 883/879/847 885/881/849\nf 886/1288/1257 887/882/850 883/879/847\nf 888/1284/1253 889/883/851 887/882/850\nf 890/1282/1251 885/881/849 889/883/851\nf 33/26/25 36/33/32 892/885/853\nf 36/33/32 41/32/31 891/884/852\nf 878/876/844 861/859/826 891/884/852\nf 873/870/838 892/885/853 875/872/840\nf 893/902/870 894/886/854 896/888/856\nf 894/886/854 897/889/857 895/887/855\nf 873/870/838 871/868/836 898/890/858\nf 871/868/836 869/891/859 895/887/855\nf 897/889/857 899/892/860 898/890/858\nf 901/895/863 902/894/862 899/892/860\nf 902/894/862 33/26/25 900/893/861\nf 873/870/838 898/890/858 892/885/853\nf 902/894/862 901/895/863 904/897/865\nf 905/1303/1268 906/898/866 903/896/864\nf 906/898/866 37/899/867 904/897/865\nf 33/26/25 902/894/862 34/27/26\nf 905/1303/1268 907/900/868 906/898/866\nf 907/900/868 893/902/870 908/901/869\nf 869/891/859 881/878/846 896/888/856\nf 881/878/846 37/899/867 908/901/869\nf 909/923/891 910/903/871 912/905/873\nf 910/903/871 913/906/874 911/904/872\nf 897/889/857 894/886/854 914/907/875\nf 893/902/870 912/905/873 894/886/854\nf 913/906/874 915/908/876 914/907/875\nf 915/908/876 917/910/878 916/909/877\nf 901/895/863 899/892/860 918/911/879\nf 899/892/860 897/889/857 916/909/877\nf 918/911/879 917/910/878 920/913/881\nf 921/915/883 922/914/882 919/912/880\nf 905/1303/1268 903/896/864 922/914/882\nf 901/895/863 918/911/879 903/896/864\nf 922/914/882 921/915/883 924/917/885\nf 909/923/891 912/905/873 923/916/884\nf 912/905/873 893/902/870 924/917/885\nf 905/1303/1268 922/914/882 907/900/868\nf 925/934/902 926/918/886 928/920/888\nf 926/918/886 929/921/889 927/919/887\nf 913/906/874 910/903/871 930/922/890\nf 910/903/871 909/923/891 927/919/887\nf 929/921/889 931/924/892 930/922/890\nf 933/927/895 934/926/894 931/924/892\nf 934/926/894 917/910/878 932/925/893\nf 915/908/876 913/906/874 932/925/893\nf 934/926/894 933/927/895 936/929/897\nf 937/931/899 938/930/898 935/928/896\nf 938/930/898 921/915/883 936/929/897\nf 917/910/878 934/926/894 919/912/880\nf 938/930/898 937/931/899 940/933/901\nf 939/932/900 925/934/902 940/933/901\nf 909/923/891 923/916/884 928/920/888\nf 921/915/883 938/930/898 923/916/884\nf 941/950/918 942/935/903 944/937/905\nf 942/935/903 945/938/906 943/936/904\nf 259/266/232 256/263/229 946/939/907\nf 256/263/229 255/279/245 943/936/904\nf 945/938/906 947/940/908 946/939/907\nf 949/943/911 950/942/910 947/940/908\nf 950/942/910 263/271/237 948/941/909\nf 261/268/234 259/266/232 948/941/909\nf 950/942/910 949/943/911 952/945/913\nf 953/947/915 954/946/914 951/944/912\nf 954/946/914 267/276/242 952/945/913\nf 263/271/237 950/942/910 265/272/238\nf 954/946/914 953/947/915 956/949/917\nf 955/948/916 941/950/918 956/949/917\nf 255/279/245 269/277/243 944/937/905\nf 267/276/242 954/946/914 269/277/243\nf 958/954/922 178/951/919 957/952/920\nf 177/175/152 176/174/151 957/952/920\nf 959/953/921 945/938/906 957/952/920\nf 941/950/918 958/954/922 942/935/903\nf 959/953/921 176/174/151 960/955/923\nf 175/173/150 174/172/149 960/955/923\nf 949/943/911 947/940/908 961/956/924\nf 945/938/906 959/953/921 947/940/908\nf 961/956/924 174/172/149 962/957/925\nf 171/169/146 170/958/926 962/957/925\nf 953/947/915 951/944/912 963/959/927\nf 951/944/912 949/943/911 962/957/925\nf 963/959/927 170/958/926 964/960/928\nf 173/171/148 178/951/919 964/960/928\nf 958/954/922 941/950/918 964/960/928\nf 955/948/916 953/947/915 964/960/928\nf 966/963/931 608/600/567 965/961/929\nf 607/599/566 606/598/565 965/961/929\nf 967/962/930 448/445/411 965/961/929\nf 444/440/406 966/963/931 445/441/407\nf 967/962/930 606/598/565 968/964/932\nf 605/597/564 604/596/563 968/964/932\nf 452/448/414 450/446/412 969/965/933\nf 448/445/411 967/962/930 450/446/412\nf 604/596/563 601/594/561 969/965/933\nf 601/594/561 600/593/560 970/966/934\nf 456/453/419 454/450/416 971/967/935\nf 454/450/416 452/448/414 970/966/934\nf 971/967/935 600/593/560 972/968/936\nf 603/601/568 608/600/567 972/968/936\nf 966/963/931 444/440/406 972/968/936\nf 458/454/420 456/453/419 972/968/936\nf 976/973/941 973/969/937 975/971/939\nf 977/974/942 978/972/940 974/970/938\nf 978/972/940 753/750/717 975/971/939\nf 749/985/953 976/973/941 750/747/714\nf 978/972/940 977/974/942 980/976/944\nf 979/975/943 981/977/945 980/976/944\nf 757/754/721 755/752/719 982/978/946\nf 755/752/719 753/750/717 980/976/944\nf 981/977/945 983/979/947 982/978/946\nf 983/979/947 985/981/949 984/980/948\nf 761/760/727 759/756/723 986/982/950\nf 759/756/723 757/754/721 984/980/948\nf 985/981/949 987/983/951 986/982/950\nf 973/969/937 976/973/941 987/983/951\nf 976/973/941 749/985/953 988/984/952\nf 763/761/728 761/760/727 988/984/952\nf 989/1234/1203 990/986/954 992/988/956\nf 990/986/954 993/989/957 991/987/955\nf 994/990/958 995/991/959 991/987/955\nf 996/992/960 997/993/961 991/987/955\nf 1001/999/967 998/994/962 1000/996/964\nf 999/995/963 1002/997/965 1000/996/964\nf 981/977/945 979/975/943 1003/998/966\nf 977/974/942 1001/999/967 979/975/943\nf 1003/998/966 1002/997/965 1005/1001/969\nf 1004/1000/968 1006/1002/970 1005/1001/969\nf 985/981/949 983/979/947 1007/1003/971\nf 983/979/947 981/977/945 1005/1001/969\nf 1007/1003/971 1006/1002/970 1009/1005/973\nf 1008/1004/972 1010/1006/974 1009/1005/973\nf 1011/1007/975 973/969/937 1009/1005/973\nf 987/983/951 985/981/949 1009/1005/973\nf 348/348/314 347/1008/976 1012/1009/977\nf 378/376/342 336/1010/978 1012/1009/977\nf 334/339/305 332/336/302 1012/1009/977\nf 329/334/300 328/333/299 1012/1009/977\nf 183/1037/1005 1013/1011/979 184/179/156\nf 1013/1011/979 1015/1013/981 1014/1012/980\nf 408/403/369 405/400/366 1016/1014/982\nf 405/400/366 185/180/157 1014/1012/980\nf 1015/1013/981 1017/1015/983 1016/1014/982\nf 1017/1015/983 1019/1017/985 1018/1016/984\nf 413/409/375 411/406/372 1020/1018/986\nf 411/406/372 408/403/369 1018/1016/984\nf 1020/1018/986 1019/1017/985 1022/1020/988\nf 179/176/153 182/182/159 1021/1019/987\nf 182/182/159 187/414/380 1022/1020/988\nf 413/409/375 1020/1018/986 415/410/376\nf 1026/1025/993 1023/1021/989 1025/1023/991\nf 1027/1026/994 1028/1024/992 1024/1022/990\nf 1028/1024/992 1015/1013/981 1025/1023/991\nf 183/1037/1005 1026/1025/993 1013/1011/979\nf 1028/1024/992 1027/1026/994 1030/1028/996\nf 1029/1027/995 1031/1029/997 1030/1028/996\nf 1019/1017/985 1017/1015/983 1032/1030/998\nf 1015/1013/981 1028/1024/992 1017/1015/983\nf 1031/1029/997 1033/1031/999 1032/1030/998\nf 1033/1031/999 1035/1033/1001 1034/1032/1000\nf 179/176/153 1021/1019/987 1036/1034/1002\nf 1021/1019/987 1019/1017/985 1034/1032/1000\nf 1035/1033/1001 1037/1035/1003 1036/1034/1002\nf 1023/1021/989 1026/1025/993 1037/1035/1003\nf 1026/1025/993 183/1037/1005 1038/1036/1004\nf 180/177/154 179/176/153 1038/1036/1004\nf 1039/1053/1021 1040/1038/1006 1042/1040/1008\nf 1040/1038/1006 1043/1041/1009 1041/1039/1007\nf 1044/1042/1010 1027/1026/994 1041/1039/1007\nf 1023/1021/989 1042/1040/1008 1024/1022/990\nf 1043/1041/1009 1045/1043/1011 1044/1042/1010\nf 1047/1046/1014 1048/1045/1013 1045/1043/1011\nf 1031/1029/997 1029/1027/995 1048/1045/1013\nf 1027/1026/994 1044/1042/1010 1029/1027/995\nf 1048/1045/1013 1047/1046/1014 1050/1048/1016\nf 1051/1050/1018 1052/1049/1017 1049/1047/1015\nf 1035/1033/1001 1033/1031/999 1052/1049/1017\nf 1033/1031/999 1031/1029/997 1050/1048/1016\nf 1052/1049/1017 1051/1050/1018 1054/1052/1020\nf 1053/1051/1019 1039/1053/1021 1054/1052/1020\nf 1042/1040/1008 1023/1021/989 1054/1052/1020\nf 1037/1035/1003 1035/1033/1001 1054/1052/1020\nf 1056/1058/1026 50/1054/1022 1055/1055/1023\nf 49/38/37 48/1056/1024 1055/1055/1023\nf 1043/1041/1009 1040/1038/1006 1057/1057/1025\nf 1040/1038/1006 1039/1053/1021 1055/1055/1023\nf 1057/1057/1025 48/1056/1024 1058/1059/1027\nf 47/37/36 46/1060/1028 1058/1059/1027\nf 1059/1061/1029 1047/1046/1014 1058/1059/1027\nf 1045/1043/1011 1043/1041/1009 1058/1059/1027\nf 1059/1061/1029 46/1060/1028 1060/1062/1030\nf 43/34/33 42/1063/1031 1060/1062/1030\nf 1061/1064/1032 1051/1050/1018 1060/1062/1030\nf 1047/1046/1014 1059/1061/1029 1049/1047/1015\nf 1061/1064/1032 42/1063/1031 1062/1065/1033\nf 45/36/35 50/1054/1022 1062/1065/1033\nf 1039/1053/1021 1053/1051/1019 1056/1058/1026\nf 1051/1050/1018 1061/1064/1032 1053/1051/1019\nf 1064/1070/1038 401/1066/1034 1063/1067/1035\nf 400/394/360 399/1068/1036 1063/1067/1035\nf 496/488/455 493/485/452 1065/1069/1037\nf 493/485/452 492/501/468 1063/1067/1035\nf 1065/1069/1037 399/1068/1036 1066/1071/1039\nf 398/393/359 397/1072/1040 1066/1071/1039\nf 1067/1073/1041 500/494/461 1066/1071/1039\nf 498/490/457 496/488/455 1066/1071/1039\nf 1067/1073/1041 397/1072/1040 1068/1074/1042\nf 394/390/356 393/1075/1043 1068/1074/1042\nf 1069/1076/1044 504/498/465 1068/1074/1042\nf 500/494/461 1067/1073/1041 502/495/462\nf 1069/1076/1044 393/1075/1043 1070/1077/1045\nf 396/392/358 401/1066/1034 1070/1077/1045\nf 492/501/468 506/499/466 1064/1070/1038\nf 504/498/465 1069/1076/1044 506/499/466\nf 1074/1082/1050 1071/1078/1046 1073/1080/1048\nf 1075/1083/1051 1076/1081/1049 1072/1079/1047\nf 1076/1081/1049 346/469/436 1073/1080/1048\nf 344/461/427 1074/1082/1050 345/347/313\nf 1076/1081/1049 1075/1083/1051 1078/1085/1053\nf 1077/1084/1052 1079/1086/1054 1078/1085/1053\nf 1080/1087/1055 381/1088/1056 1078/1085/1053\nf 346/469/436 1076/1081/1049 475/468/435\nf 1079/1086/1054 1081/1089/1057 1080/1087/1055\nf 1081/1089/1057 1083/1091/1059 1082/1090/1058\nf 385/382/348 382/379/345 1084/1092/1060\nf 382/379/345 381/1088/1056 1082/1090/1058\nf 1084/1092/1060 1083/1091/1059 1086/1094/1062\nf 1071/1078/1046 1074/1082/1050 1085/1093/1061\nf 344/461/427 464/460/426 1074/1082/1050\nf 464/460/426 385/382/348 1086/1094/1062\nf 1090/1099/1067 1087/1095/1063 1089/1097/1065\nf 1091/1118/1086 1092/1098/1066 1088/1096/1064\nf 1092/1098/1066 1075/1083/1051 1089/1097/1065\nf 1071/1078/1046 1090/1099/1067 1072/1079/1047\nf 1091/1118/1086 1093/1100/1068 1092/1098/1066\nf 1093/1100/1068 1095/1102/1070 1094/1101/1069\nf 1079/1086/1054 1077/1084/1052 1096/1103/1071\nf 1075/1083/1051 1092/1098/1066 1077/1084/1052\nf 1095/1102/1070 1097/1104/1072 1096/1103/1071\nf 1097/1104/1072 1099/1106/1074 1098/1105/1073\nf 1083/1091/1059 1081/1089/1057 1100/1107/1075\nf 1081/1089/1057 1079/1086/1054 1098/1105/1073\nf 1100/1107/1075 1099/1106/1074 1102/1109/1077\nf 1087/1095/1063 1090/1099/1067 1101/1108/1076\nf 1090/1099/1067 1071/1078/1046 1102/1109/1077\nf 1083/1091/1059 1100/1107/1075 1085/1093/1061\nf 1103/1126/1094 1104/1110/1078 1106/1112/1080\nf 1104/1110/1078 1107/1113/1081 1105/1111/1079\nf 1091/1118/1086 1088/1096/1064 1108/1114/1082\nf 1088/1096/1064 1087/1095/1063 1105/1111/1079\nf 1107/1113/1081 1109/1115/1083 1108/1114/1082\nf 1111/1119/1087 1112/1117/1085 1109/1115/1083\nf 1112/1117/1085 1095/1102/1070 1110/1116/1084\nf 1093/1100/1068 1091/1118/1086 1110/1116/1084\nf 1112/1117/1085 1111/1119/1087 1114/1121/1089\nf 1115/1123/1091 1116/1122/1090 1113/1120/1088\nf 1116/1122/1090 1099/1106/1074 1114/1121/1089\nf 1095/1102/1070 1112/1117/1085 1097/1104/1072\nf 1116/1122/1090 1115/1123/1091 1118/1125/1093\nf 1117/1124/1092 1103/1126/1094 1118/1125/1093\nf 1087/1095/1063 1101/1108/1076 1106/1112/1080\nf 1099/1106/1074 1116/1122/1090 1101/1108/1076\nf 1122/1131/1099 1119/1127/1095 1121/1129/1097\nf 1123/1132/1100 1124/1130/1098 1120/1128/1096\nf 1124/1130/1098 1107/1113/1081 1121/1129/1097\nf 1104/1110/1078 1103/1126/1094 1121/1129/1097\nf 1124/1130/1098 1123/1132/1100 1126/1134/1102\nf 1127/1149/1118 1128/1135/1103 1125/1133/1101\nf 1128/1135/1103 1111/1119/1087 1126/1134/1102\nf 1107/1113/1081 1124/1130/1098 1109/1115/1083\nf 1127/1149/1118 1129/1136/1104 1128/1135/1103\nf 1129/1136/1104 1131/464/1106 1130/1137/1105\nf 1115/1123/1091 1113/1120/1088 1132/1138/1107\nf 1113/1120/1088 1111/1119/1087 1130/1137/1105\nf 1131/464/1106 1133/1139/1108 1132/1138/1107\nf 1133/1139/1108 1119/1127/1095 1134/1140/1109\nf 1103/1126/1094 1117/1124/1092 1122/1131/1099\nf 1117/1124/1092 1115/1123/1091 1134/1140/1109\nf 1136/1145/1114 473/1141/1110 1135/1142/1111\nf 472/466/433 471/1143/1112 1135/1142/1111\nf 1123/1132/1100 1120/1128/1096 1137/1144/1113\nf 1120/1128/1096 1119/1127/1095 1135/1142/1111\nf 1137/1144/1113 471/1143/1112 1138/1146/1115\nf 470/465/431 469/1147/1116 1138/1146/1115\nf 1139/1148/1117 1127/1149/1118 1138/1146/1115\nf 1123/1132/1100 1137/1144/1113 1125/1133/1101\nf 1139/1148/1117 469/1147/1116 1140/1150/1119\nf 466/462/428 465/1151/1120 1140/1150/1119\nf 1141/1152/1121 1131/464/1106 1140/1150/1119\nf 1127/1149/1118 1139/1148/1117 1129/1136/1104\nf 1141/1152/1121 465/1151/1120 1142/1153/1122\nf 468/464/430 473/1141/1110 1142/1153/1122\nf 1119/1127/1095 1133/1139/1108 1136/1145/1114\nf 1133/1139/1108 1131/464/1106 1142/1153/1122\nf 1146/1159/1128 1143/1154/1123 1145/1156/1125\nf 1147/1160/1129 1148/1157/1126 1144/1155/1124\nf 1148/1157/1126 833/1158/1127 1145/1156/1125\nf 829/827/794 1146/1159/1128 830/828/795\nf 1148/1157/1126 1147/1160/1129 1150/1162/1131\nf 1149/1161/1130 1151/1163/1132 1150/1162/1131\nf 837/834/801 835/832/799 1152/1164/1133\nf 835/832/799 833/1158/1127 1150/1162/1131\nf 1151/1163/1132 1153/1165/1134 1152/1164/1133\nf 1153/1165/1134 1155/1167/1136 1154/1166/1135\nf 848/845/812 859/857/824 1156/1168/1137\nf 837/834/801 1152/1164/1133 859/857/824\nf 1155/1167/1136 1157/1169/1138 1156/1168/1137\nf 1143/1154/1123 1146/1159/1128 1157/1169/1138\nf 1146/1159/1128 829/827/794 1158/1170/1139\nf 848/845/812 1156/1168/1137 849/846/813\nf 304/1185/1154 1159/1171/1140 1161/1173/1142\nf 1159/1171/1140 1162/1174/1143 1160/1172/1141\nf 1147/1160/1129 1144/1155/1124 1163/1175/1144\nf 1144/1155/1124 1143/1154/1123 1160/1172/1141\nf 1162/1174/1143 1164/1176/1145 1163/1175/1144\nf 1166/1179/1148 1167/1178/1147 1164/1176/1145\nf 1167/1178/1147 1151/1163/1132 1165/1177/1146\nf 1149/1161/1130 1147/1160/1129 1165/1177/1146\nf 1167/1178/1147 1166/1179/1148 1169/1181/1150\nf 306/1183/1152 1170/1182/1151 1168/1180/1149\nf 1170/1182/1151 1155/1167/1136 1169/1181/1150\nf 1151/1163/1132 1167/1178/1147 1153/1165/1134\nf 1170/1182/1151 306/1183/1152 1171/1184/1153\nf 305/311/277 304/1185/1154 1171/1184/1153\nf 1143/1154/1123 1157/1169/1138 1161/1173/1142\nf 1155/1167/1136 1170/1182/1151 1157/1169/1138\nf 302/309/275 355/353/319 303/310/276\nf 355/353/319 354/359/325 1172/1186/1155\nf 1162/1174/1143 1159/1171/1140 1173/1187/1156\nf 1159/1171/1140 304/1185/1154 1172/1186/1155\nf 354/359/325 359/358/324 1173/1187/1156\nf 358/357/323 1175/1189/1158 359/358/324\nf 1175/1189/1158 1166/1179/1148 1174/1188/1157\nf 1164/1176/1145 1162/1174/1143 1174/1188/1157\nf 1175/1189/1158 358/357/323 1176/1190/1159\nf 298/306/272 301/312/278 362/362/328\nf 301/312/278 306/1183/1152 1176/1190/1159\nf 1166/1179/1148 1175/1189/1158 1168/1180/1149\nf 1008/1004/972 1006/1002/970 1177/1191/1160\nf 1004/1000/968 1002/997/965 1177/1191/1160\nf 999/995/963 998/994/962 1177/1191/1160\nf 1010/1006/974 1008/1004/972 1178/1192/1161\nf 1182/1197/1166 1179/1193/1162 1181/1195/1164\nf 1183/1210/1179 1184/1196/1165 1180/1194/1163\nf 1184/1196/1165 977/974/942 1181/1195/1164\nf 973/969/937 1182/1197/1166 974/970/938\nf 1183/1210/1179 1185/1198/1167 1184/1196/1165\nf 1187/1201/1170 1188/1200/1169 1185/1198/1167\nf 998/994/962 1001/999/967 1188/1200/1169\nf 977/974/942 1184/1196/1165 1001/999/967\nf 1188/1200/1169 1187/1201/1170 1190/1203/1172\nf 1191/1205/1174 1192/1204/1173 1189/1202/1171\nf 1192/1204/1173 1010/1006/974 1190/1203/1172\nf 998/994/962 1188/1200/1169 1178/1192/1161\nf 1192/1204/1173 1191/1205/1174 1194/1207/1176\nf 1179/1193/1162 1182/1197/1166 1193/1206/1175\nf 973/969/937 1011/1007/975 1182/1197/1166\nf 1010/1006/974 1192/1204/1173 1011/1007/975\nf 1196/1211/1180 997/993/961 1195/1208/1177\nf 996/992/960 995/991/959 1195/1208/1177\nf 1197/1209/1178 1183/1210/1179 1195/1208/1177\nf 1179/1193/1162 1196/1211/1180 1180/1194/1163\nf 1197/1209/1178 995/991/959 1198/1212/1181\nf 994/990/958 993/989/957 1198/1212/1181\nf 1187/1201/1170 1185/1198/1167 1199/1213/1182\nf 1185/1198/1167 1183/1210/1179 1198/1212/1181\nf 1203/1222/1191 1200/1214/1183 1202/1216/1185\nf 1201/1215/1184 1204/1217/1186 1202/1216/1185\nf 1205/1218/1187 1206/1219/1188 1202/1216/1185\nf 1207/1220/1189 1208/1221/1190 1202/1216/1185\nf 989/1234/1203 992/988/956 1210/1224/1193\nf 992/988/956 997/993/961 1209/1223/1192\nf 1179/1193/1162 1193/1206/1175 1196/1211/1180\nf 1191/1205/1174 1210/1224/1193 1193/1206/1175\nf 1214/1229/1198 1211/1225/1194 1213/1227/1196\nf 1215/1230/1199 1216/1228/1197 1212/1226/1195\nf 1216/1228/1197 1191/1205/1174 1213/1227/1196\nf 1187/1201/1170 1214/1229/1198 1189/1202/1171\nf 1216/1228/1197 1215/1230/1199 1218/1232/1201\nf 1219/1255/1224 1220/1233/1202 1217/1231/1200\nf 1220/1233/1202 989/1234/1203 1218/1232/1201\nf 1191/1205/1174 1216/1228/1197 1210/1224/1193\nf 1219/1255/1224 1221/1235/1204 1220/1233/1202\nf 1221/1235/1204 1223/1237/1206 1222/1236/1205\nf 993/989/957 990/986/954 1224/1238/1207\nf 990/986/954 989/1234/1203 1222/1236/1205\nf 1223/1237/1206 1225/1239/1208 1224/1238/1207\nf 1225/1239/1208 1211/1225/1194 1226/1240/1209\nf 1187/1201/1170 1199/1213/1182 1214/1229/1198\nf 1199/1213/1182 993/989/957 1226/1240/1209\nf 1230/1245/1214 1227/1241/1210 1229/1243/1212\nf 1231/1246/1215 1232/1244/1213 1228/1242/1211\nf 1232/1244/1213 1215/1230/1199 1229/1243/1212\nf 1211/1225/1194 1230/1245/1214 1212/1226/1195\nf 1232/1244/1213 1231/1246/1215 1234/1248/1217\nf 1233/1247/1216 1235/1249/1218 1234/1248/1217\nf 1219/1255/1224 1217/1231/1200 1236/1250/1219\nf 1215/1230/1199 1232/1244/1213 1217/1231/1200\nf 1235/1249/1218 1237/1251/1220 1236/1250/1219\nf 1237/1251/1220 1239/1253/1222 1238/1252/1221\nf 1223/1237/1206 1221/1235/1204 1240/1254/1223\nf 1221/1235/1204 1219/1255/1224 1238/1252/1221\nf 1239/1253/1222 1241/1256/1225 1240/1254/1223\nf 1241/1256/1225 1227/1241/1210 1242/1257/1226\nf 1211/1225/1194 1225/1239/1208 1230/1245/1214\nf 1225/1239/1208 1223/1237/1206 1242/1257/1226\nf 1246/1262/1231 1243/1258/1227 1245/1260/1229\nf 1247/1263/1232 1248/1261/1230 1244/1259/1228\nf 1248/1261/1230 1231/1246/1215 1245/1260/1229\nf 1227/1241/1210 1246/1262/1231 1228/1242/1211\nf 1248/1261/1230 1247/1263/1232 1250/1265/1234\nf 1251/1278/1247 1252/1266/1235 1249/1264/1233\nf 1252/1266/1235 1235/1249/1218 1250/1265/1234\nf 1231/1246/1215 1248/1261/1230 1233/1247/1216\nf 1251/1278/1247 1253/1267/1236 1252/1266/1235\nf 1253/1267/1236 1255/1269/1238 1254/1268/1237\nf 1239/1253/1222 1237/1251/1220 1256/1270/1239\nf 1237/1251/1220 1235/1249/1218 1254/1268/1237\nf 1255/1269/1238 1257/1271/1240 1256/1270/1239\nf 1257/1271/1240 1243/1258/1227 1258/1272/1241\nf 1227/1241/1210 1241/1256/1225 1246/1262/1231\nf 1241/1256/1225 1239/1253/1222 1258/1272/1241\nf 1260/1275/1244 1208/1221/1190 1259/1273/1242\nf 1207/1220/1189 1206/1219/1188 1259/1273/1242\nf 1261/1274/1243 1247/1263/1232 1259/1273/1242\nf 1243/1258/1227 1260/1275/1244 1244/1259/1228\nf 1261/1274/1243 1206/1219/1188 1262/1276/1245\nf 1205/1218/1187 1204/1217/1186 1262/1276/1245\nf 1263/1277/1246 1251/1278/1247 1262/1276/1245\nf 1247/1263/1232 1261/1274/1243 1249/1264/1233\nf 1263/1277/1246 1204/1217/1186 1264/1279/1248\nf 1201/1215/1184 1200/1214/1183 1264/1279/1248\nf 1255/1269/1238 1253/1267/1236 1265/1280/1249\nf 1253/1267/1236 1251/1278/1247 1264/1279/1248\nf 1265/1280/1249 1200/1214/1183 1266/1281/1250\nf 1203/1222/1191 1208/1221/1190 1266/1281/1250\nf 1243/1258/1227 1257/1271/1240 1260/1275/1244\nf 1257/1271/1240 1255/1269/1238 1266/1281/1250\nf 1268/1286/1255 890/1282/1251 1267/1283/1252\nf 889/883/851 888/1284/1253 1267/1283/1252\nf 929/921/889 926/918/886 1269/1285/1254\nf 926/918/886 925/934/902 1267/1283/1252\nf 1269/1285/1254 888/1284/1253 1270/1287/1256\nf 887/882/850 886/1288/1257 1270/1287/1256\nf 933/927/895 931/924/892 1271/1289/1258\nf 931/924/892 929/921/889 1270/1287/1256\nf 1271/1289/1258 886/1288/1257 1272/1290/1259\nf 883/879/847 882/1291/1260 1272/1290/1259\nf 1273/1292/1261 937/931/899 1272/1290/1259\nf 933/927/895 1271/1289/1258 935/928/896\nf 1273/1292/1261 882/1291/1260 1274/1293/1262\nf 885/881/849 890/1282/1251 1274/1293/1262\nf 925/934/902 939/932/900 1268/1286/1255\nf 937/931/899 1273/1292/1261 939/932/900\n"
  },
  {
    "path": "static/models/target.obj",
    "content": "# Blender v2.76 (sub 0) OBJ File: 'target.blend'\n# www.blender.org\no Cylinder_Cylinder.002\nv 0.000000 0.000000 -0.319792\nv 0.062388 0.000000 -0.313648\nv -0.062388 0.000000 -0.313648\nv -0.122379 0.000000 -0.295450\nv -0.177667 0.000000 -0.265898\nv -0.226127 0.000000 -0.226128\nv -0.265897 0.000000 -0.177667\nv -0.295449 0.000000 -0.122380\nv -0.313648 0.000000 -0.062389\nv -0.319792 0.000000 -0.000000\nv -0.313648 0.000000 0.062388\nv -0.295450 0.000000 0.122379\nv -0.265898 0.000000 0.177667\nv -0.226127 0.000000 0.226127\nv -0.177667 0.000000 0.265898\nv -0.122379 0.000000 0.295450\nv -0.062389 0.000000 0.313648\nv -0.000000 0.000000 0.319792\nv -0.000000 0.000000 0.291944\nv -0.056955 0.000000 0.286334\nv -0.111722 0.000000 0.269721\nv -0.162195 0.000000 0.242742\nv -0.206435 0.000000 0.206435\nv -0.242742 0.000000 0.162195\nv -0.269721 0.000000 0.111722\nv -0.286334 0.000000 0.056955\nv -0.291943 0.000000 -0.000000\nv -0.286334 0.000000 -0.056956\nv -0.269720 0.000000 -0.111722\nv -0.242742 0.000000 -0.162195\nv -0.206435 0.000000 -0.206435\nv -0.162195 0.000000 -0.242742\nv -0.111722 0.000000 -0.269721\nv -0.056955 0.000000 -0.286334\nv -0.000000 0.000000 -0.291943\nv 0.056955 0.000000 -0.286334\nv 0.111722 0.000000 -0.269721\nv 0.162195 0.000000 -0.242742\nv 0.206435 0.000000 -0.206435\nv 0.242742 0.000000 -0.162195\nv 0.269721 0.000000 -0.111722\nv 0.286334 0.000000 -0.056955\nv 0.291943 0.000000 0.000000\nv 0.286334 0.000000 0.056955\nv 0.269721 0.000000 0.111722\nv 0.242742 0.000000 0.162195\nv 0.206435 0.000000 0.206435\nv 0.162195 0.000000 0.242742\nv 0.111722 0.000000 0.269721\nv 0.056955 0.000000 0.286334\nv 0.062388 0.000000 0.313648\nv 0.122379 0.000000 0.295450\nv 0.177667 0.000000 0.265898\nv 0.226127 0.000000 0.226127\nv 0.265898 0.000000 0.177667\nv 0.295450 0.000000 0.122379\nv 0.313648 0.000000 0.062388\nv 0.319792 0.000000 0.000000\nv 0.313648 0.000000 -0.062388\nv 0.295450 0.000000 -0.122379\nv 0.265898 0.000000 -0.177667\nv 0.226127 0.000000 -0.226127\nv 0.177667 0.000000 -0.265898\nv 0.122379 0.000000 -0.295450\nv -0.035452 0.000000 0.007052\nv -0.033395 0.000000 0.013833\nv -0.030055 0.000000 0.020082\nv -0.025560 0.000000 0.025560\nv -0.020082 0.000000 0.030055\nv -0.013833 0.000000 0.033395\nv -0.007052 0.000000 0.035452\nv -0.000000 0.000000 0.036147\nv 0.007052 0.000000 0.035452\nv 0.013833 0.000000 0.033395\nv 0.020082 0.000000 0.030055\nv 0.025560 0.000000 0.025560\nv 0.030055 0.000000 0.020082\nv 0.033395 0.000000 0.013833\nv 0.035452 0.000000 0.007052\nv 0.036147 0.000000 0.000000\nv 0.035452 0.000000 -0.007052\nv 0.033395 0.000000 -0.013833\nv 0.030055 0.000000 -0.020082\nv 0.025560 0.000000 -0.025559\nv 0.020082 0.000000 -0.030055\nv 0.013833 0.000000 -0.033395\nv 0.007052 0.000000 -0.035452\nv -0.000000 0.000000 -0.036147\nv -0.007052 0.000000 -0.035452\nv -0.013833 0.000000 -0.033395\nv -0.020082 0.000000 -0.030055\nv -0.025559 0.000000 -0.025559\nv -0.030055 0.000000 -0.020082\nv -0.033395 0.000000 -0.013833\nv -0.035452 0.000000 -0.007052\nv -0.036147 0.000000 0.000000\ns off\nf 10 27 28\nf 44 58 43\nf 80 88 96\nf 2 1 35\nf 3 4 33\nf 5 6 31\nf 7 8 29\nf 9 10 28\nf 11 12 26\nf 13 14 24\nf 15 16 22\nf 17 18 19\nf 35 36 2\nf 17 19 20\nf 34 35 1\nf 17 20 21\nf 34 1 3\nf 17 21 16\nf 33 34 3\nf 16 21 22\nf 32 33 4\nf 15 22 23\nf 32 4 5\nf 15 23 14\nf 31 32 5\nf 14 23 24\nf 30 31 6\nf 13 24 25\nf 30 6 7\nf 13 25 12\nf 29 30 7\nf 12 25 26\nf 28 29 8\nf 11 26 27\nf 28 8 9\nf 11 27 10\nf 64 2 37\nf 50 19 18\nf 2 36 37\nf 49 50 51\nf 64 37 38\nf 50 18 51\nf 64 38 63\nf 48 49 52\nf 63 38 39\nf 49 51 52\nf 63 39 62\nf 47 48 53\nf 62 39 40\nf 48 52 53\nf 62 40 61\nf 46 47 54\nf 61 40 41\nf 47 53 54\nf 61 41 60\nf 45 46 55\nf 60 41 42\nf 46 54 55\nf 60 42 59\nf 44 45 56\nf 59 42 43\nf 45 55 56\nf 59 43 58\nf 57 58 44\nf 44 56 57\nf 96 65 68\nf 66 67 68\nf 68 69 70\nf 70 71 72\nf 72 73 76\nf 74 75 76\nf 76 77 78\nf 78 79 76\nf 80 81 84\nf 82 83 84\nf 84 85 86\nf 86 87 84\nf 88 89 92\nf 90 91 92\nf 92 93 94\nf 94 95 92\nf 65 66 68\nf 68 70 96\nf 73 74 76\nf 76 79 80\nf 81 82 84\nf 84 87 88\nf 89 90 92\nf 92 95 96\nf 96 70 72\nf 72 76 96\nf 80 84 88\nf 88 92 96\nf 96 76 80\n"
  },
  {
    "path": "style/splash.scss",
    "content": "@font-face {\n  font-family: 'PlatformMedium';\n  src: url('/static/fonts/Platform-Medium-Web.eot');\n  src: url('/static/fonts/Platform-Medium-Web.eot?#iefix') format('embedded-opentype'),\n       url('/static/fonts/Platform-Medium-Web.woff2') format('woff2'),\n       url('/static/fonts/Platform-Medium-Web.woff') format('woff');\n}\n\n@font-face {\n  font-family: 'PlatformRegular';\n  src: url('/static/fonts/Platform-Regular-Web.eot');\n  src: url('/static/fonts/Platform-Regular-Web.eot?#iefix') format('embedded-opentype'),\n       url('/static/fonts/Platform-Regular-Web.woff2') format('woff2'),\n       url('/static/fonts/Platform-Regular-Web.woff') format('woff');\n}\n\n@font-face {\n  font-family: 'PlatformLight';\n  src: url('/static/fonts/Platform-Light-Web.eot');\n  src: url('/static/fonts/Platform-Light-Web.eot?#iefix') format('embedded-opentype'),\n       url('/static/fonts/Platform-Light-Web.woff2') format('woff2'),\n       url('/static/fonts/Platform-Light-Web.woff') format('woff');\n}\n\nhtml, body{\n  height: 100%;\n  font-family: 'PlatformRegular', sans-serif;\n  letter-spacing: 0.05em;\n  color: white;\n  overflow:hidden;\n  -webkit-tap-highlight-color: transparent;\n}\n\n\n@mixin mobilePhone {\n\t@media (max-width:540px), (max-height:540px){\n\t\t@content;\n\t}\n}\n\n// used to hide navbars in older mobile browsers\n#spacer { height: 1px; }\n\n#splash {\n\tz-index:1000;\n\twidth: 100%;\n\theight: 100%;\n\tposition: absolute;\n\ttop: 0px;\n\tleft: 0px;\n\tdisplay: block;\n\toverflow:hidden;\n\n\t&.invisible {\n\t\tdisplay: none;\n\t}\n\n\t// ----------------------------------------------------\n\t// background image\n\t// ----------------------------------------------------\n\n\t@media screen and (max-aspect-ratio: 1/1){\n\t\tbackground-image: url(/static/img/SplashMobileHiRes2.jpg);\n\t}\n\n\t$hiResCross : 1080px;\n\n\t@media screen and (min-aspect-ratio: 1/1) and (max-width: $hiResCross){\n\t\tbackground-image: url(/static/img/SplashDesktopMedR.jpg);\n\t}\n\n\t@media screen and (min-aspect-ratio: 1/1) and (min-width: $hiResCross){\n\t\tbackground-image: url(/static/img/SplashDesktopHiR.jpg) ;\n\t}\n\n\t@media screen and (max-height: 600px){\n\t\tbackground-position-y: -50px;\n\t}\n\n\tbackground-position-x: center;\n\tbackground-position-y: center;\n\tbackground-repeat:  no-repeat;\n\tbackground-attachment: fixed;\n\tbackground-size:  cover;\n    background-color: rgb(255,161,153);\n\n    // ----------------------------------------------------\n    // enter buttons\n    // ----------------------------------------------------\n\n\t#webvr-loader {\n\n\t}\n\n\t$buttonWidth : 195px;\n\t$buttonHeight : 55px;\n\n\t#enter-container {\n\t\tposition: absolute;\n\t\tleft: 50%;\n        bottom: 25%;\n\t\t@media screen and (max-height: 320px){\n\t\t\tbottom: 60px !important;\n\t\t\ttransform: scale(0.6) translate(-80%, 0);\n\t\t}\n\t\theight: $buttonHeight;\n\t\twidth: $buttonWidth;\n\t\ttransform: translate(-50%, 0);\n\t\tpointer-events: none;\n\n\t\t&.loaded {\n\n\t\t\tpointer-events: initial;\n\n\t\t\t#enterButton {\n\t\t\t\topacity: 1;\n\t\t\t}\n\n\t\t\t#loader {\n\t\t\t\topacity: 0;\n\t\t\t}\n\t\t}\n\n\t\t#loader {\n\t\t\tposition: absolute;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\tline-height: $buttonHeight;\n\t\t\tborder: 2px solid white;\n\t\t\tbox-sizing: border-box;\n\t\t\tpadding-left: 45px;\n\t\t\tfont-weight: 500;\n            letter-spacing: 0.05em;\n\n\t\t\timg {\n\t\t\t\tposition: absolute;\n\t\t\t\tright: 40px;\n\t\t\t\ttop: 48%;\n\t\t\t\ttransform: translate(0, -50%);\n\t\t\t\twidth: $buttonHeight * 0.5;\n\t\t\t\theight: $buttonHeight * 0.45;\n\t\t\t}\n\t\t}\n\n\t\t#loader, #enterButton{\n\t\t\ttransition: opacity 0.2s;\n\t\t}\n\n\t\t#enterButton {\n\t\t\tfont-family: 'PlatformRegular', sans-serif;\n            letter-spacing: 0.05em;\n\t\t\tdisplay: block;\n\t\t\tfont-size: 16px;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\topacity: 0;\n\n\n\t\t\t.webvr-ui-button {\n\t\t\t\tborder: 2px solid white;\n\t\t\t\tposition: absolute;\n\t\t\t\ttransform: translate(-50%, 0)!important;\n\t\t\t\ttop: 0px!important;\n\t\t\t\tbackground-color: transparent;\n\t\t\t\twhite-space: nowrap;\n\t\t\t\t.webvr-ui-title {\n\t\t\t\t\tfont-size: 16px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t#enter-360{\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 50%;\n\t\t\t\tbottom: -30px;\n\t\t\t\ttransform: translate(-50%, 0);\n\t\t\t\twidth: 100%;\n\t\t\t\tfont-size: 14px;\n\t\t\t\ttext-align: center;\n\t\t\t\twidth: 220px;\n\t\t\t\ttext-decoration: underline;\n\t\t\t\tcursor: pointer;\n\n\t\t\t\t@include mobilePhone {\n\t\t\t\t\tfont-size: 12px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// ----------------------------------------------------\n\t// about secondary\n\t// ----------------------------------------------------\n\n\t$headphoneSize : 25px;\n\t$topCornerMargin : 20px;\n\t$buttonSize : 30px;\n\t$pink : rgb(255,175,170);\n    $mediumSize : 700px;\n    $desktopMargin : 20%;\n    $mobileMargin : 5%;\n\n\t#headphones {\n\t\tposition: absolute;\n        left: $topCornerMargin;\n\t\ttop: $topCornerMargin + 5px;\n\t\twidth: $headphoneSize;\n\t\theight: $headphoneSize;\n\t\toverflow: hidden;\n\t\ttransition: width 0.15s;\n\n\t\t&:hover {\n\t\t\twidth: 210px;\n\t\t}\n\n\t\t.icon {\n\t\t\theight: 90%;\n\t\t\twidth: auto;\n\t\t\tposition: absolute;\n\t\t\tleft: 3.5px;\n\t\t}\n\n\t\t.text {\n\t\t\tline-height: $buttonSize;\n\t\t\theight: $buttonSize;\n\t\t\tfont-size: 12px;\n\t\t\twidth: 170px;\n\t\t\tposition: absolute;\n\t\t\ttext-align: right;\n\t\t\tleft: $headphoneSize + 5px;\n\t\t}\n\t}\n\n\t#headphones, #about-button {\n\t\tbackground-color: white;\n\t\tborder-radius: $buttonSize/2;\n\t\twidth: $buttonSize;\n\t\theight: $buttonSize;\n\t\tcolor: $pink;\n\t\ttext-align: center;\n\t\tline-height: $buttonSize;\n\t\topacity: 0.9;\n\t}\n\n\t#about-button {\n\t\tz-index: 1;\n\t\ttop: $topCornerMargin;\n\t\tright: $topCornerMargin;\n\t\tvisibility: hidden;\n\t\tpadding: 1px;\n\t\tposition: absolute;\n\t\tcursor: pointer;\n        font-family: 'PlatformLight', sans-serif;\n\t\tfont-size: 20px;\n\t\timg {\n\t\t\twidth: 30px;\n\t\t}\n\t}\n\n\t#about-button.visible {\n\t\tvisibility: visible;\n\t}\n\n    // ----------------------------------------------------\n\t// about\n\t// ----------------------------------------------------\n\t#about {\n\t\tz-index: 1;\n\t\toverflow: hidden;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\toverflow: auto;\n\t\tposition: absolute !important;\n\t\tvisibility: hidden;\n\t\tbackground-color: rgba(247,158,151,0.97);\n\t\tz-index: 1000;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n        font-size: calc(0.8em) !important;\n\n        .content {\n            margin-top: -0vh;\n            margin-right:$desktopMargin;\n            margin-left: $desktopMargin;\n\n            @media (max-width:$mediumSize) {\n                margin-right:$mobileMargin;\n                margin-left: $mobileMargin;\n            }\n        }\n        h1 {\n            font-family: 'PlatformMedium', sans-serif;\n            font-weight: 100;\n            font-size: 7.0vw;\n            line-height: 0%;\n        }\n\n        .description {\n            left:0px;\n            margin-left:10px;\n\n            @media (max-width:$mediumSize) {\n                margin-left:0px;\n            }\n\t\t\tmargin-top: -0vh;\n            text-decoration: none;\n            p {\n                font-family: 'PlatformLight', sans-serif;\n                max-width: 50%;\n                font-weight: 100;\n                line-height: 140%;\n        \t\tcolor: #FFF;\n\n                @media (max-width:$mediumSize) {\n                    max-width: 100%;\n                }\n\n                a {\n                    font-weight: 300;\n            \t\ttext-decoration: underline;\n            \t\tcolor: #8D81E8;\n            \t}\n        \t}\n        }\n\n        img {\n            z-index:-1;\n            position: absolute;\n            top: 45%;\n            right: 0px;\n            margin-right:25%;\n            width: 20%;\n            height: auto;\n            display:block;\n\n            @media (max-width:$mediumSize) {\n                top: initial;\n                left: 50%;\n                transform: translateX(-50%);\n                bottom: 0%;\n                width: 50%;\n            }\n\n            @media screen and (orientation:portrait) and (max-width:$mediumSize) {\n                display: block;\n            }\n            @media screen and (orientation:landscape) and (max-width:$mediumSize){\n                display: none;\n            }\n        }\n\n\t\t.xbutton {\n\t\t\twidth:20px;\n\t\t\theight:20px;\n\t\t\tfont-size: 20px;\n\t\t\tborder: none;\n\t\t\tposition: fixed;\n\t\t\ttop: 20px;\n\t\t\tright: 20px;\n\t\t\tcursor: pointer;\n\t\t}\n\n\t\t.close {\n\t\t\t// position: fixed;\n\t\t\t// width: 32px;\n\t\t\t// height: 32px;\n\t\t\t// opacity: 0.75;\n\t\t}\n\n\t\t.close:hover {\n\t\t\topacity: 1;\n\t\t}\n\n\t\t.close:before, .close:after {\n\t\t\tposition: absolute;\n\t\t\tleft: 15px;\n\t\t\tcontent: ' ';\n\t\t\theight: 33px;\n\t\t\twidth: 2px;\n\t\t\tbackground-color: #FFF;\n\t\t}\n\n\t\t.close:before {\n\t\t\ttransform: rotate(45deg);\n\t\t}\n\n\t\t.close:after {\n\t\t\ttransform: rotate(-45deg);\n\t\t}\n\n\t}\n\n\t#about.visible {\n\t\tvisibility: visible;\n\t}\n\n    // ----------------------------------------------------\n\t// badges\n\t// ----------------------------------------------------\n\t#badges {\n\n\t\t.friends-with, .webvr-logo, .pipe {\n\t\t\tposition: absolute;\n\t\t\tbottom: 17px;\n\t\t}\n\t\t.friends-with img, .webvr-logo img {\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.webvr-logo {\n\t\t\twidth: 100px;\n\t\t\tleft: 15px;\n\t\t}\n\t\t.friends-with {\n\t\t\twidth: 100px;\n\t\t\tleft: 145px;\n\t\t}\n\t\t.pipe {\n\t\t\tleft: 130px;\n\t\t\theight:60px;\n\t\t\tborder-right: 1px solid rgba(255,255,255,0.75);\n\t\t}\n\n\t\t@include mobilePhone {\n\t\t\t.friends-with img, .webvr-logo img {\n\t\t\t\twidth: 70%;\n\t\t\t}\n\t\t\t.friends-with {\n\t\t\t\tleft: 115px;\n\t\t\t\twidth: 100px;\n\t\t\t}\n\t\t\t.pipe {\n\t\t\t\tleft: 100px;\n\t\t\t\theight:45px;\n\t\t\t\tborder-right: 1px solid rgba(255,255,255,0.75);\n\t\t\t}\n\t\t}\n\t}\n\n\t#legal {\n\t\topacity: 0.75;\n\t\tposition:absolute;\n\t\tright: 20px;\n\t\tbottom: 21px;\n\t\tfont-family: 'PlatformLight', sans-serif;\n\t\tfont-size: 11px;\n\t\tcolor: #FFF;\n\t\ta {\n\t\t\ttext-decoration: none;\n\t\t\tcolor: #FFF;\n\t\t}\n\n\t\t@include mobilePhone {\n\t\t\tfont-size: 9px;\n\t\t}\n\t}\n}\n\n@mixin fullScreenBg() {\n    z-index:1000;\n    width: 100%;\n    height: 100%;\n    position: absolute;\n    top: 0px;\n    left: 0px;\n    overflow:hidden;\n\n    background-position-x: center;\n    background-position-y: center;\n    background-repeat:  no-repeat;\n    background-attachment: fixed;\n    background-size:  cover;\n    background-color: rgba(247,158,151,1.0);\n}\n\n#cardboard {\n    @include fullScreenBg();\n\n    @media screen and (orientation:portrait) {\n        display: block;\n    }\n    @media screen and (orientation:landscape) {\n        display: none;\n    }\n\n    #cardboardContainer {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate(-50%, -50%);\n\n        background-color: \"#f79e97\";\n\n        p {\n            font-weight: 100;\n            text-decoration: none;\n            line-height: 150%;\n            color: #FFF;\n        }\n\n        button {\n            cursor: pointer;\n            font-family: 'PlatformMedium', sans-serif;\n            letter-spacing: 0.05em;\n            font-size: 13px;\n            color: white;\n            padding-left:20px;\n            padding-right:20px;\n            height: 50px;\n            background-color: rgba(253,161,155,0.75);\n            border: white 2px solid;\n\n\n            position: absolute;\n            bottom: 0px;\n            right: 0px;\n\n        }\n\n        img{\n            display: block;\n            min-width:340px;\n            min-height:244px;\n            width: 100%;\n            height: 100%;\n        }\n    }\n}\n\n#error {\n    @include fullScreenBg();\n    display: block;\n\n    #errorContainer {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate(-50%, -50%);\n        min-width:320px;\n        min-height:240px;\n\n        background-color: \"#ff0\";\n\n        p {\n            position: absolute;\n            padding: 0px;\n            margin: 0px;\n            top: 0px;\n            left: 0px;\n            max-width:200px;\n        }\n\n        img{\n            max-width:400px;\n            max-height:300px;\n            width: 100%;\n            height: 100%;\n            position: absolute;\n            top: 72%;\n            left: 50%;\n            transform: translate(-50%, -50%);\n        }\n\n        button {\n            cursor: pointer;\n            font-family: 'PlatformMedium', sans-serif;\n            letter-spacing: 0.05em;\n            font-size: 13px;\n            color: white;\n            position: absolute;\n            padding-left:20px;\n            padding-right:20px;\n            height: 50px;\n            background-color: rgba(253,161,155,0.75);\n            border: white 2px solid;\n            top: 0px;\n            right: 0px;\n        }\n    }\n}\n"
  },
  {
    "path": "third_party/aframe-daydream-controller-component/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Kevin Ngo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "third_party/aframe-daydream-controller-component/METADATA",
    "content": "name: \"aframe-daydream-controller-component\"\ndescription:\n    \"Aframe component for daydream\"\n\nthird_party {\n  url {\n    type: GIT\n    value: \"https://github.com/ryanbetts/aframe-daydream-controller-component\"\n  }\n  version: \"\"\n  last_upgrade_date { year: 2017 month: 3 day: 13 }\n}"
  },
  {
    "path": "third_party/aframe-daydream-controller-component/daydream-controller.js",
    "content": "import OrientationArmModel from '../../js/orientation-arm-model'\n\nif (typeof AFRAME === 'undefined') {\n    throw new Error('Component attempted to register before AFRAME was available.');\n}\n\nvar bind = AFRAME.utils.bind;\nvar trackedControlsUtils = AFRAME.utils.trackedControls;\n\n\nvar GAMEPAD_ID_PREFIX = 'Daydream Controller';\n\n/**\n * Daydream Controller component for A-Frame.\n */\nAFRAME.registerComponent('daydream-controller', {\n    armModel: null,\n\n    /**\n     * Set if component needs multiple instancing.\n     */\n    multiple: false,\n\n    schema: {\n        controller: {\n            default: 0\n        },\n        id: {\n            default: 'Match none by default!'\n        },\n        rotationOffset: {\n            default: 0\n        },\n        hand: {\n            default: 'left'\n        },\n        buttonColor: {\n            default: '#FAFAFA'\n        }, // Off-white.\n        buttonTouchedColor: {\n            default: 'yellow'\n        }, // Light blue.\n        buttonPressedColor: {\n            default: 'orange'\n        }, // Light blue.\n        model: {\n            default: true\n        },\n        rotationOffset: {\n            default: 0\n        }, // use -999 as sentinel value to auto-determine based on hand\n        gestureTimeoutLimit: {\n            default: 100\n        }, //if gesture doesn't complete within this timeframe, reset\n        gestureTolerance: {\n            default: 0.2\n        } //percentage of the trackpad a gesture must traverse\n    },\n\n    // buttonId\n    // 0 - trackpad\n    mapping: {\n        axis0: 'trackpad',\n        axis1: 'trackpad',\n        button0: 'trackpad',\n        // button1: 'menu',\n        // button2: 'system'\n    },\n\n    bindMethods: function() {\n        this.onModelLoaded = bind(this.onModelLoaded, this);\n        this.onControllersUpdate = bind(this.onControllersUpdate, this);\n        this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this);\n        this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this);\n        this.onGamepadConnected = bind(this.onGamepadConnected, this);\n        this.onGamepadDisconnected = bind(this.onGamepadDisconnected, this);\n    },\n\n    /**\n     * Called once when component is attached. Generally for initial setup.\n     */\n    init: function() {\n        var self = this;\n        this.animationActive = 'pointing';\n        this.onButtonChanged = bind(this.onButtonChanged, this);\n        this.onButtonDown = function(evt) {\n            self.onButtonEvent(evt.detail.id, 'down');\n        };\n        this.onButtonUp = function(evt) {\n            self.onButtonEvent(evt.detail.id, 'up');\n        };\n        this.onButtonTouchStart = function(evt) {\n            self.onButtonEvent(evt.detail.id, 'touchstart');\n            evt.stopPropagation();\n            evt.preventDefault();\n        };\n        this.onButtonTouchEnd = function(evt) {\n            self.onButtonEvent(evt.detail.id, 'touchend');\n        };\n        this.onAxisMove = bind(this.onAxisMove, this);\n        this.controllerPresent = false;\n        this.everGotGamepadEvent = false;\n        this.lastControllerCheck = 0;\n        this.bindMethods();\n        this.isControllerPresent = trackedControlsUtils.isControllerPresent; // to allow mock\n        this.axisGestureTimeoutLimit = 100; //minimum\n        this.axisGestureVelocity = 100; // minimum velocity in %/ms\n        this.axisGestureThreshold = 0.1; // minimum % moved to recognize a gesture\n        this.buttonStates = {};\n        this.previousAxis = [];\n        this.previousControllerPosition = new THREE.Vector3();\n        this.armModel = new OrientationArmModel();\n        // var camera = document.querySelector(\"#avatar\");\n        // camera = camera.object3D;\n        // this.armModel.setHeadPosition(camera.position);\n\n        this.armModel.setHeadPosition({x:0,y:1.6,z:0});\n\n    },\n\n    addEventListeners: function() {\n        var el = this.el;\n        el.addEventListener('buttonchanged', this.onButtonChanged);\n        el.addEventListener('buttondown', this.onButtonDown);\n        el.addEventListener('buttonup', this.onButtonUp);\n        el.addEventListener('touchstart', this.onButtonTouchStart);\n        el.addEventListener('axismove', this.onAxisMove);\n        el.addEventListener('touchend', this.onButtonTouchEnd);\n        el.addEventListener('model-loaded', this.onModelLoaded);\n    },\n\n    removeEventListeners: function() {\n        var el = this.el;\n        el.removeEventListener('buttonchanged', this.onButtonChanged);\n        el.removeEventListener('buttondown', this.onButtonDown);\n        el.removeEventListener('buttonup', this.onButtonUp);\n        el.removeEventListener('touchstart', this.onButtonTouchStart);\n        el.removeEventListener('axismove', this.onAxisMove);\n        el.removeEventListener('touchend', this.onButtonTouchEnd);\n        el.removeEventListener('model-loaded', this.onModelLoaded);\n    },\n\n    /**\n     * Called when a component is removed (e.g., via removeAttribute).\n     * Generally undoes all modifications to the entity.\n     */\n    // TODO ... remove: function () { },\n\n    getControllerIfPresent: function() {\n        // The 'Gear VR Touchpad' gamepad exposed by Carmel has no pose,\n        // so it won't show up in the tracked-controls system controllers.\n        var gamepads = this.getGamepadsByPrefix(GAMEPAD_ID_PREFIX);\n        if (!gamepads || !gamepads.length) {\n            return undefined;\n        }\n        return gamepads[0];\n    },\n\n    checkIfControllerPresent: function() {\n        var data = this.data;\n        var isPresent = this.isControllerPresent(this.el.sceneEl, GAMEPAD_ID_PREFIX, {});\n        if (isPresent === this.controllerPresent) {\n            return;\n        }\n        this.controllerPresent = isPresent;\n        if (isPresent) {\n            this.injectTrackedControls(); // inject track-controls\n            this.addEventListeners();\n        } else {\n            this.removeEventListeners();\n        }\n    },\n\n    onGamepadConnected: function(evt) {\n        // for now, don't disable controller update listening, due to\n        // apparent issue with FF Nightly only sending one event and seeing one controller;\n        // this.everGotGamepadEvent = true;\n        // this.removeControllersUpdateListener();\n        this.checkIfControllerPresent();\n    },\n\n    onGamepadDisconnected: function(evt) {\n        // for now, don't disable controller update listening, due to\n        // apparent issue with FF Nightly only sending one event and seeing one controller;\n        // this.everGotGamepadEvent = true;\n        // this.removeControllersUpdateListener();\n        this.checkIfControllerPresent();\n    },\n\n    tick: function() {\n        var mesh = this.el.getObject3D('mesh');\n        // Update mesh animations.\n        if (mesh && mesh.update) {\n            mesh.update(delta / 1000);\n        }\n        this.updatePose();\n        this.updateButtons();\n    },\n\n    /**\n     * Called when entity resumes.\n     * Use to continue or add any dynamic or background behavior such as events.\n     */\n    play: function() {\n        this.checkIfControllerPresent();\n        window.addEventListener('gamepadconnected', this.onGamepadConnected, false);\n        window.addEventListener('gamepaddisconnected', this.onGamepadDisconnected, false);\n        this.addControllersUpdateListener();\n    },\n\n    /**\n     * Called when entity pauses.\n     * Use to stop or remove any dynamic or background behavior such as events.\n     */\n    pause: function() {\n        window.removeEventListener('gamepadconnected', this.onGamepadConnected, false);\n        window.removeEventListener('gamepaddisconnected', this.onGamepadDisconnected, false);\n        this.removeControllersUpdateListener();\n        this.removeEventListeners();\n    },\n\n    injectTrackedControls: function() {\n        var el = this.el;\n        var data = this.data;\n\n        this.controller = trackedControlsUtils.getGamepadsByPrefix(GAMEPAD_ID_PREFIX)[0]\n\n        // if we have an OpenVR Gamepad, use the fixed mapping\n        // el.setAttribute('tracked-controls', {id: GAMEPAD_ID_PREFIX, rotationOffset: data.rotationOffset});\n\n        if (!data.model) {\n            return;\n        }\n\n    },\n\n    addControllersUpdateListener: function() {\n        this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false);\n    },\n\n    removeControllersUpdateListener: function() {\n        this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false);\n    },\n\n    onControllersUpdate: function() {\n        if (!this.everGotGamepadEvent) {\n            this.checkIfControllerPresent();\n        }\n    },\n\n    onButtonChanged: function(evt) {\n        var button = this.mapping['button' + evt.detail.id];\n        var buttonMeshes = this.buttonMeshes;\n        var value;\n        value = evt.detail.state.value;\n    },\n\n    onAxisMove: function(evt) {\n        // this.axisPosition\n        //\n        // console.log('axismove', evt.detail);\n        // this.lastAxisMovement = {\n        //   time: Date.now(),\n        //   x: 0,\n        //   y: 0\n        // }\n    },\n\n    onModelLoaded: function(evt) {\n        var controllerObject3D = evt.detail.model;\n        var buttonMeshes;\n        if (!this.data.model) {\n            return;\n        }\n        buttonMeshes = this.buttonMeshes = {};\n        buttonMeshes.menu = controllerObject3D.getObjectByName('menubutton');\n        buttonMeshes.system = controllerObject3D.getObjectByName('systembutton');\n        buttonMeshes.trackpad = controllerObject3D.getObjectByName('touchpad');\n        // Offset pivot point\n        controllerObject3D.position.set(0, -0.015, 0.04);\n    },\n\n    onButtonEvent: function(id, evtName) {\n        var buttonName = this.mapping['button' + id];\n        if(!buttonName) { return; }\n        var i;\n        if (Array.isArray(buttonName)) {\n            for (i = 0; i < buttonName.length; i++) {\n                this.el.emit(buttonName[i] + evtName);\n            }\n        } else {\n            this.el.emit(buttonName + evtName);\n        }\n        // this.updateModel(buttonName, evtName);\n    },\n\n    updateModel: function(buttonName, evtName) {\n        var i;\n        if (!this.data.model) {\n            return;\n        }\n        if (Array.isArray(buttonName)) {\n            for (i = 0; i < buttonName.length; i++) {\n                this.updateButtonModel(buttonName[i], evtName);\n            }\n        } else {\n            this.updateButtonModel(buttonName, evtName);\n        }\n    },\n\n    updateButtonModel: function(buttonName, state) {\n        var color = this.data.buttonColor;\n        if (state === 'touchstart' || state === 'up') {\n            color = this.data.buttonTouchedColor;\n        } else if (state === 'down') {\n            color = this.data.buttonPressedColor;\n        }\n        var buttonMeshes = this.buttonMeshes;\n        if (!buttonMeshes) {\n            return;\n        }\n        buttonMeshes[buttonName].material.color.set(color);\n    },\n\n    /*  */\n\n    updatePose: (function() {\n        var controllerEuler = new THREE.Euler();\n        var controllerPosition = new THREE.Vector3();\n        var controllerQuaternion = new THREE.Quaternion();\n        var deltaControllerPosition = new THREE.Vector3();\n        var dolly = new THREE.Object3D();\n        var standingMatrix = new THREE.Matrix4();\n        controllerEuler.order = 'YXZ';\n        return function() {\n            var camera = document.querySelector(\"#avatar\");\n            if(!camera) return;\n            camera = camera.object3D;\n\n            var pose;\n            var orientation;\n            var position;\n            var el = this.el;\n            var controller = this.controller;\n            if (!this.controller) {\n                return;\n            }\n            pose = controller.pose;\n            orientation = pose.orientation || [0, 0, 0, 1];\n            position = pose.position || [0, 0, 0];\n            // var camera = this.el.sceneEl.camera;\n            controllerQuaternion.fromArray(orientation);\n            // Feed camera and controller into the arm model.\n            this.armModel.setHeadOrientation(camera.quaternion);\n            // no need to set the head position anymore because it is located inside camera\n            this.armModel.setHeadPosition({x:0,y:1.6,z:0});\n            this.armModel.setControllerOrientation(controllerQuaternion);\n            this.armModel.update();\n            // Get resulting pose and configure the renderer.\n            var modelPose = this.armModel.getPose();\n            controllerEuler.setFromQuaternion(modelPose.orientation)\n            el.setAttribute('rotation', {\n                x: THREE.Math.radToDeg(controllerEuler.x),\n                y: THREE.Math.radToDeg(controllerEuler.y),\n                z: THREE.Math.radToDeg(controllerEuler.z) + this.data.rotationOffset\n            });\n            // console.log(modelPose.position);\n            el.setAttribute('position', {\n                x: modelPose.position.x,\n                y: modelPose.position.y,\n                z: modelPose.position.z\n            });\n        }\n    })(),\n\n    updateButtons: function() {\n        var i;\n        var buttonState;\n        var controller = this.controller;\n        if (!this.controller) {\n            return;\n        }\n        for (i = 0; i < controller.buttons.length; ++i) {\n            buttonState = controller.buttons[i];\n            this.handleButton(i, buttonState);\n        }\n        this.handleAxes(controller.axes);\n    },\n\n    handleAxes: function(controllerAxes) {\n        var previousAxis = this.previousAxis;\n        var changed = false;\n        var i;\n        for (i = 0; i < controllerAxes.length; ++i) {\n            if (previousAxis[i] !== controllerAxes[i]) {\n                changed = true;\n                break;\n            }\n        }\n        if (!changed) {\n            return;\n        }\n        this.previousAxis = controllerAxes.slice();\n        this.el.emit('axismove', {\n            axis: this.previousAxis\n        });\n    },\n\n    handleButton: function(id, buttonState) {\n        var changed = false;\n        changed = changed || this.handlePress(id, buttonState);\n        changed = changed || this.handleTouch(id, buttonState);\n        changed = changed || this.handleValue(id, buttonState);\n        if (!changed) {\n            return;\n        }\n        this.el.emit('buttonchanged', {\n            id: id,\n            state: buttonState\n        });\n    },\n\n    /**\n     * Determine whether a button press has occured and emit events as appropriate.\n     *\n     * @param {string} id - id of the button to check.\n     * @param {object} buttonState - state of the button to check.\n     * @returns {boolean} true if button press state changed, false otherwise.\n     */\n    handlePress: function(id, buttonState) {\n        var buttonStates = this.buttonStates;\n        var evtName;\n        var previousButtonState = buttonStates[id] = buttonStates[id] || {};\n        if (buttonState.pressed === previousButtonState.pressed) {\n            return false;\n        }\n        if (buttonState.pressed) {\n            evtName = 'down';\n        } else {\n            evtName = 'up';\n        }\n        this.el.emit('button' + evtName, {\n            id: id\n        });\n        previousButtonState.pressed = buttonState.pressed;\n        return true;\n    },\n\n    /**\n     * Determine whether a button touch has occured and emit events as appropriate.\n     *\n     * @param {string} id - id of the button to check.\n     * @param {object} buttonState - state of the button to check.\n     * @returns {boolean} true if button touch state changed, false otherwise.\n     */\n    handleTouch: function(id, buttonState) {\n        var buttonStates = this.buttonStates;\n        var evtName;\n        var previousButtonState = buttonStates[id] = buttonStates[id] || {};\n        if (buttonState.touched === previousButtonState.touched) {\n            return false;\n        }\n        if (buttonState.touched) {\n            evtName = 'start';\n        } else {\n            evtName = 'end';\n        }\n        previousButtonState.touched = buttonState.touched;\n        var touches = [];\n        this.el.emit('touch' + evtName, {\n            id: id,\n            state: previousButtonState,\n            touches: touches\n        });\n        return true;\n    },\n\n    /**\n     * Determine whether a button value has changed.\n     *\n     * @param {string} id - id of the button to check.\n     * @param {object} buttonState - state of the button to check.\n     * @returns {boolean} true if button value changed, false otherwise.\n     */\n    handleValue: function(id, buttonState) {\n        var buttonStates = this.buttonStates;\n        var previousButtonState = buttonStates[id] = buttonStates[id] || {};\n        if (buttonState.value === previousButtonState.value) {\n            return false;\n        }\n        previousButtonState.value = buttonState.value;\n        return true;\n    }\n});\n"
  }
]