[med-svn] [emperor] 12/15: Imported Upstream version 0.9.3+dfsg

Afif Elghraoui afif at moszumanska.debian.org
Thu Oct 13 06:22:56 UTC 2016


This is an automated email from the git hooks/post-receive script.

afif pushed a commit to branch master
in repository emperor.

commit da2580ee14c08fad470f9e6f3af0802eeea14b4c
Author: Afif Elghraoui <afif at ghraoui.name>
Date:   Wed Oct 12 23:12:52 2016 -0700

    Imported Upstream version 0.9.3+dfsg
---
 .gitignore                                         |    42 +
 .travis.yml                                        |     7 +
 ChangeLog.md                                       |    90 +
 INSTALL.md                                         |    28 +
 LICENSE.md                                         |   223 +
 MANIFEST.in                                        |    15 +
 README.md                                          |    34 +
 doc/description_index.html                         |   209 +
 doc/img/axes_menu.png                              |   Bin 0 -> 16003 bytes
 doc/img/colors_menu.png                            |   Bin 0 -> 16257 bytes
 doc/img/description_1.png                          |   Bin 0 -> 66096 bytes
 doc/img/emperor_heading.png                        |   Bin 0 -> 12582 bytes
 doc/img/favicon.ico                                |   Bin 0 -> 5558 bytes
 doc/img/key_menu.png                               |   Bin 0 -> 17838 bytes
 doc/img/labels_menu.png                            |   Bin 0 -> 19279 bytes
 doc/img/options_menu.png                           |   Bin 0 -> 15108 bytes
 doc/img/parallel_plots.png                         |   Bin 0 -> 208007 bytes
 doc/img/sample_1.png                               |   Bin 0 -> 78421 bytes
 doc/img/scaling_menu.png                           |   Bin 0 -> 21444 bytes
 doc/img/tutorial/1.png                             |   Bin 0 -> 134941 bytes
 doc/img/tutorial/10.png                            |   Bin 0 -> 137743 bytes
 doc/img/tutorial/11.png                            |   Bin 0 -> 125936 bytes
 doc/img/tutorial/2.png                             |   Bin 0 -> 104168 bytes
 doc/img/tutorial/6.png                             |   Bin 0 -> 108753 bytes
 doc/img/tutorial/capture_2.png                     |   Bin 0 -> 100097 bytes
 doc/img/tutorial/exes.png                          |   Bin 0 -> 102868 bytes
 doc/img/tutorial/rotating_1.png                    |   Bin 0 -> 148230 bytes
 doc/img/tutorial/rotating_2.png                    |   Bin 0 -> 156729 bytes
 doc/img/tutorial/treta.png                         |   Bin 0 -> 191814 bytes
 doc/img/view_menu.png                              |   Bin 0 -> 20196 bytes
 doc/img/visibility_menu.png                        |   Bin 0 -> 27290 bytes
 doc/index.html                                     |   178 +
 doc/installation_index.html                        |   202 +
 doc/tutorial_index.html                            |   245 +
 emperor/__init__.py                                |    13 +
 emperor/biplots.py                                 |   110 +
 emperor/filter.py                                  |    72 +
 emperor/format.py                                  |   723 +
 emperor/pycogent_backports/__init__.py             |    12 +
 emperor/pycogent_backports/procrustes.py           |   157 +
 emperor/qiime_backports/__init__.py                |    13 +
 emperor/qiime_backports/biplots.py                 |    55 +
 emperor/qiime_backports/filter.py                  |   120 +
 emperor/qiime_backports/format.py                  |    40 +
 emperor/qiime_backports/make_3d_plots.py           |    67 +
 emperor/qiime_backports/parse.py                   |   270 +
 emperor/qiime_backports/util.py                    |   423 +
 emperor/sort.py                                    |    87 +
 emperor/support_files/css/colorPicker.css          |    31 +
 emperor/support_files/css/d3.parcoords.css         |    40 +
 .../css/images/ui-bg_flat_0_aaaaaa_40x100.png      |   Bin 0 -> 180 bytes
 .../css/images/ui-bg_flat_75_ffffff_40x100.png     |   Bin 0 -> 178 bytes
 .../css/images/ui-bg_glass_55_fbf9ee_1x400.png     |   Bin 0 -> 120 bytes
 .../css/images/ui-bg_glass_65_ffffff_1x400.png     |   Bin 0 -> 105 bytes
 .../css/images/ui-bg_glass_75_dadada_1x400.png     |   Bin 0 -> 111 bytes
 .../css/images/ui-bg_glass_75_e6e6e6_1x400.png     |   Bin 0 -> 110 bytes
 .../css/images/ui-bg_glass_95_fef1ec_1x400.png     |   Bin 0 -> 119 bytes
 .../ui-bg_highlight-soft_75_cccccc_1x100.png       |   Bin 0 -> 101 bytes
 .../css/images/ui-icons_222222_256x240.png         |   Bin 0 -> 4369 bytes
 .../css/images/ui-icons_2e83ff_256x240.png         |   Bin 0 -> 4369 bytes
 .../css/images/ui-icons_454545_256x240.png         |   Bin 0 -> 4369 bytes
 .../css/images/ui-icons_888888_256x240.png         |   Bin 0 -> 4369 bytes
 .../css/images/ui-icons_cd0a0a_256x240.png         |   Bin 0 -> 4369 bytes
 .../support_files/css/jquery-ui-1.8.16.custom.css  |   568 +
 emperor/support_files/css/jquery-ui2.css           |   567 +
 emperor/support_files/css/spectrum.css             |   408 +
 emperor/support_files/emperor/css/emperor.css      |   284 +
 emperor/support_files/emperor/js/emperor.js        |  2430 ++
 emperor/support_files/img/emperor.png              |   Bin 0 -> 8747 bytes
 emperor/support_files/img/favicon.ico              |   Bin 0 -> 5558 bytes
 emperor/support_files/img/pause.png                |   Bin 0 -> 2919 bytes
 emperor/support_files/img/play.png                 |   Bin 0 -> 2946 bytes
 emperor/support_files/img/reset.png                |   Bin 0 -> 2965 bytes
 emperor/support_files/js/FileSaver.min.js          |     2 +
 emperor/support_files/js/THREEx.screenshot.js      |   130 +
 emperor/support_files/js/Three.js                  | 37637 +++++++++++++++++++
 emperor/support_files/js/d3.parcoords.js           |   568 +
 emperor/support_files/js/d3.v3.min.js              |     5 +
 emperor/support_files/js/jquery-1.7.1.min.js       |     4 +
 .../js/jquery-ui-1.8.17.custom.min.js              |   356 +
 emperor/support_files/js/jquery.colorPicker.js     |   328 +
 emperor/support_files/js/js/AudioObject.js         |   172 +
 emperor/support_files/js/js/Car.js                 |   364 +
 emperor/support_files/js/js/DAT.GUI.min.js         |    53 +
 emperor/support_files/js/js/Detector.js            |    58 +
 emperor/support_files/js/js/ImprovedNoise.js       |    71 +
 emperor/support_files/js/js/PRNG.js                |    11 +
 .../support_files/js/js/RequestAnimationFrame.js   |    22 +
 emperor/support_files/js/js/ShaderExtras.js        |  1779 +
 emperor/support_files/js/js/ShaderSkin.js          |   752 +
 emperor/support_files/js/js/ShaderTerrain.js       |   306 +
 emperor/support_files/js/js/SimplexNoise.js        |   316 +
 emperor/support_files/js/js/Sparks.js              |   832 +
 emperor/support_files/js/js/Stats.js               |     8 +
 emperor/support_files/js/js/Tween.js               |    13 +
 emperor/support_files/js/js/ctm/CTMLoader.js       |   821 +
 emperor/support_files/js/js/ctm/CTMWorker.js       |    19 +
 emperor/support_files/js/js/ctm/ctm.js             |   626 +
 .../support_files/js/js/ctm/license/OpenCTM.txt    |    20 +
 .../support_files/js/js/ctm/license/js-lzma.txt    |    19 +
 .../support_files/js/js/ctm/license/js-openctm.txt |    19 +
 emperor/support_files/js/js/ctm/lzma.js            |   510 +
 .../js/js/postprocessing/BloomPass.js              |   100 +
 .../js/js/postprocessing/DotScreenPass.js          |    52 +
 .../js/js/postprocessing/EffectComposer.js         |   136 +
 .../support_files/js/js/postprocessing/FilmPass.js |    51 +
 .../support_files/js/js/postprocessing/MaskPass.js |    86 +
 .../js/js/postprocessing/RenderPass.js             |    51 +
 .../support_files/js/js/postprocessing/SavePass.js |    52 +
 .../js/js/postprocessing/ShaderPass.js             |    51 +
 .../js/js/postprocessing/TexturePass.js            |    37 +
 emperor/support_files/js/specifications.txt        |     1 +
 emperor/support_files/js/spectrum.js               |  1566 +
 emperor/util.py                                    |   488 +
 images/emperor-logos.ai                            |  1197 +
 scripts/make_emperor.py                            |   627 +
 setup.py                                           |    61 +
 tests/all_tests.py                                 |   183 +
 .../scripts_test_data/make_emperor/Fasting_Map.txt |    11 +
 .../make_emperor/Fasting_Map_modified.txt          |    11 +
 tests/scripts_test_data/make_emperor/biplot.txt    |     4 +
 .../pcoa_unweighted_unifrac_rarefaction_110_0.txt  |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_1.txt  |    14 +
 .../scripts_test_data/make_emperor/makefolders.sh  |    10 +
 .../make_emperor/otu_table_L3.txt                  |     9 +
 .../make_emperor/unweighted_unifrac_pc.txt         |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_0.txt  |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_1.txt  |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_2.txt  |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_3.txt  |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_4.txt  |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_5.txt  |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_6.txt  |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_7.txt  |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_8.txt  |    14 +
 .../pcoa_unweighted_unifrac_rarefaction_110_9.txt  |    14 +
 tests/test_biplots.py                              |   243 +
 tests/test_filter.py                               |    91 +
 tests/test_format.py                               |  1516 +
 tests/test_pycogent_backports/test_procrustes.py   |   139 +
 tests/test_qiime_backports/test_biplots.py         |   108 +
 tests/test_qiime_backports/test_filter.py          |   130 +
 tests/test_qiime_backports/test_format.py          |    44 +
 tests/test_qiime_backports/test_make_3d_plots.py   |   104 +
 tests/test_qiime_backports/test_parse.py           |   390 +
 tests/test_qiime_backports/test_util.py            |   543 +
 tests/test_sort.py                                 |   235 +
 tests/test_util.py                                 |   501 +
 148 files changed, 62594 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dc2c75b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,42 @@
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+Icon?
+ehthumbs.db
+Thumbs.db
+
+# python files
+*.pyc
+*.pyo
+
+# packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+
+# installer logs
+pip-log.txt
+
+# vi swap and temp files
+*.swp
+*.swo
+*~
+
+# compiled source
+*.com
+*.class
+*.dll
+*.exe
+*.o
+*.so
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..04d8b40
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: python
+python:
+  - "2.7"
+install:
+  - pip install .
+script:
+  - python tests/all_tests.py --emperor_scripts_dir scripts
diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
index 0000000..0bd9d1d
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,90 @@
+Emperor 0.9.3
+=============
+
+* `Use gradient colors` checkbox is now found under the `Colors` tab.
+* Merge the `Options` and `View` tabs; additionally the global opacity slider and global scale slider were moved to their respective tabs.
+* `Use gradient colors` checkbox now uses the standard blue -> red color gradient
+* Add Emperor to the Python Package Index, now you can install Emperor running `pip install emperor`.
+* Remove dependency on QIIME and PyCogent.
+* Emperor now depends on qcli and Numpy.
+
+*Bug Fixes*
+
+* Add more meaningful error message for biplots when the contingency table passed included only one row.
+
+Emperor 0.9.2 (24 Oct 2013)
+===========================
+
+*Bug Fixes*
+
+* Fixes bug where files named `procrustes_results.txt` would not be ignored in a plot comparison.
+
+
+Emperor 0.9.1 (21 Oct 2013)
+===========================
+
+*New features*
+
+* Scientific notation is now taken into account in the GUI for scientific coloring.
+* GUI is usable in mobile devices that support WebGL.
+* User documentation: tutorial, installation instructions, GUI description, etc.
+* Ability to make plot comparisons (very useful for procrustes analysis plots).
+* The user can select the number of axes to be considered in the GUI and re-plot using lower axes; this is, for example: PC3 vs PC4 vs PC10.
+* In missing_custom_axes_values you can reference other column within the mapping file to place the samples without numeric values at different points in the gradient.
+* Parallel plots functionality.
+* Separated out some options to the View menu.
+* The "Colors" tab now has a selector, which allows to use the arrows to move between categories.
+* Default coloring scheme is discrete.
+* Add color pickers for the axes and axes labels.
+* To take a screenshot (PNG) of your current visualization you can press `ctrl+p`.
+* Export to SVG your visualization.
+* Emperor now relies on QIIME 1.7.0.
+* Added option `--number_of_segments` to control the quality of all spheres
+* Labels for biplots now have a color picker.
+* Add color pickers for connecting bars in coordinate comparison plots.
+* Add option to select a master set of coordinates when making a comparison plot.
+* Adds a feature to negate axes. With this feature you can negate the coordinates of each data point. As a result, the spheres and/or edges will be adjusted appropriately. 
+* Minor additions to the separator controller for the side bar.
+* As of 308629f550ff3e108903d3bcf1ce76ce85f4cb96 Emperor is now released under a BSD license.
+
+
+*Bug Fixes*
+
+* Fixes recenter camera not working.
+* Category names are sorted alphabetically.
+* Category names with non-alphanumeric characters are colored correctly now.
+* Biplots checkbox now accurately reflects status of biplot visiblity rather than opposite.
+* Comparison bars checkbox now accurately reflects status of the visiblity rather than opposite.
+* Scaling by percent explained now works with vectors and coordinate comparison plots.
+* Fixed bug where only the first bars in coordinate comparison plots could be hidden.
+* Improved documentation for saving and exporting images.
+* Emperor now fails graciously when WebGL is not enabled and gives you a few suggestions on how to get it to work.
+
+
+
+Emperor 0.9.0 (14 May 2013)
+===========================
+
+*New features*:
+
+* Intuitive and modern graphical user interface.
+* Simple workflow to modify the color of a sample/label from the user interface.
+* Color the labels for the samples by a category in the mapping file.
+* Scale the elements in the plot by the percentage explained from the user interface.
+* Notify the user when values will be removed from the input files.
+* Search for a sample name from the graphical user interface.
+* Show a selector in the plot when double-clicking a sample name.
+* Show and hide samples by a category in the mapping file.
+* Change the opacity of spheres/ellipses from the graphical user interface.
+* Change the size of a sphere from the graphical user interface.
+* Biplots can be created with a custom axis.
+* The color of the biplot spheres can now be changed from the user interface.
+* Extensive script usage testing
+* Addition of contextualized error messages.
+* Reduced output size for datasets with rich mapping files.
+
+*Performance improvements*:
+
+* Improved performance and responsiveness from the graphical user interface.
+* Superior graphics quality; elements are rendered in the graphics card not in the CPU.
+* Enhanced performance to create the output files.
diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 0000000..2603830
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,28 @@
+Emperor Installation Notes
+==========================
+
+Emperor is a python package that relies in [QIIME](http://www.qiime.org), [NumPy](http://www.numpy.org) and [PyCogent](http://www.pycogent.org). These packages must be installed prior running the `setup.py` script.
+
+To download Emperor, use [this link](https://github.com/qiime/emperor/archive/master.zip) or use git to get the latest version of the repository:
+
+    git clone git://github.com/qiime/emperor.git
+
+Installation
+============
+
+To perform a global installation of Emperor, execute the following command from a terminal session:
+
+    python setup.py install
+
+If you do not want to do a global installation, you will have to add the Emperor scripts and libraries to the `PATH` and `PYTHONPATH` environment variables. To add these variables to your `.bash_profile` issue the following terminal commands:
+
+``` bash
+echo "export PATH=$HOME/emperor_bin/:$PATH" >> ~/.bash_profile
+echo "export PYTHONPATH=$HOME/emperor_lib/:$PYTHONPATH" >> ~/.bash_profile
+python setup.py install --install-scripts=~/emperor_bin/ --install-purelib=~/emperor_lib/ --install-lib=~/emperor_lib/
+```
+
+To test for a correct installation, open a new terminal window and issue the following command to see the help of `make_emperor.py`:
+
+    make_emperor.py -h
+
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..e0a947e
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,223 @@
+License
+=======
+
+Emperor makes usage of all of the following copyrighted packages; their licenses have been centralized in this document.
+
+--------------------------------------------------------------------------------
+
+### THREE.js ([r58](https://github.com/mrdoob/three.js/tree/r58)) is released under the MIT License
+
+The MIT License
+
+Copyright (c) 2010-2013 three.js authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+--------------------------------------------------------------------------------
+
+### jQuery ([1.8](https://github.com/jquery/jquery/tree/1.8.0), [1.7.1](https://github.com/jquery/jquery/tree/1.7.1) & [1.6.2](https://github.com/jquery/jquery/tree/1.6.2)) is released under the MIT License
+
+Copyright 2013 jQuery Foundation and other contributors
+http://jquery.com/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+--------------------------------------------------------------------------------
+
+### Spectrum ([1.0.2](https://github.com/bgrins/spectrum/tree/1.0.2)) is released under the MIT License
+
+Copyright (c) 2013, Brian Grinstead, http://briangrinstead.com
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+--------------------------------------------------------------------------------
+
+### [ColorPicker](https://github.com/laktek/really-simple-color-picker/) is released under the MIT License
+
+Copyright (c) 2012 Lakshan Perera
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+--------------------------------------------------------------------------------
+
+### Parallel Coordinates [v0.1.7](http://syntagmatic.github.io/parallel-coordinates/)
+
+Copyright (c) 2012, Kai Chang
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* The name Kai Chang may not be used to endorse or promote products
+  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+### d3 [v3](https://github.com/mbostock/d3) is released under the
+
+Copyright (c) 2013, Michael Bostock
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* The name Michael Bostock may not be used to endorse or promote products
+  derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
+
+### THREEx.screenshot [v1](http://learningthreejs.com/data/THREEx/docs/THREEx.screenshot.html) 
+[repository](https://github.com/jeromeetienne/threex)
+
+Copyright (c) 2011 Jerome Etienne, http://jetienne.com
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+--------------------------------------------------------------------------------
+
+### FileSaver.js [283e78fd3c](https://github.com/eligrey/FileSaver.js/) 
+
+Copyright (c) 2011 [Eli Grey](http://eligrey.com).
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..441b5dd
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,15 @@
+include README.md
+include INSTALL.md
+include LICENSE.md
+include ChangeLog.md
+
+graft emperor
+graft scripts
+graft doc
+graft images
+graft tests
+
+global-exclude *.pyc
+global-exclude *.pyo
+global-exclude .git
+global-exclude *~
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5afb06a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+Emperor
+=======
+
+[![Build Status](https://travis-ci.org/qiime/emperor.png?branch=master)](https://travis-ci.org/qiime/emperor)
+
+Emperor is a next-generation tool for the analysis and visualization of large microbial ecology datasets; amongst many features Emperor provides a modern user interface that will rapidly adjust to your daily workflow.
+
+To start using Emperor, please refer to the [installation notes](INSTALL.md).
+
+## Usage examples
+
+The main interface to create Emperor visualizations is the `make_emperor.py` script, inputing a mapping file and a PCoA data file, will generate an Emperor graphical user interface to analyze and visualize your data.
+
+If you have a QIIME compliant mapping file and a PCoA file, try the following command from a terminal session:
+
+```bash
+make_emperor.py -i unweighted_unifrac_pc.txt -m mapping_file.txt
+```
+
+That command will create a new directory called emperor, there you will find a file called `index.html` open it with Google Chrome to start visualizing and interacting with your data.
+
+Similarly if you have a study expressed over a gradient, for example a study where you have multiple samples over time, you can use this metadata with your visualization using the `-a` option:
+
+```bash
+make_emperor.py -i unweighted_unifrac_pc_time.txt -m mapping_with_time.txt -a TIMEPOINT
+```
+
+Some build examples are bundled with every Emperor repository, you can begin exploring some sample data using **Google Chrome**:
+
+- To see an example of a simple PCoA plot, see this [link](http://emperor.colorado.edu/master/make_emperor/emperor_output/index.html).
+- To see an example of a Jackknifed plot, see this [link](http://emperor.colorado.edu/master/make_emperor/jackknifed_pcoa/index.html).
+- To see an example of a PCoA Biplot, see this [link](http://emperor.colorado.edu/master/make_emperor/biplot/index.html).
+- To see an example of a PCoA plot with connecting lines between samples, see this [link](http://emperor.colorado.edu/master/make_emperor/vectors/index.html).
+- To see an example of a PCoA plot with connecting lines between samples and an explicit axis, see this [link](http://emperor.colorado.edu/master/make_emperor/sorted_by_DOB/index.html).
diff --git a/doc/description_index.html b/doc/description_index.html
new file mode 100644
index 0000000..801de03
--- /dev/null
+++ b/doc/description_index.html
@@ -0,0 +1,209 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Emperor</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="">
+    <meta name="author" content="">
+    <link href="bootstrap/css/bootstrap.css" rel="stylesheet">
+    <style type="text/css">
+      body {
+        padding-top: 20px;
+        padding-bottom: 60px;
+      }
+
+      /* Custom container */
+      .container {
+        margin: 0 auto;
+        max-width: 1000px;
+      }
+      .container > hr {
+        margin: 60px 0;
+      }
+
+      /* Main marketing message and sign up button */
+      .jumbotron {
+        margin: 80px 0;
+        text-align: center;
+      }
+      .jumbotron h1 {
+        font-size: 100px;
+        line-height: 1;
+      }
+      .jumbotron .lead {
+        font-size: 24px;
+        line-height: 1.25;
+      }
+      .jumbotron .btn {
+        font-size: 21px;
+        padding: 14px 24px;
+      }
+
+      /* Supporting marketing content */
+      .marketing {
+        margin: 60px 0;
+      }
+      .marketing p + h4 {
+        margin-top: 28px;
+      }
+
+
+      /* Customize the navbar links to be fill the entire space of the .navbar */
+      .navbar .navbar-inner {
+        padding: 0;
+      }
+      .navbar .nav {
+        margin: 0;
+        display: table;
+        width: 100%;
+      }
+      .navbar .nav li {
+        display: table-cell;
+        width: 1%;
+        float: none;
+      }
+      .navbar .nav li a {
+        font-weight: bold;
+        text-align: center;
+        border-left: 1px solid rgba(255,255,255,.75);
+        border-right: 1px solid rgba(0,0,0,.1);
+      }
+      .navbar .nav li:first-child a {
+        border-left: 0;
+        border-radius: 3px 0 0 3px;
+      }
+      .navbar .nav li:last-child a {
+        border-right: 0;
+        border-radius: 0 3px 3px 0;
+      }
+    </style>
+    <link href="bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
+
+    <!-- GitHub ribbon -->
+    <a href="https://github.com/qiime/emperor"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png" alt="Fork me on GitHub"></a>
+    <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
+    <!--[if lt IE 9]>
+      <script src="bootstrap/js/html5shiv.js"></script>
+    <![endif]-->
+
+    <!-- Fav and touch icons -->
+    <link rel="shortcut icon" href="img/favicon.ico">
+  </head>
+
+  <body>
+
+    <div class="container">
+
+      <div class="masthead">
+        <a href="index.html"><p align="center"><img src="img/emperor_heading.png" alt="Emperor Logo"></p></a>
+        <div class="navbar">
+          <div class="navbar-inner">
+            <div class="container">
+              <ul class="nav">
+                <li><a href="index.html">Home</a></li>
+                <li class="active"><a href="description_index.html">Description</a></li>
+                <li><a href="tutorial_index.html">Tutorial</a></li>
+                <li><a href="installation_index.html">Installation</a></li>
+                <li><a href="https://github.com/qiime/emperor/issues">Support</a></li>
+              </ul>
+            </div>
+          </div>
+        </div><!-- /.navbar -->
+      </div>
+
+      <!-- Jumbotron -->
+      <div class="jumbotron">
+
+      </div>
+        <h2>Overview</h2>
+        <p class="lead" align="justify">
+          Emperor is a local full-browser enabled scatter plots visual tool. Its modern user interface, allows you to customize the appearance of your plot by controlling things like: opacity, sphere scaling, coloring, dimensions being presented among many others.
+          <br><br>
+          This document tries to go through an overview of the controls that can be found in the graphical user interface as well as some of the common use cases that Emperor allows you to work with.
+          <br><br>
+          Emperor's graphical user interface is mainly composed of a visualization canvas (left side) and a set of settings controllers (right side). Figure 1 highlights the (a) total number of points being displayed; (b) main display with the first three dimensions of this dataset; (c) tool-tabs to manipulate features of the plot; (d) visualization type selector to switch between parallel plots and three dimensional plots. Figure 2 presents the same dataset but using a parallel plot display.
+          <br><br>
+        </p>
+        <img src="img/description_1.png" alt="Emperor Figure Description">
+        <p class="strong" align="justify">
+          Figure 1. Emperor’s graphical user interface. (a) Sample counter; shows the total number of samples in the dataset and the number of samples that are currently visible. (b) Canvas; main plot scene where the data is displayed. (c) Tool tabs; controls that allow you to modify and interact with your data.
+        </p>
+        <img src="img/parallel_plots.png" alt="Emperor Figure Description">
+        <p class="strong" align="justify">
+          Figure 2. Parallel plot visualization of the dataset presented in Figure 1. Parallel plots are particularly useful to get an overview of the distribution of other dimensions in a given set of points.
+        </p>
+        <hr>
+        <h2>Description</h2>
+          <p class="lead" align="justify">
+            Emperor presents multiple tabs on the right sidebar, each of these tabs perform different tasks over the data presented in the visualization canvas.
+          </p>
+        <h3 class="muted">Key</h3>
+          <p class="lead" align="justify">
+            Shows a list of the sample identifiers that are contained in the current dataset and a box with the color that the sample is currently colored by on screen. Double-clicking each square, will make an indicator, white arrow, appear on screen.
+          </p>
+          <p align="center"><img src="img/key_menu.png" alt="Key Menu Example"></p>
+        <h3 class="muted">Color</h3>
+          <p class="lead" align="justify">
+            Shows a list of the categories listed under the mapping file column selected in the menu of this tab (for this example DOB). Each category has a color box where you can modify the current color of the samples that belong to this category.
+          </p>
+          <p align="center"><img src="img/colors_menu.png" alt="Coloring Menu Example"></p>
+        <h3 class="muted">Vsibility</h3>
+          <p class="lead" align="justify">
+            Shows a list of the categories listed under the mapping file column selected in the menu of this tab, for each of these categories a checkbox and a slider is presented. The checkbox will allow you to hide (unchecked) or show (checked) samples that belong to each category. The slider controls the opacity of the samples defined under this category.
+          </p>
+          <p align="center"><img src="img/visibility_menu.png" alt="Visibility Menu Example"></p>
+        <h3 class="muted">Scaling</h3>
+          <p class="lead" align="justify">
+            Similar to visibility but controls the scale of the spheres. The values range from 0.2 to 4, when the slider is located at a value of 2, the spheres will be twice as bigger as in the beginning. By default all sliders are set to a value of 1.
+          </p>
+          <p align="center"><img src="img/scaling_menu.png" alt="Scaling Menu Example"></p>
+        <h3 class="muted">Labels</h3>
+          <p class="lead" align="justify">
+            Similar to Visibility but controls the appearance of the labels. The checkbox at the top of this tab will add or remove the labels from the plot.
+          </p>
+          <p align="center"><img src="img/labels_menu.png" alt="Labels Menu Example"></p>
+        <h3 class="muted">Axes</h3>
+          <p class="lead" align="justify">
+            Although Emperor is only capable of displaying three dimensions at a time, the dimensions that are being displayed can be selected from this menu. To update the plot to reflect the latest changes, you’ll have to click the refresh button.
+          </p>
+          <p align="center"><img src="img/axes_menu.png" alt="Axes Menu Example"></p>
+        <h3 class="muted">View</h3>
+          <p class="lead" align="justify">
+            General features of a plot as presented by Emperor, are modified from this menu; by default Emperor will try to color samples along a continuous color gradient, to turn this option off, select “Use discrete colors”. The sphere opacity and sphere scale controls override the options in Scaling and Visibility tabs.
+          </p>
+          <p align="center"><img src="img/view_menu.png" alt="Options Menu Example"></p>
+        <h3 class="muted">Options</h3>
+          <p class="lead" align="justify">
+            The different options that will affect the overall plot as it is are listed under this tab. Scaling the coordinates by the percent explained, will re-scale the data by multiplying all the values in that dimension by the ratio explained. By default, Emperor will color the samples in a discrete way, each group is independent, but you can select “Use gradient colors” to create a gradient in the values of the columns. This is helpful when we are trying to display categories that  [...]
+          </p>
+          <p align="center"><img src="img/options_menu.png" alt="Options Menu Example"></p>
+      <hr>
+
+      <div class="footer">
+        <a href="https://github.com/qiime/emperor/network/members">
+          <p align="center">© The Emperor Development Team</p>
+        </a>
+      </div>
+
+    </div> <!-- /container -->
+
+    <!-- Le javascript
+    ================================================== -->
+    <!-- Placed at the end of the document so the pages load faster -->
+    <script src="bootstrap/js/jquery.js"></script>
+    <script src="bootstrap/js/bootstrap-transition.js"></script>
+    <script src="bootstrap/js/bootstrap-alert.js"></script>
+    <script src="bootstrap/js/bootstrap-modal.js"></script>
+    <script src="bootstrap/js/bootstrap-dropdown.js"></script>
+    <script src="bootstrap/js/bootstrap-scrollspy.js"></script>
+    <script src="bootstrap/js/bootstrap-tab.js"></script>
+    <script src="bootstrap/js/bootstrap-tooltip.js"></script>
+    <script src="bootstrap/js/bootstrap-popover.js"></script>
+    <script src="bootstrap/js/bootstrap-button.js"></script>
+    <script src="bootstrap/js/bootstrap-collapse.js"></script>
+    <script src="bootstrap/js/bootstrap-carousel.js"></script>
+    <script src="bootstrap/js/bootstrap-typeahead.js"></script>
+
+  </body>
+</html>
diff --git a/doc/img/axes_menu.png b/doc/img/axes_menu.png
new file mode 100644
index 0000000..d89dd00
Binary files /dev/null and b/doc/img/axes_menu.png differ
diff --git a/doc/img/colors_menu.png b/doc/img/colors_menu.png
new file mode 100644
index 0000000..dbcc4a9
Binary files /dev/null and b/doc/img/colors_menu.png differ
diff --git a/doc/img/description_1.png b/doc/img/description_1.png
new file mode 100644
index 0000000..63e5e2d
Binary files /dev/null and b/doc/img/description_1.png differ
diff --git a/doc/img/emperor_heading.png b/doc/img/emperor_heading.png
new file mode 100644
index 0000000..428def7
Binary files /dev/null and b/doc/img/emperor_heading.png differ
diff --git a/doc/img/favicon.ico b/doc/img/favicon.ico
new file mode 100644
index 0000000..f2a98a3
Binary files /dev/null and b/doc/img/favicon.ico differ
diff --git a/doc/img/key_menu.png b/doc/img/key_menu.png
new file mode 100644
index 0000000..dc998a8
Binary files /dev/null and b/doc/img/key_menu.png differ
diff --git a/doc/img/labels_menu.png b/doc/img/labels_menu.png
new file mode 100644
index 0000000..5711e46
Binary files /dev/null and b/doc/img/labels_menu.png differ
diff --git a/doc/img/options_menu.png b/doc/img/options_menu.png
new file mode 100644
index 0000000..e7d1176
Binary files /dev/null and b/doc/img/options_menu.png differ
diff --git a/doc/img/parallel_plots.png b/doc/img/parallel_plots.png
new file mode 100644
index 0000000..88d2ce2
Binary files /dev/null and b/doc/img/parallel_plots.png differ
diff --git a/doc/img/sample_1.png b/doc/img/sample_1.png
new file mode 100644
index 0000000..7b39997
Binary files /dev/null and b/doc/img/sample_1.png differ
diff --git a/doc/img/scaling_menu.png b/doc/img/scaling_menu.png
new file mode 100644
index 0000000..84c2bc4
Binary files /dev/null and b/doc/img/scaling_menu.png differ
diff --git a/doc/img/tutorial/1.png b/doc/img/tutorial/1.png
new file mode 100644
index 0000000..8931eae
Binary files /dev/null and b/doc/img/tutorial/1.png differ
diff --git a/doc/img/tutorial/10.png b/doc/img/tutorial/10.png
new file mode 100644
index 0000000..797caec
Binary files /dev/null and b/doc/img/tutorial/10.png differ
diff --git a/doc/img/tutorial/11.png b/doc/img/tutorial/11.png
new file mode 100644
index 0000000..508d265
Binary files /dev/null and b/doc/img/tutorial/11.png differ
diff --git a/doc/img/tutorial/2.png b/doc/img/tutorial/2.png
new file mode 100644
index 0000000..6cfe85b
Binary files /dev/null and b/doc/img/tutorial/2.png differ
diff --git a/doc/img/tutorial/6.png b/doc/img/tutorial/6.png
new file mode 100644
index 0000000..22c0212
Binary files /dev/null and b/doc/img/tutorial/6.png differ
diff --git a/doc/img/tutorial/capture_2.png b/doc/img/tutorial/capture_2.png
new file mode 100644
index 0000000..e24f895
Binary files /dev/null and b/doc/img/tutorial/capture_2.png differ
diff --git a/doc/img/tutorial/exes.png b/doc/img/tutorial/exes.png
new file mode 100644
index 0000000..e531c71
Binary files /dev/null and b/doc/img/tutorial/exes.png differ
diff --git a/doc/img/tutorial/rotating_1.png b/doc/img/tutorial/rotating_1.png
new file mode 100644
index 0000000..b78c5fe
Binary files /dev/null and b/doc/img/tutorial/rotating_1.png differ
diff --git a/doc/img/tutorial/rotating_2.png b/doc/img/tutorial/rotating_2.png
new file mode 100644
index 0000000..bdcc8ab
Binary files /dev/null and b/doc/img/tutorial/rotating_2.png differ
diff --git a/doc/img/tutorial/treta.png b/doc/img/tutorial/treta.png
new file mode 100644
index 0000000..f65b77e
Binary files /dev/null and b/doc/img/tutorial/treta.png differ
diff --git a/doc/img/view_menu.png b/doc/img/view_menu.png
new file mode 100644
index 0000000..8d1125d
Binary files /dev/null and b/doc/img/view_menu.png differ
diff --git a/doc/img/visibility_menu.png b/doc/img/visibility_menu.png
new file mode 100644
index 0000000..a2390ed
Binary files /dev/null and b/doc/img/visibility_menu.png differ
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000..47d027f
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Emperor</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="">
+    <meta name="author" content="">
+    <link href="bootstrap/css/bootstrap.css" rel="stylesheet">
+    <style type="text/css">
+      body {
+        padding-top: 20px;
+        padding-bottom: 60px;
+      }
+
+      /* Custom container */
+      .container {
+        margin: 0 auto;
+        max-width: 1000px;
+      }
+      .container > hr {
+        margin: 60px 0;
+      }
+
+      /* Main marketing message and sign up button */
+      .jumbotron {
+        margin: 80px 0;
+        text-align: center;
+      }
+      .jumbotron h1 {
+        font-size: 100px;
+        line-height: 1;
+      }
+      .jumbotron .lead {
+        font-size: 24px;
+        line-height: 1.25;
+      }
+      .jumbotron .btn {
+        font-size: 21px;
+        padding: 14px 24px;
+      }
+
+      /* Supporting marketing content */
+      .marketing {
+        margin: 60px 0;
+      }
+      .marketing p + h4 {
+        margin-top: 28px;
+      }
+
+
+      /* Customize the navbar links to be fill the entire space of the .navbar */
+      .navbar .navbar-inner {
+        padding: 0;
+      }
+      .navbar .nav {
+        margin: 0;
+        display: table;
+        width: 100%;
+      }
+      .navbar .nav li {
+        display: table-cell;
+        width: 1%;
+        float: none;
+      }
+      .navbar .nav li a {
+        font-weight: bold;
+        text-align: center;
+        border-left: 1px solid rgba(255,255,255,.75);
+        border-right: 1px solid rgba(0,0,0,.1);
+      }
+      .navbar .nav li:first-child a {
+        border-left: 0;
+        border-radius: 3px 0 0 3px;
+      }
+      .navbar .nav li:last-child a {
+        border-right: 0;
+        border-radius: 0 3px 3px 0;
+      }
+    </style>
+    <link href="bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
+
+    <!-- GitHub ribbon -->
+    <a href="https://github.com/qiime/emperor"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png" alt="Fork me on GitHub"></a>
+    <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
+    <!--[if lt IE 9]>
+      <script src="bootstrap/js/html5shiv.js"></script>
+    <![endif]-->
+
+    <!-- Fav and touch icons -->
+    <link rel="shortcut icon" href="img/favicon.ico">
+  </head>
+
+  <body>
+
+    <div class="container">
+
+      <div class="masthead">
+        <a href="index.html"><p align="center"><img src="img/emperor_heading.png" alt="Emperor Logo"></p></a>
+        <div class="navbar">
+          <div class="navbar-inner">
+            <div class="container">
+              <ul class="nav">
+                <li class="active"><a href="#">Home</a></li>
+                <li><a href="description_index.html">Description</a></li>
+                <li><a href="tutorial_index.html">Tutorial</a></li>
+                <li><a href="installation_index.html">Installation</a></li>
+                <li><a href="https://github.com/qiime/emperor/issues">Support</a></li>
+              </ul>
+            </div>
+          </div>
+        </div><!-- /.navbar -->
+      </div>
+
+      <!-- Jumbotron -->
+      <div class="jumbotron">
+<!--         <img src="img/emperor_heading.png" alt="Emperor">
+        <br><br><br><br> -->
+        <p class="lead" align="justify">Emperor is an interactive next generation tool for the analysis, visualization and understanding of high throughput microbial ecology datasets.
+        </p>
+        <img src="img/sample_1.png" alt="Emperor">
+        <p class="lead" align="justify">
+          Due to it’s tailor-made graphical user interface, delving into a new dataset to elucidate the patterns hidden in the data, has never been easier. Emperor brings a rich set of customizations and modifications that can be integrated into any <a href="http://qiime.org">QIIME</a> compliant dataset; with lightweight data files and hardware accelerated graphics, constitutes itself as the state of the art for analyzing N-dimensional data using principal coordinates analysis.
+        </p>
+        <br>
+        <strong><p class="text-error lead" align="justify">We've encountered that for large studies (more than five thousand samples) a 64-bit browser is needed. Even though we recommend Google Chrome, there are no official 64-bit binaries for OS X or Windows (Linux builds are 64-bit by default, see <a href="https://code.google.com/p/chromium/issues/detail?id=18323">issue #18323</a> and <a href="https://code.google.com/p/chromium/issues/detail?id=312958">issue #312958</a>), if you encoun [...]
+      </div>
+
+      <hr>
+
+      <!-- Example row of columns -->
+      <div class="row-fluid">
+        <div class="span4">
+          <h2>Biplots</h2>
+          <p>To visualize the taxa  driving the differences in a PCoA plot we can use biplots.</p>
+          <p><a class="btn" href="http://emperor.colorado.edu/master/make_emperor/biplot/index.html">Biplots Example »</a></p>
+        </div>
+        <div class="span4">
+          <h2>Jackknifing</h2>
+          <p>A jackknifed PCoA plot (with confidence intervals for each sample) helps to assure that our rarefaction selection is not the cause of the clustering patterns we are looking in beta diversity.</p>
+          <p><a class="btn" href="http://emperor.colorado.edu/master/make_emperor/jackknifed_pcoa/index.html">Jackknifing Example »</a></p>
+       </div>
+        <div class="span4">
+          <h2>Gradients</h2>
+          <p>Integrating gradient information to our coloring scheme, such as pH, time, or geographical location can be done in multiple ways with Emperor. One of them, is to add an explicit axis to the plot, from the mapping file.</p>
+          <p><a class="btn" href="http://emperor.colorado.edu/master/make_emperor/vectors/index.html">Gradient Example »</a></p>
+        </div>
+      </div>
+
+      <hr>
+
+      <div class="footer">
+        <a href="https://github.com/qiime/emperor/network/members">
+          <p align="center">© The Emperor Development Team</p>
+        </a>
+      </div>
+
+    </div> <!-- /container -->
+
+    <!-- Le javascript
+    ================================================== -->
+    <!-- Placed at the end of the document so the pages load faster -->
+    <script src="bootstrap/js/jquery.js"></script>
+    <script src="bootstrap/js/bootstrap-transition.js"></script>
+    <script src="bootstrap/js/bootstrap-alert.js"></script>
+    <script src="bootstrap/js/bootstrap-modal.js"></script>
+    <script src="bootstrap/js/bootstrap-dropdown.js"></script>
+    <script src="bootstrap/js/bootstrap-scrollspy.js"></script>
+    <script src="bootstrap/js/bootstrap-tab.js"></script>
+    <script src="bootstrap/js/bootstrap-tooltip.js"></script>
+    <script src="bootstrap/js/bootstrap-popover.js"></script>
+    <script src="bootstrap/js/bootstrap-button.js"></script>
+    <script src="bootstrap/js/bootstrap-collapse.js"></script>
+    <script src="bootstrap/js/bootstrap-carousel.js"></script>
+    <script src="bootstrap/js/bootstrap-typeahead.js"></script>
+
+  </body>
+</html>
diff --git a/doc/installation_index.html b/doc/installation_index.html
new file mode 100644
index 0000000..bfc931d
--- /dev/null
+++ b/doc/installation_index.html
@@ -0,0 +1,202 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Emperor</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="">
+    <meta name="author" content="">
+    <link href="bootstrap/css/bootstrap.css" rel="stylesheet">
+    <style type="text/css">
+      body {
+        padding-top: 20px;
+        padding-bottom: 60px;
+      }
+
+      /* Custom container */
+      .container {
+        margin: 0 auto;
+        max-width: 1000px;
+      }
+      .container > hr {
+        margin: 60px 0;
+      }
+
+      /* Main marketing message and sign up button */
+      .jumbotron {
+        margin: 80px 0;
+        text-align: center;
+      }
+      .jumbotron h1 {
+        font-size: 100px;
+        line-height: 1;
+      }
+      .jumbotron .lead {
+        font-size: 24px;
+        line-height: 1.25;
+      }
+      .jumbotron .btn {
+        font-size: 21px;
+        padding: 14px 24px;
+      }
+
+      /* Supporting marketing content */
+      .marketing {
+        margin: 60px 0;
+      }
+      .marketing p + h4 {
+        margin-top: 28px;
+      }
+
+
+      /* Customize the navbar links to be fill the entire space of the .navbar */
+      .navbar .navbar-inner {
+        padding: 0;
+      }
+      .navbar .nav {
+        margin: 0;
+        display: table;
+        width: 100%;
+      }
+      .navbar .nav li {
+        display: table-cell;
+        width: 1%;
+        float: none;
+      }
+      .navbar .nav li a {
+        font-weight: bold;
+        text-align: center;
+        border-left: 1px solid rgba(255,255,255,.75);
+        border-right: 1px solid rgba(0,0,0,.1);
+      }
+      .navbar .nav li:first-child a {
+        border-left: 0;
+        border-radius: 3px 0 0 3px;
+      }
+      .navbar .nav li:last-child a {
+        border-right: 0;
+        border-radius: 0 3px 3px 0;
+      }
+    </style>
+    <link href="bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
+
+    <!-- GitHub ribbon -->
+    <a href="https://github.com/qiime/emperor"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png" alt="Fork me on GitHub"></a>
+    <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
+    <!--[if lt IE 9]>
+      <script src="bootstrap/js/html5shiv.js"></script>
+    <![endif]-->
+
+    <!-- Fav and touch icons -->
+    <link rel="shortcut icon" href="img/favicon.ico">
+  </head>
+
+  <body>
+
+    <div class="container">
+
+      <div class="masthead">
+        <a href="index.html"><p align="center"><img src="img/emperor_heading.png" alt="Emperor Logo"></p></a>
+        <div class="navbar">
+          <div class="navbar-inner">
+            <div class="container">
+              <ul class="nav">
+                <li><a href="index.html">Home</a></li>
+                <li><a href="description_index.html">Description</a></li>
+                <li><a href="tutorial_index.html">Tutorial</a></li>
+                <li class="active"><a href="installation_index.html">Installation</a></li>
+                <li><a href="https://github.com/qiime/emperor/issues">Support</a></li>
+              </ul>
+            </div>
+          </div>
+        </div><!-- /.navbar -->
+      </div>
+
+      <!-- Jumbotron -->
+      <div class="jumbotron">
+      </div>
+        <h2>Downloading</h2>
+        <p class="lead" align="justify">
+          Emperor is a python package hosted in <a href="https://github.com/qiime/emperor/">GitHub</a> that relies on <a href="http://www.numpy.org/">NumPy</a> and <a href="ftp://thebeast.colorado.edu/pub/qcli-releases/qcli-0.1.0.tar.gz">qcli</a>. These packages must be installed prior running the <code>setup.py</code> script.
+        </p>
+        <p align="center">
+          <a class="btn btn-large btn-primary" href="https://github.com/qiime/emperor/archive/0.9.2.zip">
+            Download Stable Version
+          </a>
+          <a class="btn btn-large btn-warning" href="https://github.com/qiime/emperor/archive/master.zip">
+            Download Development Version
+          </a>
+        </p>
+        <br>
+        <p class="lead" align="justify">
+          To clone the git repository directly into your machine:
+          <br>
+          <pre><code>git clone git://github.com/qiime/emperor.git</code></pre>
+        </p>
+        <hr>
+        <h2>Installing</h2>
+        <p class="lead" align="justify">
+          To perform a global installation of Emperor, execute the following command from a terminal session:
+          <br>
+          <pre><code>python setup.py install</code></pre>
+        </p>
+        <p class="lead" align="justify">
+          <br>
+          If you do not want to do a global installation, you will have to add the Emperor scripts and libraries to the <code>PATH</code> and <code>PYTHONPATH</code> environment variables. To add these variables to your <code>.bash_profile</code> issue the following terminal commands:
+          <br>
+          <pre><code>echo "export PATH=$HOME/emperor_bin/:$PATH" >> ~/.bash_profile<br>echo "export PYTHONPATH=$HOME/emperor_lib/:$PYTHONPATH" >> ~/.bash_profile<br>python setup.py install --install-scripts=~/emperor_bin/ --install-purelib=~/emperor_lib/ --install-lib=~/emperor_lib/</code></pre>
+          <br>
+        </p>
+        <p class="lead" align="justify">
+          Alternatively Emperor can be installed from the Python Package Index (<a href="https://pypi.python.org/pypi">PyPi</a>)
+          <br>
+          <pre><code>pip install emperor</code></pre>
+        </p>
+        <br>
+        <h2>Verifying your installation</h2>
+        <p class="lead" align="justify">
+          To test for a correct installation, open a new terminal window and issue the following command to see the help of <code>make_emperor.py</code>:
+          <pre><code>make_emperor.py -h
+Usage: make_emperor.py [options] {-i/--input_coords INPUT_COORDS -m/--map_fp MAP_FP}
+
+[] indicates optional input (order unimportant)
+{} indicates required input (order unimportant)
+
+This script automates the creation  of three-dimensional PCoA plots to be visualized with Emperor using Google Chrome.
+
+Example usage: 
+Print help message and exit
+ make_emperor.py -h
+
+...
+</code></pre>
+        </p>
+      <hr>
+
+      <div class="footer">
+        <a href="https://github.com/qiime/emperor/network/members">
+          <p align="center">© The Emperor Development Team</p>
+        </a>
+      </div>
+
+    </div> <!-- /container -->
+
+    <!-- Le javascript
+    ================================================== -->
+    <!-- Placed at the end of the document so the pages load faster -->
+    <script src="bootstrap/js/jquery.js"></script>
+    <script src="bootstrap/js/bootstrap-transition.js"></script>
+    <script src="bootstrap/js/bootstrap-alert.js"></script>
+    <script src="bootstrap/js/bootstrap-modal.js"></script>
+    <script src="bootstrap/js/bootstrap-dropdown.js"></script>
+    <script src="bootstrap/js/bootstrap-scrollspy.js"></script>
+    <script src="bootstrap/js/bootstrap-tab.js"></script>
+    <script src="bootstrap/js/bootstrap-tooltip.js"></script>
+    <script src="bootstrap/js/bootstrap-popover.js"></script>
+    <script src="bootstrap/js/bootstrap-button.js"></script>
+    <script src="bootstrap/js/bootstrap-collapse.js"></script>
+    <script src="bootstrap/js/bootstrap-carousel.js"></script>
+    <script src="bootstrap/js/bootstrap-typeahead.js"></script>
+
+  </body>
+</html>
diff --git a/doc/tutorial_index.html b/doc/tutorial_index.html
new file mode 100644
index 0000000..2586827
--- /dev/null
+++ b/doc/tutorial_index.html
@@ -0,0 +1,245 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Emperor</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta name="description" content="">
+    <meta name="author" content="">
+    <link href="bootstrap/css/bootstrap.css" rel="stylesheet">
+    <style type="text/css">
+      body {
+        padding-top: 20px;
+        padding-bottom: 60px;
+      }
+
+      /* Custom container */
+      .container {
+        margin: 0 auto;
+        max-width: 1000px;
+      }
+      .container > hr {
+        margin: 60px 0;
+      }
+
+      /* Main marketing message and sign up button */
+      .jumbotron {
+        margin: 80px 0;
+        text-align: center;
+      }
+      .jumbotron h1 {
+        font-size: 100px;
+        line-height: 1;
+      }
+      .jumbotron .lead {
+        font-size: 24px;
+        line-height: 1.25;
+      }
+      .jumbotron .btn {
+        font-size: 21px;
+        padding: 14px 24px;
+      }
+
+      /* Supporting marketing content */
+      .marketing {
+        margin: 60px 0;
+      }
+      .marketing p + h4 {
+        margin-top: 28px;
+      }
+
+
+      /* Customize the navbar links to be fill the entire space of the .navbar */
+      .navbar .navbar-inner {
+        padding: 0;
+      }
+      .navbar .nav {
+        margin: 0;
+        display: table;
+        width: 100%;
+      }
+      .navbar .nav li {
+        display: table-cell;
+        width: 1%;
+        float: none;
+      }
+      .navbar .nav li a {
+        font-weight: bold;
+        text-align: center;
+        border-left: 1px solid rgba(255,255,255,.75);
+        border-right: 1px solid rgba(0,0,0,.1);
+      }
+      .navbar .nav li:first-child a {
+        border-left: 0;
+        border-radius: 3px 0 0 3px;
+      }
+      .navbar .nav li:last-child a {
+        border-right: 0;
+        border-radius: 0 3px 3px 0;
+      }
+    </style>
+    <link href="bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
+
+    <!-- GitHub ribbon -->
+    <a href="https://github.com/qiime/emperor"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png" alt="Fork me on GitHub"></a>
+    <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
+    <!--[if lt IE 9]>
+      <script src="bootstrap/js/html5shiv.js"></script>
+    <![endif]-->
+
+    <!-- Fav and touch icons -->
+    <link rel="shortcut icon" href="img/favicon.ico">
+  </head>
+
+  <body>
+
+    <div class="container">
+
+      <div class="masthead">
+        <a href="index.html"><p align="center"><img src="img/emperor_heading.png" alt="Emperor Logo"></p></a>
+        <div class="navbar">
+          <div class="navbar-inner">
+            <div class="container">
+              <ul class="nav">
+                <li><a href="index.html">Home</a></li>
+                <li><a href="description_index.html">Description</a></li>
+                <li class="active"><a href="tutorial_index.html">Tutorial</a></li>
+                <li><a href="installation_index.html">Installation</a></li>
+                <li><a href="https://github.com/qiime/emperor/issues">Support</a></li>
+              </ul>
+            </div>
+          </div>
+        </div><!-- /.navbar -->
+      </div>
+        <h2>Tutorial</h2>
+        <h3 class="muted">Introduction</h3>
+          <p class="lead" align="justify">
+            This tutorial attempts to cover most of the common use cases within Emperor.
+            <br>
+            The dataset that we will use to exemplify Emperor’s functionality, comes from the <a href="http://www.pnas.org/content/107/14/6477.long">Fierer et. al. 2010</a> article, where three individuals and their keyboards were sampled, Figure 1.
+            <br>
+            To get started, first download and unzip these <a href="files/files.zip">files</a>, here you will find a principal coordinates file, and a mapping file. These input files are in the standard format used in <a href="http://www.qiime.org">QIIME</a>. Before continuing with the tutorial make sure you have the latest version of <a href=" https://www.google.com/intl/en/chrome/browser/">Google Chrome</a> installed in your computer. The sections described in this tutorial are:
+            <br>
+            <ul >
+            <li><a class="lead" href="#section0">Command Line Interface</a></li>
+            <li><a class="lead" href="#section1">Graphical User Interface</a></li>
+            <li><a class="lead" href="#section2">Changing The Coloring</a></li>
+            <li><a class="lead" href="#section3">Changing The Opacity</a></li>
+            <li><a class="lead" href="#section4">Color Schemes</a></li>
+            <li><a class="lead" href="#section5">Exporting</a></li>
+            <li><a class="lead" href="#section6">Sharing</a></li>
+            </ul>
+          </p>
+        <hr>
+        <h3 class="muted" id="section0">Command Line Interface</h3>
+          <p class="lead" align="justify">
+            Emperor offers a convenient command line interface script (<code>make_emperor.py</code>), see the <a href="installation_index.html">installation notes</a> for further details. The minimum inputs that this interface requires are a coordinates file and a mapping file, it’s worth noting that the script interface offers many options that allow the user to refine the amount of data to visualize or even to change the way it will be presented, calling <code>make_emperor.py -h</code> [...]
+            <br>
+            Besides the coordinates and mapping file options, we will also use the <code>-b</code> option and the <code>-o</code> option. The <code>-b</code> option allows us to specify the categories that we want displayed in the GUI, it also allows us to combine columns from the mapping file into a single column. The <code>-o</code> option allows us to specify the name of the output directory.
+          </p>
+          <pre><code>make_emperor.py -i unweighted_unifrac_pc.txt -m mapping_file.txt -b "AGE,HAND,HAND_KEY,BODY_SITE,HOST_SUBJECT_ID,HAND,HAND&&HOST_SUBJECT_ID" -o keyboard</code></pre>
+          <br>
+          <p class="lead" align="justify">
+            Note: when <code>&&</code> is used with the <code>-b</code> option, two categories from the mapping file will be combined, in this case the values in <code>HAND</code> and the values in <code>HOST_SUBJECT_ID</code> will be concatenated.
+            <br>
+            A new folder named keyboard will be created, as this is the name we gave via <code>-o</code>, here you will find two files, these two files will be named the same for all datasets:
+          </p>
+          <p class="lead" align="justify">
+            <ul>
+              <li><code>index.html</code></li>
+              <li><code>emperor_required_resources/</code></li>
+            </ul>
+          </p>
+          <p class="lead" align="justify">
+            <br>
+            Opening <code>index.html</code> with Google Chrome will present the main GUI, to do so you can right click this file and make sure you open it with Google Chrome.
+          </p>
+        <hr>
+        <h3 class="muted" id="section1">Graphical User Interface</h3>
+          <p class="lead" align="justify">
+            Once you open the <code>index.html</code> file that was just generated, you should see something similar to the following figure:
+          </p>
+          <p align="center"><img src="img/tutorial/1.png" alt="GUI 1"></p>
+          <p class="lead" align="justify">
+            For a detailed explanation of the options available from the main interface refer to the <a href="description_index.html">description section</a>.
+            <br>
+          </p>
+        <h3 class="muted" id="section2">Changing The Coloring</h3>
+          <p class="lead" align="justify">
+            Spheres drawn in the main canvas are colored by the selected column in the mapping file under the Colors tab, in this example <code>AGE</code>. The available metadata categories are sorted in alphabetical order hence <code>AGE</code> is the first in this list. To change the coloring scheme click the Colors tab, then in the dropdown menu shown at the top click to select <code>HOST_SUBJECT_ID</code>. Now that samples are colored by this grouping, spheres will be colored in thre [...]
+          </p>
+          <p align="center"><img src="img/tutorial/2.png" alt="HOST_SUBJECT_ID Coloring"></p>
+          <p class="lead" align="justify">
+            Modifying the colors used to distinguish between subjects is achieved by clicking the frame by the side of each subject name. For example to change the color of the samples belonging to <code>232:M9</code>, click on the green square by the left side of this label (1), a color picker will appear on screen (2), select a new color by pointing and clicking at it with the mouse; to confirm this selection use the <code>choose</code> button (3).
+          </p>
+          <p align="center"><img src="img/tutorial/capture_2.png" alt="Three panels"></p>
+        <h3 class="muted" id="section3">Changing The Opacity</h3>
+          <p class="lead" align="justify">
+            Similarly to the coloring of the samples, opacity is an attribute that can be determined on a per-category basis. To illustrate an example where this is useful, let’s reduce the opacity in the samples without an <code>AGE</code> value i. e. the keys of the subject’s keyboard. Begin by navigating to the visibility tab and with the slider under the label that reads <code>NA</code>, move it until you reach almost half the opacity. The end result will be similar to the next figure.
+          </p>
+          <p align="center"><img src="img/tutorial/6.png" alt="Opacity reduction"></p>
+        <h3 class="muted" id="section4">Color Schemes</h3>
+          <p class="lead" align="justify">
+            Frequently you will find that metadata categories have multiple values that are easier to visualize with a color map, on the other side when the values have no sequential relationship, it’s better to use what’s called a discrete set of colors. In this dataset the <code>HAND&&HOST_SUBJECT_ID</code> category that we create with the <code>–b</code> option is a good example. Switch to the Colors tab and then in the dropdown menu to change the category from <code>HOST_SUBJECT_ID</ [...]
+          </p>
+          <p align="center"><img src="img/tutorial/treta.png" alt="To Discrete"></p>
+          <p class="lead" align="justify">
+            Pointing out the location of a sample within the plot, can be achieved by using the Key menu for example let’s indicate where is sample <code>M9Indr217.140998</code>. In the Key tab and search for the name either using the text entry box or navigating through the list of values. Once you’ve found it click in the colored square by it’s side and a white arrow will appear on the plot pointing at the place where the sample is located.
+          </p>
+          <p align="center"><img src="img/tutorial/10.png" alt="Filter"></p>
+          <p class="lead" align="justify">
+            Often times PCoA data will span over more than three dimensions, other dimensions can be visualized using the axes menu; just select the three coordinate axes that you want your data to be shaped by and hit the refresh button. In this instance let’s go to the Axes menu select P3 in the Axis 1 drop down menu, P4 in the Axis 2 drop down menu and P5 in the Axis 3 drop down menu, lastly click the refresh button this will redraw the visualization using the coordinate values in eac [...]
+          </p>
+          <p align="center"><img src="img/tutorial/exes.png" alt="Filter"></p>
+          <p class="lead" align="justify">
+            Dragging the plot with the left button of the mouse, will let you rotate the plot in the direction that the mouse is being moved. If the right click is pressed instead, the plot will be dragged with all it’s elements. To zoom-in or zoom-out in the plot use the scroll wheel. And note that at any time you can go back to the options tab and click recenter camera to go back to the original position.
+          </p>
+          <p align="center"><img src="img/tutorial/rotating_1.png" alt="Filter"></p>
+          <p align="center"><img src="img/tutorial/rotating_2.png" alt="Filter"></p>
+        <h3 class="muted" id="section5">Exporting</h3>
+          <p class="lead" align="justify">
+            Emperor generates publication quality figures in scalable vector graphics (SVG) format. The "Save as SVG" button is found under the "Options" tab, note that if you mark the "Create labels?" checkbox an additional file with legends describing the coloring scheme, a color to category name pair, will be downloaded to your computer.
+          </p>
+          <p align="center"><img src="img/tutorial/11.png" alt="Export Screenshot"></p>
+          <p class="lead" align="justify">
+            In this example setting the file name as "figure_example" renames the legends file as "figure_example_labels.svg" and the current view in the visualization canvas as "figure_example.svg".
+            <br><br>
+            Converting these figures into other formats can be achieved using external and freely available tools like <a href="http://inkscape.org">Inkscape</a>, which conveniently provides a command line utility to convert files into different formats. The following command line call will convert our SVG formatted file to a PDF formatted file.
+          </p>
+          <pre><code>inkscape -f figure_example.svg -A figure_example.pdf</code></pre>
+          <p class="lead" align="justify">
+            More information about the usage of this command line utility can be found <a href="http://inkscape.org/doc/inkscape-man.html">here</a>.
+          </p>
+        <h3 class="muted" id="section6">Sharing</h3>
+          <p class="lead" align="justify">
+            Simply compress the output directory (specified by the <code>-o</code> option in <code>make_emperor.py</code>) and share. The newly created <code>index.html</code> file contained within the output directory can then be visualized and manipulated through the Chrome web browser as long as it is accompanied by the <code>emperor_required_resources</code> folder.
+          </p>
+
+      <hr>
+      <div class="footer">
+        <a href="https://github.com/qiime/emperor/network/members">
+          <p align="center">© The Emperor Development Team</p>
+        </a>
+      </div>
+
+    </div> <!-- /container -->
+
+    <!-- Le javascript
+    ================================================== -->
+    <!-- Placed at the end of the document so the pages load faster -->
+    <script src="bootstrap/js/jquery.js"></script>
+    <script src="bootstrap/js/bootstrap-transition.js"></script>
+    <script src="bootstrap/js/bootstrap-alert.js"></script>
+    <script src="bootstrap/js/bootstrap-modal.js"></script>
+    <script src="bootstrap/js/bootstrap-dropdown.js"></script>
+    <script src="bootstrap/js/bootstrap-scrollspy.js"></script>
+    <script src="bootstrap/js/bootstrap-tab.js"></script>
+    <script src="bootstrap/js/bootstrap-tooltip.js"></script>
+    <script src="bootstrap/js/bootstrap-popover.js"></script>
+    <script src="bootstrap/js/bootstrap-button.js"></script>
+    <script src="bootstrap/js/bootstrap-collapse.js"></script>
+    <script src="bootstrap/js/bootstrap-carousel.js"></script>
+    <script src="bootstrap/js/bootstrap-typeahead.js"></script>
+
+  </body>
+</html>
diff --git a/emperor/__init__.py b/emperor/__init__.py
new file mode 100644
index 0000000..593e9b9
--- /dev/null
+++ b/emperor/__init__.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+# File created on 24 Jan 2013
+
+__author__ = "Emperor Development Team"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Antonio Gonzalez Pena", "Meg Pirrung", "Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Antonio Gonzalez Pena"
+__email__ = "antgonza at gmail.com"
+__status__ = "Release"
+
+__all__ = ['biplots', 'format', 'filter', 'sort', 'util']
diff --git a/emperor/biplots.py b/emperor/biplots.py
new file mode 100644
index 0000000..3dceb90
--- /dev/null
+++ b/emperor/biplots.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+# File created on 14 Apr 2013
+from __future__ import division
+
+__author__ = "Yoshiki Vazquez Baeza"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+from numpy import argsort, array
+
+from emperor.util import EmperorUnsupportedComputation
+from emperor.sort import sort_taxa_table_by_pcoa_coords
+from emperor.qiime_backports.biplots import (get_taxa_prevalence,
+    get_taxa_coords, make_biplot_scores_output)
+
+def extract_taxa_data(otu_coords, otu_table, lineages, prevalence, N=0):
+    """Extrac the N most prevalent elements according to a prevalence vector
+
+    Inputs:
+    otu_coords: coordinates where specific taxa is centered
+    otu_table: contingency table
+    lineages: taxonomic assignments for each row in the otu_table
+    prevalence: vector with prevalnce from 0 to 1 for each row of the otu_table
+    as returned from qiime.biplots.get_taxa_prevalence
+    N: number of most prevalent elements to retain, if zero is passed, will
+    reatain all available
+
+    Outputs:
+    otu_coords: N most prevalent coords
+    out_otu_table: N most prevalent rows of the otu_table
+    out_otu_lineages: N most prevalent taxonomic assignments for each row of the
+    otu_table
+    out_prevalence: first N values of prevalence
+
+    Based on qiime.biplots.remove_rare_taxa; though this function opperates on
+    generic data that's not in dict forma and returns the appropriate result.
+    """
+    # If less than zero or greater than length of taxa, N = fix to max
+    if N<=0 or N>len(prevalence):
+        N = len(prevalence)
+
+    # get the first N indices to keep from all of the taxa data
+    indices = argsort(prevalence)
+    indices = indices[::-1][:N]
+
+    # remove the indices that are not needed and return them individually
+    out_otu_coords = otu_coords[indices, :]
+    out_otu_table = otu_table[indices, :]
+    out_otu_lineages = [lineages[index] for index in indices]
+    out_prevalence = prevalence[indices]
+
+    return out_otu_coords, out_otu_table, out_otu_lineages, out_prevalence
+
+def preprocess_otu_table(otu_sample_ids, otu_table, lineages,
+                        coords_data, coords_headers, N=0):
+    """Preprocess the OTU table to to generate the required data for the biplots
+
+    Input:
+    otu_sample_ids: sample identifiers for the otu_table
+    otu_table: contingency table
+    lineages: taxonomic assignments for the OTUs in the otu_table
+    coords_data: principal coordinates data where the taxa will be mapped
+    N: number of most prevalent taxa to keep, by default will use all
+
+    Output:
+    otu_coords: coordinates representing the N most prevalent taxa in otu_table
+    otu_table: N most prevalent OTUs from the input otu_table
+    otu_lineages: taxonomic assignments corresponding to the N most prevalent
+    OTUs
+    otu_prevalence: vector with the prevalence scores of the N highest values
+    lines: coords where the N most prevalent taxa will be positioned in the
+    biplot
+    """
+
+    # return empty values if any of the taxa data is empty
+    if (otu_sample_ids == []) or (otu_table == array([])) or (lineages == []):
+        return [], [], [], [], ''
+
+    # this means there's only one or fewer rows in the contingency table
+    if len(otu_table) <= 1 or len(lineages) <= 1:
+        raise EmperorUnsupportedComputation, "Biplots are not supported for "+\
+            "contingency tables with one or fewer rows"
+
+    # if this element is a list take the first headers and coordinates
+    # both of these will be the master coordinates, i. e. where data is centered
+    if type(coords_data) == list and type(coords_headers) == list:
+        coords_data = coords_data[0]
+        coords_headers = coords_headers[0]
+
+    # re-arrange the otu table so it matches the order of the samples in the
+    # coordinates data & remove any sample that is not in the coordinates header
+    otu_sample_ids, otu_table = sort_taxa_table_by_pcoa_coords(coords_headers,
+        otu_table, otu_sample_ids)
+
+    # retrieve the prevalence and the coords prior the filtering
+    prevalence = get_taxa_prevalence(otu_table)
+    bi_plot_coords = get_taxa_coords(otu_table, coords_data)
+
+    o_otu_coords, o_otu_table, o_otu_lineages, o_prevalence =\
+        extract_taxa_data(bi_plot_coords, otu_table, lineages, prevalence, N)
+
+    lines = '\n'.join(make_biplot_scores_output({'coord': o_otu_coords,
+        'lineages': o_otu_lineages}))
+
+    return o_otu_coords, o_otu_table, o_otu_lineages, o_prevalence, lines
diff --git a/emperor/filter.py b/emperor/filter.py
new file mode 100644
index 0000000..96f766d
--- /dev/null
+++ b/emperor/filter.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# File created on 12 May 2013
+from __future__ import division
+
+__author__ = "Yoshiki Vazquez Baeza"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+from numpy import array
+
+def filter_samples_from_coords(headers, coords, valid_sample_ids, negate=False):
+    """Filter samples from a pair of headers and coordinates
+
+    headers: list of sample ids corresponding to the coordinates data
+    coords: numpy array of float values
+    valid_sample_ids: list of sample ids to keep
+    negate: False means keep the samples in valid_sample_ids, True means remove
+    the samples in valid_sample_ids
+
+    Notes:
+    Raises ValueError when all the samples are filtered out
+    """
+    out_coord_ids, out_coords = [], []
+
+    # define the strategy to take for each of the iterations
+    if negate:
+        def keep_sample(s):
+            return s not in valid_sample_ids
+    else:
+        def keep_sample(s):
+            return s in valid_sample_ids
+
+    for sample_id, coord in zip(headers, coords):
+        if keep_sample(sample_id):
+            out_coord_ids.append(sample_id)
+            out_coords.append(array(coord))
+
+    # do not allow empty sets as return values, raise an exception
+    if len(out_coord_ids) < 1:
+        raise ValueError, "All samples have been filtered out"
+
+    return out_coord_ids, array(out_coords)
+
+def keep_samples_from_pcoa_data(headers, coords, sample_ids):
+    """Controller function to filter coordinates data according to a list
+
+    headers: list of sample identifiers, if used for jackknifed data, this
+    should be a list of lists containing the sample identifiers
+    coords: 2-D numpy array with the float data in the coordinates, if used for
+    jackknifed data, coords should be a list of 2-D numpy arrays
+    sample_ids: list of sample ids that should be kept
+    """
+
+    # if the coords are a list then it means that the input jackknifed
+    if type(coords) == list:
+        out_coords, out_headers = [], []
+
+        for single_headers, single_coords in zip(headers, coords):
+            a, b = filter_samples_from_coords(single_headers, single_coords,
+                sample_ids)
+
+            out_headers.append(a)
+            out_coords.append(b)
+
+        return out_headers, out_coords
+    else:
+        return filter_samples_from_coords(headers, coords, sample_ids)
diff --git a/emperor/format.py b/emperor/format.py
new file mode 100755
index 0000000..0a097ac
--- /dev/null
+++ b/emperor/format.py
@@ -0,0 +1,723 @@
+#!/usr/bin/env python
+# File created on 24 Jan 2013
+from __future__ import division
+
+__author__ = "Antonio Gonzalez Pena"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Meg Pirrung", "Antonio Gonzalez Pena", "Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+
+from sys import argv
+from copy import deepcopy
+from os.path import abspath
+from datetime import datetime
+from StringIO import StringIO
+from socket import gethostname
+
+from numpy import max, min, abs, argsort, array
+
+from emperor.util import (keep_columns_from_mapping_file,
+    get_emperor_library_version)
+
+from emperor.qiime_backports.format import format_mapping_file
+from emperor.qiime_backports.parse import (mapping_file_to_dict,
+    parse_mapping_file)
+from emperor.qiime_backports.filter import (
+    filter_mapping_file_by_metadata_states,sample_ids_from_metadata_description)
+from emperor.qiime_backports import __version__ as qiime_backports_version
+
+class EmperorLogicError(ValueError):
+    """Exception raised when a requirement for the Emperor GUI is not met"""
+    pass
+
+def format_pcoa_to_js(header, coords, eigvals, pct_var, custom_axes=[],
+                    coords_low=None, coords_high=None, number_of_axes=10, 
+                    number_of_segments=8):
+    """Write the javascript necessary to represent a pcoa file in emperor
+
+    Inputs:
+    header: sample names for the pcoa file 1-D array
+    coords: coordinates of the PCoA file, 2-D array
+    eigvals: eigen-values of the PCoA file, 1-D array
+    pct_var: percentage of variation of the PCoA file, 1-D array
+    custom_axes: list of category names for the custom axes
+    coords_low: coordinates representing the lower edges of an ellipse
+    coords_high: coordinates representing the highere edges of an ellipse
+    number_of_axes: number of axes to be returned
+    number_of_segments: number of segments and rings for each sphere 
+
+    Output:
+    string: javascript representation of the PCoA data inputed, contains a list
+    of spheres, list of ellipses (if coords_low and coords_high are present) and
+    several setup variables.
+
+    Formats the output of qiime.parse.parse_coords_file into javascript variable
+    declarations.
+    """
+    js_pcoa_string = ''
+    
+    # validating that the number of coords in coords
+    if number_of_axes>len(coords[0]):
+        number_of_axes = len(coords[0])
+    # validating that all the axes are above 0.51%, this accounts for really
+    # small variations explained in some axes that end up being not practical
+    # the GUI has some problems when presenting those values on screen
+    valid_pcoalabels = len([i for i in pct_var if i>0.51])
+    if number_of_axes>valid_pcoalabels:
+        number_of_axes = valid_pcoalabels
+    if number_of_axes<3:
+        raise EmperorLogicError, "Due to the variation explained, Emperor "+\
+            "could not plot at least 3 axes, check the input files to ensure"+\
+            " that the percent explained is greater than 0.5 in at least "+\
+            "three axes."
+
+    # ranges for the PCoA space
+    max_x = max(coords[:,0:1])
+    max_y = max(coords[:,1:2])
+    max_z = max(coords[:,2:3])
+    min_x = min(coords[:,0:1])
+    min_y = min(coords[:,1:2])
+    min_z = min(coords[:,2:3])
+    maximum = max(abs(coords[:,:number_of_axes]))
+    pcoalabels = pct_var[:number_of_axes]
+    
+    radius = (max_x-min_x)*.012
+
+    # write the values for all the spheres
+    js_pcoa_string += '\nvar g_spherePositions = new Array();\n'
+    for point, coord in zip(header, coords):
+        all_coords = ', '.join(["'P%d': %f" % (i+1,coord[i]) for i in range(number_of_axes)])
+        js_pcoa_string += ("g_spherePositions['%s'] = { 'name': '%s', 'color': "
+            "0, 'x': %f, 'y': %f, 'z': %f, %s };\n" % (point, point, coord[0],
+            coord[1],coord[2], all_coords))
+
+    # write the values for all the ellipses
+    js_pcoa_string += '\nvar g_ellipsesDimensions = new Array();\n'
+    if coords_low != None and coords_high != None:
+        for s_header, s_coord, s_low, s_high in zip(header, coords, coords_low,
+            coords_high):
+            delta = abs(s_high-s_low)
+            all_coords = ', '.join(["'P%d': %f" % (i+1,s_coord[i]) for i in range(number_of_axes)])
+            js_pcoa_string += ("g_ellipsesDimensions['%s'] = { 'name': '%s', "
+                "'color': 0, 'width': %f, 'height': %f, 'length': %f , 'x': %f,"
+                " 'y': %f, 'z': %f, %s }\n" % (s_header, s_header,delta[0], delta[1],
+                delta[2], s_coord[0], s_coord[1], s_coord[2], all_coords))
+    
+    js_pcoa_string += 'var g_segments = %d, g_rings = %d, g_radius = %f;\n' % (number_of_segments, 
+        number_of_segments, radius)
+    js_pcoa_string += 'var g_xAxisLength = %f;\n' % (abs(max_x)+abs(min_x))
+    js_pcoa_string += 'var g_yAxisLength = %f;\n' % (abs(max_y)+abs(min_y))
+    js_pcoa_string += 'var g_zAxisLength = %f;\n' % (abs(max_z)+abs(min_z))
+    js_pcoa_string += 'var g_xMaximumValue = %f;\n' % (max_x)
+    js_pcoa_string += 'var g_yMaximumValue = %f;\n' % (max_y)
+    js_pcoa_string += 'var g_zMaximumValue = %f;\n' % (max_z)
+    js_pcoa_string += 'var g_xMinimumValue = %f;\n' % (min_x)
+    js_pcoa_string += 'var g_yMinimumValue = %f;\n' % (min_y)
+    js_pcoa_string += 'var g_zMinimumValue = %f;\n' % (min_z)
+    js_pcoa_string += 'var g_maximum = %f;\n' % maximum
+
+    offset = 0
+
+    # create three vars, pc1, pc2 and pc3 if no custom_axes are passed, then use
+    # the values of the percent explained by the PCoA; if custom_axes are passed
+    # use as many as you can (since customs axes can be either [0, 1, 2, 3])
+    for i in range(0, 3):
+        try:
+            js_pcoa_string += 'var g_pc%dLabel = \"%s\";\n' % (i+1,
+                custom_axes[i])
+            offset+=1 # offset will help us retrieve the correct pcoalabels val
+        except:
+            # if there are custom axes then subtract the number of custom axes
+            js_pcoa_string += 'var g_pc%dLabel = \"PC%d (%.0f %%)\";\n' %\
+                (i+1, i+1-offset, pcoalabels[i-offset])
+    js_pcoa_string += 'var g_number_of_custom_axes = %d;\n' % offset
+    
+    js_pcts = []
+    js_pcts_round = []
+    if custom_axes == None: custom_axes = []
+    for element in custom_axes + list(pct_var[:number_of_axes]):
+        try:
+            # scale the percent so it's a number from 0 to 1
+            js_pcts.append('%f' % (float(element)/100))
+            js_pcts_round.append('%d' % (round(element)))
+        except ValueError:
+            js_pcts.append('%f' % (float(pct_var[0]/100)))
+            js_pcts_round.append('%d' % (round(pct_var[0])))
+    js_pcoa_string += 'var g_fractionExplained = [%s];\n' % ', '.join(js_pcts)
+    js_pcoa_string += 'var g_fractionExplainedRounded = [%s];\n' % ', '.join(js_pcts_round)
+    
+    return js_pcoa_string
+
+def format_mapping_file_to_js(mapping_file_data, mapping_file_headers, columns):
+    """Write a javascript representation of the mapping file
+
+    Inputs:
+    mapping_file_data: contents of the mapping file
+    mapping_file_headers: headers of the mapping file
+    columns: valid columns to use, usually a subset of mapping_file_headers
+
+    Outputs:
+    string: javascript representation of the mapping file
+    """
+    js_mapping_file_string = ''
+
+    mapping_file_dict = mapping_file_to_dict(mapping_file_data,
+        mapping_file_headers)
+
+    map_values = []
+    for k,v in mapping_file_dict.items():
+        if 'SampleID' in columns:
+            vals = ["'%s'" % k] + ["'%s'" % v[col]\
+                for col in mapping_file_headers[1:]]
+        else:
+            vals = ["'%s'" % v[col] for col in mapping_file_headers[1:]]
+        map_values.append("'%s': [%s]" % (k, ','.join(vals)))
+
+    if 'SampleID' not in columns:
+        mapping_file_headers = mapping_file_headers[1:]
+
+    # format the mapping file as javascript objects
+    js_mapping_file_string += 'var g_mappingFileHeaders = [%s];\n' % ','.join(
+        ["'%s'" % col for col in mapping_file_headers])
+    js_mapping_file_string += 'var g_mappingFileData = { %s };\n' % ','.join(
+        map_values)
+
+    return js_mapping_file_string
+
+def format_taxa_to_js(otu_coords, lineages, prevalence, min_taxon_radius=0.5,
+                    max_taxon_radius=5, radius=1.0):
+    """Write a string representing the taxa in a PCoA plot as javascript
+    
+    Inputs:
+    otu_coords: numpy array where the taxa is positioned
+    lineages: label for each of these lineages
+    prevalence: score of prevalence for each of the taxa that is drawn
+
+    *These parameters should work more as constants and once we find out that
+    there's a value that is too big to be presented, the proper checks should
+    be put into place. Currently we haven't found such cases in any study*
+    min_taxon_radius: minimum value for the radius of the spheres on the plot
+    max_taxon_radious: maximum value for the radius of the spheres on the plot
+    radius: default value size
+
+    Outputs:
+    js_biplots_string: javascript string where the taxa information is written
+    to create the spheres representing each of these, will return only the
+    variable declaration if the inputs are empty.
+    """
+    js_biplots_string = []
+    js_biplots_string.append('\nvar g_taxaPositions = new Array();\n')
+
+    # if we have prevalence scores, calculate the taxa radii values
+    if len(prevalence):
+        taxa_radii = radius*(min_taxon_radius+(max_taxon_radius-
+            min_taxon_radius)*prevalence)
+    else:
+        taxa_radii = []
+
+    index = 0
+
+    # write the data in the form of a dictionary
+    for taxa_label, taxa_coord, t_radius in zip(lineages,otu_coords,taxa_radii):
+        js_biplots_string.append(("g_taxaPositions['%d'] = { 'lineage': '%s', "
+            "'x': %f, 'y': %f, 'z': %f, 'radius': %f};\n") % (index,
+            taxa_label, taxa_coord[0], taxa_coord[1], taxa_coord[2], t_radius))
+        index += 1
+    js_biplots_string.append('\n')
+    # join the array of strings as a single string
+    return ''.join(js_biplots_string)
+
+def format_vectors_to_js(mapping_file_data, mapping_file_headers, coords_data,
+                        coords_headers, connected_by_header,
+                        sorted_by_header=None):
+    """Write a string representing the vectors in a PCoA plot as javascript
+
+    Inputs:
+    mapping_file_data: contents of the mapping file
+    mapping_file_headers: headers of the mapping file
+    coords_data: coordinates of the PCoA plot in a numpy 2-D array or a list of
+    numpy 2-D arrays for jackknifed input
+    coords_headers: headers of the coords in the PCoA plot or a list of lists
+    with the headers for jackknifed input
+    connected_by_header: header of the mapping file that represents how the
+    lines will be connected
+    sorted_by_header: numeric-only header name to sort the samples in the
+    vectors
+
+    Output:
+    js_vectors_string: string that represents the vectors in the shape of a
+    javascript object
+
+    Notes:
+    If using jackknifed input, the coordinates and headers that will be used are
+    the ones belonging to the master coords i. e. the first element.
+    """
+
+    js_vectors_string = []
+    js_vectors_string.append('\nvar g_vectorPositions = new Array();\n')
+
+    if connected_by_header != None:
+        # check if we are processing jackknifed input, if so just get the master
+        if type(coords_data) == list:
+            coords_data = coords_data[0]
+            coords_headers = coords_headers[0]
+
+        columns_to_keep = ['SampleID', connected_by_header]
+
+        # do not ad None if sorted_by_header is None or empty
+        if sorted_by_header:
+            columns_to_keep.append(sorted_by_header)
+
+        # reduce the amount of data by keeping the required fields only
+        mapping_file_data, mapping_file_headers =\
+            keep_columns_from_mapping_file(mapping_file_data,
+            mapping_file_headers, columns_to_keep)
+
+        # format the mapping file to use this with the filtering function
+        mf_string = format_mapping_file(mapping_file_headers, mapping_file_data)
+
+        index = mapping_file_headers.index(connected_by_header)
+        connected_by = list(set([line[index] for line in mapping_file_data]))
+
+        for category in connected_by:
+            # convert to StringIO to for each iteration; else the object
+            # won't be usable after the first iteration & you'll get an error
+            sample_ids = sample_ids_from_metadata_description(
+                StringIO(mf_string),'%s:%s' % (connected_by_header,category))
+
+            # if there is a sorting header, sort the coords using these values
+            if sorted_by_header:
+                sorting_index = mapping_file_headers.index(sorted_by_header)
+                to_sort = [line for line in mapping_file_data if line[0] in\
+                    sample_ids]
+
+                # get the sorted sample ids from the sorted-reduced mapping file
+                sample_ids = zip(*sorted(to_sort,
+                    key=lambda x: float(x[sorting_index])))[0]
+
+            # each category value is a new vector
+            js_vectors_string.append("g_vectorPositions['%s'] = new Array();\n"
+                % (category))
+
+            for s in sample_ids:
+                index = coords_headers.index(s)
+
+                # print the first three elements of each coord for each sample
+                js_vectors_string.append("g_vectorPositions['%s']['%s'] = %s;\n"
+                    % (category, s, coords_data[index, :3].tolist()))
+
+    return ''.join(js_vectors_string)
+
+def format_comparison_bars_to_js(coords_data, coords_headers, clones,
+                                is_serial_comparison=True):
+    """Format coordinates data to create a comparison plot
+
+    Inputs:
+    coords_data: numpy array with the replicated coordinates
+    cooreds_headers: list with the headers for each of replicated coordinates
+    clones: number of replicates in the coords_data and coords_headers
+    is_serial_comparison: whether the samples will be connected one after the
+    other (True) or all will originate in the first set of coordinates.
+
+    Outputs:
+    Javascript object that contains the data for the comparison plot
+
+    Raises:
+    AssertionError if the coords_data and coords_headers don't have the same
+    length.
+    AssertionError if the number of clones doesn't concord with the samples
+    being presented.
+
+    Unless the value of clones is > 0 this function will return an empty
+    javascript object initialization.
+    """
+
+    js_comparison_string = []
+    js_comparison_string.append('\nvar g_comparisonPositions = new Array();\n')
+
+    if is_serial_comparison:
+        js_comparison_string.append('var g_isSerialComparisonPlot = true;\n')
+    else:
+        js_comparison_string.append('var g_isSerialComparisonPlot = false;\n')
+
+    if clones:
+        headers_length = len(coords_headers)
+
+        # assert some sanity checks
+        assert headers_length == len(coords_data), "The coords data and"+\
+            "the coords headers must have the same length"
+        assert headers_length%clones == 0, "There has to be an exact "+\
+            "number of clones of the data"
+
+        # get the indices that the sample names get sorted by, this will group
+        # all the samples with the same prefix together, and since the suffixes
+        # are numeric, the samples will be one after the other i. e. sample_0,
+        # sample_1, sample_2 and other_0, other_1, other_2 and so on. With these
+        # indices sort the coordinates and then the headers themselves, though
+        # convert to a numpy array first & back to a list to avoid sorting again
+        indices = argsort(coords_headers)
+        coords_data = coords_data[indices, :]
+        coords_headers = array(coords_headers)[indices, :].tolist()
+
+        # in steps of the number of clones iterate through the headers and the
+        # coords to create the javascript object with the coordinates
+        for index in xrange(0, headers_length, clones):
+            # 1st object must have _0 as a suffix, trim it reveal the sample id
+            assert coords_headers[index].endswith('_0'), "There's an internal"+\
+                " inconsistency with the sample ids"
+            sample_id = coords_headers[index][:-2]
+
+            # convert all elements in the numpy array into a string before
+            # formatting the elements into the javascript dictionary object
+            js_comparison_string.append("g_comparisonPositions['%s'] = [%s];\n"%
+                (sample_id, str(', '.join(map(str,
+                coords_data[index:(index+clones), 0:3].tolist())))))
+    return ''.join(js_comparison_string)
+
+
+def format_emperor_html_footer_string(has_biplots=False, has_ellipses=False,
+                                    has_vectors=False, has_edges=False):
+    """Create an HTML footer according to the things being presented in the plot
+
+    has_biplots: whether the plot has biplots or not
+    has_ellipses: whether the plot has ellipses or not
+    has_vectors: whether the plot has vectors or not
+    has_edges: whether the plot has edges between samples (comparison plot)
+
+
+    This function will remove unnecessary GUI elements from index.html to avoid
+    confusions i. e. showing an ellipse opacity slider when there are no
+    ellipses in the plot.
+    """
+    optional_strings = []
+
+    # the order of these statements matter, see _EMPEROR_FOOTER_HTML_STRING
+    # we use python's built-in ternary operator to add or not a string
+    optional_strings.append(_BIPLOT_SPHERES_COLOR_SELECTOR if has_biplots else
+        '')
+    optional_strings.append(_BIPLOT_VISIBILITY_SELECTOR if has_biplots else '')
+    optional_strings.append(_TAXA_LABELS_SELECTOR if has_biplots else '')
+    optional_strings.append(_TAXA_LABELS_COLOR_SELECTOR if has_biplots else '')
+    optional_strings.append(_EDGES_COLOR_SELECTOR if has_edges else '')
+    optional_strings.append(_ELLIPSE_OPACITY_SLIDER if has_ellipses else '')
+    optional_strings.append(_VECTORS_OPACITY_SLIDER if has_vectors else '')
+    optional_strings.append(_EDGES_VISIBILITY_SELECTOR if has_edges else '')
+
+    return _EMPEROR_FOOTER_HTML_STRING % tuple(optional_strings)
+
+def format_emperor_autograph(metadata_fp, coords_fp, language='HTML'):
+    """Create a signature with some meta-data of the Emperor package
+
+    language: language to which it will be formatted as a multi-line comment
+
+    """
+
+    # supported open and closing of multi-line comments for different languages
+    _languages = {'HTML':('<!--', '-->'), 'Python':('"""', '"""'), 'C':('/*',
+        '*/'), 'Bash':('<<COMMENT', 'COMMENT')}
+
+    assert language in _languages.keys(), '%s is not a supported language' %\
+        language
+
+    autograph = []
+    autograph.append(_languages[language][0])
+    autograph.append("*Summary of Emperor's Information*")
+
+    # add the day and time at which the command was called
+    autograph.append(datetime.now().strftime('Command executed on %B %d, %Y at'
+        ' %H:%M:%S'))
+
+    # add library version and SHA-1 if available
+    autograph.append('Emperor Version: %s' %  get_emperor_library_version())
+    autograph.append('QIIME Version: %s' % qiime_backports_version)
+    autograph.append('HostName: %s' % gethostname())
+
+    # full path to input files
+    autograph.append('Metadata: %s' % abspath(metadata_fp))
+    autograph.append('Coordinates: %s' % abspath(coords_fp))
+
+    if any([True for element in argv if 'make_emperor.py' in element]):
+        autograph.append('Command: %s' % ' '.join(argv))
+    else:
+        autograph.append('Command: Cannot find direct call to make_emperor.py')
+    autograph.append(_languages[language][1])
+
+    return '%s' % '\n'.join(autograph)
+
+
+EMPEROR_HEADER_HTML_STRING =\
+"""<!doctype html>
+<html lang="en">
+
+<head>
+    <title>Emperor</title>
+    <meta charset="utf-8">
+    <link rel="shortcut icon" href="emperor_required_resources/img/favicon.ico" />
+    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+    <link rel="stylesheet" type="text/css" href="emperor_required_resources/emperor/css/emperor.css">
+    <link rel="stylesheet" type="text/css" href="emperor_required_resources/css/jquery-ui2.css">
+    <link rel="stylesheet" type="text/css" href="emperor_required_resources/css/colorPicker.css">
+    <link rel="stylesheet" type="text/css" href="emperor_required_resources/css/spectrum.css">
+    <link rel="stylesheet" type="text/css" href="emperor_required_resources/css/d3.parcoords.css">
+    <table id="logotable" style="vertical-align:middle;text-align:center;height:100%;width:100%;margin:0;padding:0;border:0;">
+        <tr><td><img src="emperor_required_resources/img/emperor.png" alt="Emperor" id="logo"/></td></tr>
+    </table>
+    <script type="text/javascript" src="emperor_required_resources/js/d3.v3.min.js"></script>
+    <script type="text/javascript" src="emperor_required_resources/js/d3.parcoords.js"></script>
+    <script type="text/javascript" src="emperor_required_resources/js/jquery-1.7.1.min.js"></script>
+    <script type="text/javascript" src="emperor_required_resources/js/jquery-ui-1.8.17.custom.min.js"></script>
+    <script src="emperor_required_resources/js/jquery.colorPicker.js"></script>
+    <script src="emperor_required_resources/js/spectrum.js"></script>
+
+    <script src="emperor_required_resources/js/Three.js"></script>
+    <script src="emperor_required_resources/js/js/Detector.js"></script>
+    <script src="emperor_required_resources/js/js/RequestAnimationFrame.js"></script>
+    <script src="emperor_required_resources/emperor/js/emperor.js"></script>
+    <script type="text/javascript" src="emperor_required_resources/js/THREEx.screenshot.js"></script>
+    <script type="text/javascript" src="emperor_required_resources/js/FileSaver.min.js"></script>
+    
+    <script type="text/javascript">
+    
+"""
+
+_ELLIPSE_OPACITY_SLIDER = """
+            <br>
+            <label for="ellipseopacity" class="text">Ellipse Opacity</label>
+            <label id="ellipseopacity" class="slidervalue"></label>
+            <div id="eopacityslider" class="slider-range-max"></div>"""
+
+_VECTORS_OPACITY_SLIDER = """
+            <br>
+            <label for="vectorsopacity" class="text">Vectors Opacity</label>
+            <label id="vectorsopacity" class="slidervalue"></label>
+            <div id="vopacityslider" class="slider-range-max"></div>"""
+
+_TAXA_LABELS_SELECTOR = """
+            <form name="biplotoptions">
+            <input type="checkbox" onClick="toggleTaxaLabels()">Biplots Label Visibility</input>
+            </form>"""
+
+_TAXA_LABELS_COLOR_SELECTOR = """
+            <tr><td><div id="taxalabelcolor" class="colorbox"></div></td><td><label>Taxa Label Color</label></td></tr>
+"""
+
+_BIPLOT_VISIBILITY_SELECTOR = """
+            <br>
+            <form name="biplotsvisibility">
+            <input type="checkbox" onClick="toggleBiplotVisibility()" checked>Biplots Visibility</input>
+            </form>
+            <br>"""
+
+_BIPLOT_SPHERES_COLOR_SELECTOR ="""
+            <br>
+            <table>
+                <tr><td><div id="taxaspherescolor" class="colorbox" name="taxaspherescolor"></div></td><td title="taxacolor">Taxa Spheres Color</td></tr>
+            </table>
+            <br>"""
+
+_EDGES_VISIBILITY_SELECTOR = """
+            <br>
+            <form name="edgesvisibility">
+            <input type="checkbox" onClick="toggleEdgesVisibility()" checked>Edges Visibility</input>
+            </form>
+            <br>"""
+
+_EDGES_COLOR_SELECTOR = """
+            <tr><td><div id="edgecolorselector_a" class="colorbox" name="edgecolorselector_a"></div></td><td title="edgecolor_a">Edge Color Selector A</td></tr>
+            <tr><td><div id="edgecolorselector_b" class="colorbox" name="edgecolorselector_b"></div></td><td title="edgecolor_b">Edge Color Selector B</td></tr>
+"""
+
+_EMPEROR_FOOTER_HTML_STRING ="""document.getElementById("logo").style.display = 'none';
+document.getElementById("logotable").style.display = 'none';
+
+ </script>
+</head>
+
+<body>
+
+<div id="overlay">
+    <div>
+    <img src="emperor_required_resources/img/emperor.png" alt="Emperor" id="smalllogo"/>
+        <h1>WebGL is not enabled!</h1>
+        <p>Emperor's visualization framework is WebGL based, it seems that your system doesn't have this resource available. Here is what you can do:</p>
+        <p id="explanation"><strong>Chrome:</strong> Type "chrome://flags/" into the address bar, then search for "Disable WebGL". Disable this option if you haven't already. <em>Note:</em> If you follow these steps and still don't see an image, go to "chrome://flags/" and then search for "Override software rendering list" and enable this option.</p>
+        <p id="explanation"><strong>Safari:</strong> Open Safari's menu and select Preferences. Click on the advanced tab, and then check "Show Developer" menu. Then open the "Developer" menu and select "Enable WebGL".</p>
+        <p id="explanation"><strong>Firefox:</strong> Go to Options through Firefox > Options or Tools > Options. Go to Advanced, then General. Check "Use hardware acceleration when available" and restart Firefox.</p>
+        <p id="explanation"><strong>Other browsers:</strong> The only browsers that support WebGL are Chrome, Safari, and Firefox. Please switch to these browsers when using Emperor.</p>
+        <p id="explanation"><em>Note:</em> Once you went through these changes, reload the page and it should work!</p>
+        <p id="source">Sources: Instructions for <a href="https://www.biodigitalhuman.com/home/enabling-webgl.html">Chrome and Safari</a>, and <a href="http://www.infewbytes.com/?p=144">Firefox</a></p>
+    </div>
+</div>
+
+<div id="plotToggle">
+    <form>
+      <div id="plottype">
+        <input id="pcoa" type="radio" id="pcoa" name="plottype" checked="checked" /><label for="pcoa">PCoA</label>
+        <input id="parallel" type="radio" id="parallel" name="plottype" /><label for="parallel">Parallel</label>
+      </div>
+    </form>
+</div>
+<div id="pcoaPlotWrapper" class="plotWrapper">
+    <label id="pointCount" class="ontop">
+    </label>
+
+    <div id="finder" class="arrow-right">
+    </div>
+
+    <div id="labels" class="unselectable">
+    </div>
+
+    <div id="taxalabels" class="unselectable">
+    </div>
+
+    <div id="axislabels" class="axislabels">
+    </div>
+
+    <div id="main_plot">
+    </div>
+</div>
+
+<div id="parallelPlotWrapper" class="plotWrapper">
+</div>
+
+<div class="separator" ondblclick="separatorDoubleClick()"></div>
+
+<div id="menu">
+    <div id="menutabs">
+        <ul>
+            <li><a href="#keytab">Key</a></li>
+            <li><a href="#colorby">Colors</a></li>
+            <li><a href="#showby">Visibility</a></li>
+            <li><a href="#scalingby">Scaling</a></li>
+            <li><a href="#labelby">Labels</a></li>
+            <li><a href="#axes">Axes</a></li>
+            <li><a href="#options">Options</a></li>
+        </ul>
+        <div id="keytab">
+            <form name="keyFilter">
+            <label>Filter  </label><input name="filterBox" id="searchBox" type="text" onkeyup="filterKey()"></input>
+            </form>
+            <div id="key">
+            </div>
+        </div>
+        <div id="colorby">
+            <br>%s
+            <input type="checkbox" onchange="toggleContinuousAndDiscreteColors(this)" id="discreteorcontinuouscolors" name="discreteorcontinuouscolors">  Use gradient colors</input>
+            <br><br>
+            <select id="colorbycombo" onchange="colorByMenuChanged()" size="3">
+            </select>
+            <div class="list" id="colorbylist">
+            </div>
+        </div>
+        <div id="showby" align="center">%s
+            <table width="100%%">
+                <tr>
+                    <td align="center">
+                        <select id="showbycombo" onchange="showByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="showbylist" style="height:100%%;width:100%%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereopacity" class="text">Global Sphere Opacity</label>
+                        <label id="sphereopacity" class="slidervalue"></label>
+                        <div id="sopacityslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="scalingby" align="center">
+            <table width="100%%">
+                <tr>
+                    <td align="center">
+                        <select id="scalingbycombo" onchange="scalingByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="scalingbylist" style="height:100%%;width:100%%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereradius" class="text">Global Sphere Scale</label>
+                        <label id="sphereradius" class="slidervalue"></label>
+                        <div id="sradiusslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="labelby">
+        <div id="labelsTop">
+            <form name="plotoptions">
+            <input type="checkbox" onClick="toggleLabels()">Samples Label Visibility</input>
+            </form>%s
+            <br>
+            <label for="labelopacity" class="text">Label Opacity</label>
+            <label id="labelopacity" class="slidervalue"></label>
+            <div id="lopacityslider" class="slider-range-max"></div>
+            <div id="labelColorHolder clearfix">
+            <table>
+                <tr><td><div id="labelColor" class="colorbox"></div></td><td><label>Master Label Color</label></td></tr>%s
+            </table></div>
+        </div>
+            <br>
+            <select id="labelcombo" onchange="labelMenuChanged()">
+            </select>
+            <div class="list" id="labellist">
+            </div>
+        </div>
+        <div id="axes">
+            <div id="pcoaaxes">
+                <div class="list" id="axeslist">
+                </div>
+            </div>
+        </div>
+        <div id="options">
+            <table>
+                <tr><td><div id="axeslabelscolor" class="colorbox" name="axeslabelscolor"></div></td><td title="Axes Labels Color">Axes Labels Color</td></tr>
+                <tr><td><div id="axescolor" class="colorbox" name="axescolor"></div></td><td title="Axes Color Title">Axes Color</td></tr>
+                <tr><td><div id="rendererbackgroundcolor" class="colorbox" name="rendererbackgroundcolor"></div></td><td title="Background Color Title">Background Color</td></tr>%s
+            </table>
+            <div id="pcoaviewoptions" class="">%s%s%s
+                <form name="settingsoptionscolor">
+                </form>
+                <div id="pcoaoptions" class="">
+                    <form name="settingsoptions">
+                        <input type="checkbox" onchange="toggleScaleCoordinates(this)" id="scale_checkbox" name="scale_checkbox">Scale coords by percent explained</input>
+                    </form>
+                </div>
+                <br><input id="reset" class="button" type="submit" value="Recenter Camera" style="" onClick="resetCamera()">
+                <br><br>
+                <hr class='section-break'>
+                <br>Filename <small>(only letters, numbers, ., - and _)</small>:
+                <br><input name="saveas_name" id="saveas_name" value="screenshot" type="text"/>
+                <br><input id="saveas_legends" class="checkbox" type="checkbox" style=""> Create legend
+                <input id="saveas" class="button" type="submit" value="Save as SVG" style="" onClick="saveSVG()"/>
+                <br><br>For a PNG, simply press 'ctrl+p'.
+                <div id="paralleloptions" class="">
+                </div>
+            </div>
+            <br>
+        </div>
+    </div>  
+</div>
+</body>
+
+</html>
+"""
diff --git a/emperor/pycogent_backports/__init__.py b/emperor/pycogent_backports/__init__.py
new file mode 100644
index 0000000..6c64933
--- /dev/null
+++ b/emperor/pycogent_backports/__init__.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+__author__ = "Emperor Development Team"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza"] 
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+__all__ = ['procrustes']
diff --git a/emperor/pycogent_backports/procrustes.py b/emperor/pycogent_backports/procrustes.py
new file mode 100644
index 0000000..8de5fbf
--- /dev/null
+++ b/emperor/pycogent_backports/procrustes.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python
+"""Procrustes analysis.  Main fn: procrustes
+
+See for example: 
+Principles of Multivariate analysis, by Krzanowski
+"""
+
+from numpy.linalg import svd
+from numpy import (array, sqrt, sum, zeros, trace, dot, transpose,
+    divide, square, subtract, shape, any, abs, mean)
+from numpy import append as numpy_append
+
+__author__ = "Justin Kuczynski"
+__copyright__ = "Copyright 2007-2012, The Cogent Project"
+__credits__ = ["Justin Kuczynski"]
+__license__ = "BSD"
+__version__ = "1.5.3-dev"
+__maintainer__ = "Justin Kuczynski"
+__email__ = "justinak at gmail.com"
+__status__ = "Production"
+
+def procrustes(data1, data2):
+    """Procrustes analysis, a similarity test for two data sets.
+    
+    Each input matrix is a set of points or vectors (the rows of the matrix)
+    The dimension of the space is the number of columns of each matrix.
+    Given two identially sized matrices, procrustes standardizes both
+    such that:
+    - trace(AA') = 1  (A' is the transpose, and the product is
+    a standard matrix product).
+    - Both sets of points are centered around the origin
+    
+    Procrustes then applies the optimal transform to the second matrix
+    (including scaling/dilation, rotations, and reflections) to minimize
+    M^2 = sum(square(mtx1 - mtx2)), or the sum of the squares of the pointwise
+    differences between the two input datasets
+    
+    If two data sets have different dimensionality (different number of
+    columns), simply add columns of zeros the the smaller of the two.
+    
+    This function was not designed to handle datasets with different numbers of
+    datapoints (rows)
+    
+    Arguments:
+        - data1: matrix, n rows represent points in k (columns) space
+        data1 is the reference data, after it is standardised, the data from
+        data2 will be transformed to fit the pattern in data1
+        - data2: n rows of data in k space to be fit to data1.  Must be the 
+        same shape (numrows, numcols) as data1
+        - both must have >1 unique points
+        
+    Returns:
+        - mtx1: a standardized version of data1
+        - mtx2: the orientation of data2 that best fits data1.
+        centered, but not necessarily trace(mtx2*mtx2') = 1
+        - disparity: a metric for the dissimilarity of the two datasets,
+        disparity = M^2 defined above
+        
+    Notes:
+        - The disparity should not depend on the order of the input matrices, 
+        but the output matrices will, as only the first output matrix is
+        guaranteed to be scaled such that trace(AA') = 1.
+        - duplicate datapoints are generally ok, duplicating a data point will
+        increase it's effect on the procrustes fit.
+        - the disparity scales as the number of points per input matrix
+        
+    
+    """
+    SMALL_NUM = 1e-6 # used to check for zero values in added dimension
+    
+    # make local copies
+#     mtx1 = array(data1.copy(),'d')
+#     mtx2 = array(data2.copy(),'d')
+    num_rows, num_cols = shape(data1)
+    if (num_rows, num_cols) != shape(data2):
+        raise ValueError("input matrices must be of same shape")
+    if (num_rows == 0 or num_cols == 0):
+        raise ValueError("input matrices must be >0 rows, >0 cols")
+        
+    
+    # add a dimension to allow reflections (rotations in n + 1 dimensions)
+    mtx1 = numpy_append(data1, zeros((num_rows, 1)), 1)
+    mtx2 = numpy_append(data2, zeros((num_rows, 1)), 1)
+    
+    # standardize each matrix
+    mtx1 = center(mtx1)
+    mtx2 = center(mtx2)
+    
+    if ((not any(mtx1)) or (not any(mtx2))):
+        raise ValueError("input matrices must contain >1 unique points")
+
+    mtx1 = normalize(mtx1)
+    mtx2 = normalize(mtx2)
+    
+       
+    # transform mtx2 to minimize disparity (sum( (mtx1[i,j] - mtx2[i,j])^2) )
+    mtx2 = match_points(mtx1, mtx2)
+    
+    # WARNING: I haven't proven that after matching the matrices, no point has
+    # a nonzero component in the added dimension.  I believe it is true,
+    # though, since the unchanged matrix has no points extending into 
+    # that dimension
+    
+    if any(abs(mtx2[:,-1]) > SMALL_NUM):
+        raise StandardError("we have accidentially added a dimension to \
+the matrix, and the vectors have nonzero components in that dimension")
+    
+    # strip extra dimension which was added to allow reflections
+    mtx1 = mtx1[:,:-1]
+    mtx2 = mtx2[:,:-1]
+    
+    disparity = get_disparity(mtx1, mtx2)
+    
+    return mtx1, mtx2, disparity
+    
+def center(mtx):
+    """translate all data (rows of the matrix) to center on the origin
+    
+    returns a shifted version of the input data.  The new matrix is such that
+    the center of mass of the row vectors is centered at the origin.  
+    Returns a numpy float ('d') array
+    """
+    result = array(mtx, 'd')
+    result -= mean(result, 0) 
+    # subtract each column's mean from each element in that column
+    return result
+
+def normalize(mtx):
+    """change scaling of data (in rows) such that trace(mtx*mtx') = 1
+    
+    mtx' denotes the transpose of mtx """
+    result = array(mtx, 'd')
+    num_pts, num_dims = shape(result)
+    mag = trace(dot(result, transpose(result)))
+    norm = sqrt(mag)
+    result /= norm
+    return result
+
+def match_points(mtx1, mtx2):
+    """returns a transformed mtx2 that matches mtx1.
+    
+    returns a new matrix which is a transform of mtx2.  Scales and rotates
+    a copy of mtx 2.  See procrustes docs for details.
+    """
+    u,s,vh = svd(dot(transpose(mtx1), mtx2))
+    q = dot(transpose(vh), transpose(u))
+    new_mtx2 = dot(mtx2, q)
+    new_mtx2 *= sum(s)
+    
+    return new_mtx2
+
+def get_disparity(mtx1, mtx2):
+    """ returns a measure of the dissimilarity between two data sets
+    
+    returns M^2 = sum(square(mtx1 - mtx2)), the pointwise sum of squared
+    differences"""
+    return(sum(square(mtx1 - mtx2)))
diff --git a/emperor/qiime_backports/__init__.py b/emperor/qiime_backports/__init__.py
new file mode 100644
index 0000000..6f85b3b
--- /dev/null
+++ b/emperor/qiime_backports/__init__.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+# File created on 24 Jan 2013
+
+__author__ = "Emperor Development Team"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+__all__ = ['biplots', 'filter', 'format', 'make_3d_plots', 'parse', 'util']
diff --git a/emperor/qiime_backports/biplots.py b/emperor/qiime_backports/biplots.py
new file mode 100644
index 0000000..c6fda39
--- /dev/null
+++ b/emperor/qiime_backports/biplots.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+#file make_3d_plots.py
+
+__author__ = "Dan Knights"
+__copyright__ = "Copyright 2011, The QIIME Project" 
+__credits__ = ["Dan Knights", "Justin Kuczynski"] #remember to add yourself
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Dan Knights"
+__email__ = "daniel.knights at colorado.edu"
+__status__ = "Development"
+
+from emperor.qiime_backports.parse import parse_otu_table
+from numpy import array, apply_along_axis, dot,delete, argsort
+import numpy as np
+
+def get_taxa_coords(tax_counts,sample_coords):
+    """Returns the PCoA coords of each taxon based on the coords of the samples."""
+    # normalize taxa counts along each row/sample (i.e. to get relative abundance)
+    tax_counts = apply_along_axis(lambda x: x/float(sum(x)), 0, tax_counts)
+    # normalize taxa counts along each column/taxa (i.e. to make PCoA score contributions sum to 1)
+    tax_ratios = apply_along_axis(lambda x: x/float(sum(x)), 1, tax_counts)
+    return(dot(tax_ratios,sample_coords))
+
+def get_taxa_prevalence(tax_counts):
+    """Returns the each lineage's portion of the total count 
+    
+    takes an otu_table (rows = otus), normalizes samples to equal counts,
+    and returns each otu's relative representation in this normalized otu table,
+    scaled such that the rarest otu is 0, most prominent is 1
+    """
+    tax_ratios = apply_along_axis(lambda x: x/float(sum(x)), 0, tax_counts)
+    lineage_sums = apply_along_axis(lambda x: sum(x), 1, tax_ratios)
+    total_count = sum(lineage_sums)
+    prevalence = lineage_sums / float(total_count)
+    # scale prevalence from 0 to 1
+    prevalence = (prevalence - min(prevalence)) / (max(prevalence) - min(prevalence))
+    return prevalence
+
+def make_biplot_scores_output(taxa):
+    """Create convenient output format of taxon biplot coordinates
+       
+       taxa is a dict containing 'lineages' and a coord matrix 'coord'
+       
+       output is a list of lines, each containing coords for one taxon
+    """
+    output = []
+    ndims = len(taxa['coord'][1])
+    header = '#Taxon\t' + '\t'.join(['pc%d' %(i) for i in xrange(ndims)])
+    output.append(header)
+    for i, taxon in enumerate(taxa['lineages']):
+        line = taxon + '\t'
+        line += '\t'.join(map(str, taxa['coord'][i]))
+        output.append(line)
+    return output
diff --git a/emperor/qiime_backports/filter.py b/emperor/qiime_backports/filter.py
new file mode 100644
index 0000000..b959f83
--- /dev/null
+++ b/emperor/qiime_backports/filter.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+# File created on 18 May 2010
+from __future__ import division
+
+__author__ = "Greg Caporaso"
+__copyright__ = "Copyright 2011, The QIIME Project"
+__credits__ = ["Greg Caporaso", "Will Van Treuren", "Daniel McDonald",
+               "Jai Ram Rideout", "Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+__status__ = "Development"
+
+from emperor.qiime_backports.format import format_mapping_file
+from emperor.qiime_backports.parse import (parse_metadata_state_descriptions,
+    parse_mapping_file)
+
+def filter_mapping_file_by_metadata_states(mapping_f,valid_states_str):
+    sample_ids_to_keep = sample_ids_from_metadata_description(mapping_f,valid_states_str)
+    mapping_f.seek(0)
+    return filter_mapping_file_from_mapping_f(mapping_f,sample_ids_to_keep)
+
+def sample_ids_from_metadata_description(mapping_f,valid_states_str):
+    """ Given a description of metadata, return the corresponding sample ids
+    """
+    map_data, map_header, map_comments = parse_mapping_file(mapping_f)
+    valid_states = parse_metadata_state_descriptions(valid_states_str)
+    sample_ids = get_sample_ids(map_data, map_header, valid_states)
+
+    if len(sample_ids)<1:
+        raise ValueError,"All samples have been filtered out for the criteria"+\
+            " described in the valid states"
+
+    return sample_ids
+
+def filter_mapping_file_from_mapping_f(mapping_f,sample_ids_to_keep,negate=False):
+    """ Filter rows from a metadata mapping file """
+    mapping_data, header, comments = parse_mapping_file(mapping_f)
+    filtered_mapping_data = []
+    sample_ids_to_keep = {}.fromkeys(sample_ids_to_keep)
+    
+    for mapping_datum in mapping_data:
+        hit = mapping_datum[0] in sample_ids_to_keep
+        if hit and not negate:
+            filtered_mapping_data.append(mapping_datum)
+        elif not hit and negate:
+            filtered_mapping_data.append(mapping_datum)
+        else:
+            pass
+    return format_mapping_file(header,filtered_mapping_data)
+
+def get_sample_ids(map_data, map_header, states):
+    """Takes col states in {col:[vals]} format.
+
+    If val starts with !, exclude rather than include.
+    
+    Combines cols with and, states with or.
+
+    For example, Study:Dog,Hand will return rows where Study is Dog or Hand;
+    Study:Dog,Hand;BodySite:Palm,Stool will return rows where Study is Dog
+    or Hand _and_ BodySite is Palm or Stool; Study:*,!Dog;BodySite:*,!Stool
+    will return all rows except the ones where the Study is Dog or the BodySite
+    is Stool.
+    """
+    
+    name_to_col = dict([(s,map_header.index(s)) for s in states])
+    good_ids = []
+    for row in map_data:    #remember to exclude header
+        include = True
+        for s, vals in states.items():
+            curr_state = row[name_to_col[s]]
+            include = include and (curr_state in vals or '*' in vals) \
+                and not '!'+curr_state in vals
+        if include:        
+            good_ids.append(row[0])
+    return good_ids
+
+def filter_mapping_file(map_data, map_header, good_sample_ids, 
+               include_repeat_cols=False, column_rename_ids=None):
+    """Filters map according to several criteria.
+
+    - keep only sample ids in good_sample_ids
+    - drop cols that are different in every sample (except id)
+    - drop cols that are the same in every sample
+    """
+    # keeping samples
+    to_keep = []
+    to_keep.extend([i for i in map_data if i[0] in good_sample_ids])
+    
+    # keeping columns
+    headers = []
+    to_keep = zip(*to_keep)
+    headers.append(map_header[0])
+    result = [to_keep[0]]
+    
+    if column_rename_ids:
+        # reduce in 1 as we are not using the first colum (SampleID)
+        column_rename_ids = column_rename_ids-1
+        for i,l in enumerate(to_keep[1:-1]):
+            if i==column_rename_ids:
+                if len(set(l))!=len(result[0]):
+                     raise ValueError, "The column to rename the samples is not unique."
+                result.append(result[0])
+                result[0] = l
+                headers.append('SampleID_was_' + map_header[i+1])
+            elif include_repeat_cols or len(set(l))>1:
+                headers.append(map_header[i+1])
+                result.append(l)
+    else:
+        for i,l in enumerate(to_keep[1:-1]):
+            if include_repeat_cols or len(set(l))>1:
+                headers.append(map_header[i+1])
+                result.append(l)
+    headers.append(map_header[-1])
+    result.append(to_keep[-1])
+    
+    result = map(list,zip(*result))
+    
+    return headers, result
diff --git a/emperor/qiime_backports/format.py b/emperor/qiime_backports/format.py
new file mode 100644
index 0000000..c62343b
--- /dev/null
+++ b/emperor/qiime_backports/format.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+from __future__ import division
+__author__ = "Rob Knight"
+__copyright__ = "Copyright 2011, The QIIME Project" 
+__credits__ = ["Rob Knight", "Justin Kuczynski", "Antonio Gonzalez Pena",
+               "Daniel McDonald", "Jai Ram Rideout"]
+#remember to add yourself if you make changes
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+__status__ = "Development"
+
+def format_mapping_file(headers, mapping_data, comments=None):
+    """ returns a large formatted string representing the entire mapping file
+
+    each input is a list, and all data should be strings, not e.g. ints
+    * headers defines column labels, and SampleID should not include a '#'
+    * mapping_data is a list of lists, each sublist is a row in the mapping file
+    each mapping_data sublist must be the same length as headers - use ''
+    for absent data
+    * if included, commments will be inserted above the header line
+    comments should not include a # - that will be appended in this formatter
+    """
+    result = [] # each elem is a string representing a line
+
+    result.append('#' + '\t'.join(headers))
+
+    if comments != None:
+        for comment in comments:
+            result.append('#' + comment)
+
+    for mapping_line in mapping_data:
+        if not (len(mapping_line) == len(headers)):
+            raise RuntimeError('error formatting mapping file, does each '+\
+             'sample have the same length of data as the headers?')
+        result.append('\t'.join(mapping_line))
+
+    str_result = '\n'.join(result)
+    return str_result
diff --git a/emperor/qiime_backports/make_3d_plots.py b/emperor/qiime_backports/make_3d_plots.py
new file mode 100644
index 0000000..4b88632
--- /dev/null
+++ b/emperor/qiime_backports/make_3d_plots.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+#file make_3d_plots.py
+
+__author__ = "Dan Knights"
+__copyright__ = "Copyright 2011, The QIIME Project" 
+__credits__ = ["Dan Knights"]
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Development"
+
+from numpy import (array, append, transpose, column_stack, hstack,
+    apply_along_axis, nan, isnan, asarray)
+
+def get_custom_coords(axis_names,mapping, coords):
+    """Gets custom axis coords from the mapping file.
+       Appends custom as first column(s) of PCoA coords matrix.
+
+       Params:
+        axis_names, the names of headers of mapping file columns
+        mapping, the mapping file object (with list of headers in element 0)
+        coords, the PCoA coords object, with coords matrix in element 1
+    """
+    for i, axis in enumerate(reversed(axis_names)):
+        if not axis in mapping[0]:
+            raise ValueError, 'Warning: could not find custom axis %s in map headers: %s' \
+                % (axis, mapping[0])
+        else:
+            # get index of column in mapping file
+            col_idx = mapping[0].index(axis)
+            # extract column data
+            col = zip(*mapping[1:])[col_idx]
+            sample_IDs = zip(*mapping[1:])[0]
+            new_coords = array([])
+            # load custom coord for this axis for each sample ID 
+            for id in coords[0]:
+                if id in sample_IDs:
+                    row_idx = list(sample_IDs).index(id)
+                    try:
+                        as_float = float(col[row_idx])
+                        new_coords = append(new_coords,as_float)
+                    except ValueError:
+                        new_coords = append(new_coords,nan)
+            new_coords = transpose(column_stack(new_coords))
+            # append new coords to beginning column of coords matrix
+            coords[1] = hstack((new_coords,coords[1]))
+
+def remove_nans(coords):
+    """Deletes any samples with NANs in their coordinates"""
+    s = apply_along_axis(sum,1,isnan(coords[1])) == 0
+    coords[0] = (asarray(coords[0])[s]).tolist()
+    coords[1] = coords[1][s,:]
+
+def scale_custom_coords(custom_axes,coords):
+    """Scales custom coordinates to match min/max of PC1"""
+
+    # the target min and max
+    to_mn = min(coords[1][:,len(custom_axes)])
+    to_mx = 2*max(coords[1][:,len(custom_axes)])
+
+    # affine transformation for each custom axis
+    for i in xrange(len(custom_axes)):
+        from_mn = min(coords[1][:,i])
+        from_mx = max(coords[1][:,i])
+        coords[1][:,i] = (coords[1][:,i]  - from_mn) / (from_mx - from_mn)
+        coords[1][:,i] = (coords[1][:,i]) * (to_mx-to_mn) + to_mn
diff --git a/emperor/qiime_backports/parse.py b/emperor/qiime_backports/parse.py
new file mode 100644
index 0000000..5297b07
--- /dev/null
+++ b/emperor/qiime_backports/parse.py
@@ -0,0 +1,270 @@
+#!/usr/bin/env python
+#file parse.py: parsers for map file, distance matrix file, env file
+
+__author__ = "Rob Knight"
+__copyright__ = "Copyright 2011, The QIIME Project"
+__credits__ = ["Rob Knight", "Greg Caporaso", "Justin Kuczynski",
+                "Cathy Lozupone", "Antonio Gonzalez Pena", "Jai Ram Rideout"]
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+__status__ = "Development"
+
+
+from string import strip
+
+from numpy import asarray
+
+class QiimeParseError(Exception):
+    pass
+
+def parse_mapping_file(lines, strip_quotes=True, suppress_stripping=False):
+    """Parser for map file that relates samples to metadata.
+    
+    Format: header line with fields
+            optionally other comment lines starting with #
+            tab-delimited fields
+
+    Result: list of lists of fields, incl. headers.
+    """
+    if hasattr(lines,"upper"):
+        # Try opening if a string was passed
+        try:
+            lines = open(lines,'U')
+        except IOError:
+            raise QiimeParseError,\
+             ("A string was passed that doesn't refer "
+              "to an accessible filepath.")
+        
+    if strip_quotes:
+        if suppress_stripping:
+            # remove quotes but not spaces
+            strip_f = lambda x: x.replace('"','')
+        else:
+            # remove quotes and spaces
+            strip_f = lambda x: x.replace('"','').strip()
+    else:
+        if suppress_stripping:
+            # don't remove quotes or spaces
+            strip_f = lambda x: x
+        else:
+            # remove spaces but not quotes
+            strip_f = lambda x: x.strip()
+    
+    # Create lists to store the results
+    mapping_data = []
+    header = []
+    comments = []
+    
+    # Begin iterating over lines
+    for line in lines:
+        line = strip_f(line)
+        if not line or (suppress_stripping and not line.strip()):
+            # skip blank lines when not stripping lines
+            continue
+        
+        if line.startswith('#'):
+            line = line[1:]
+            if not header:
+                header = line.strip().split('\t')
+            else:
+                comments.append(line)
+        else:
+            # Will add empty string to empty fields
+            tmp_line = map(strip_f, line.split('\t'))
+            if len(tmp_line)<len(header):
+                tmp_line.extend(['']*(len(header)-len(tmp_line)))
+            mapping_data.append(tmp_line)
+    if not header:
+        raise QiimeParseError, "No header line was found in mapping file."
+    if not mapping_data:
+        raise QiimeParseError, "No data found in mapping file."
+    
+    return mapping_data, header, comments
+
+def mapping_file_to_dict(mapping_data, header):
+    """processes mapping data in list of lists format into a 2 deep dict"""
+    map_dict = {}
+    for i in range(len(mapping_data)):
+        sam = mapping_data[i]
+        map_dict[sam[0]] = {}
+        for j in range(len(header)):
+            if j == 0: continue # sampleID field
+            map_dict[sam[0]][header[j]] = sam[j]
+    return map_dict
+
+def parse_metadata_state_descriptions(state_string):
+    """From string in format 'col1:good1,good2;col2:good1' return dict."""
+    result = {}
+    state_string = state_string.strip()
+    if state_string:
+        cols = map(strip, state_string.split(';'))
+        for c in cols:
+            # split on the first colon to account for category names with colons
+            colname, vals = map(strip, c.split(':', 1))
+            vals = map(strip, vals.split(','))
+            result[colname] = set(vals)
+    return result
+
+def parse_mapping_file_to_dict(*args, **kwargs):
+    """Parser for map file that relates samples to metadata.
+    
+    input format: header line with fields
+            optionally other comment lines starting with #
+            tab-delimited fields
+
+    calls parse_mapping_file, then processes the result into a 2d dict, assuming
+    the first field is the sample id
+    e.g.: {'sample1':{'age':'3','sex':'male'},'sample2':...
+
+    returns the dict, and a list of comment lines
+"""
+    mapping_data, header, comments = parse_mapping_file(*args,**kwargs)
+    return mapping_file_to_dict(mapping_data, header), comments
+
+def process_otu_table_sample_ids(sample_id_fields):
+    """ process the sample IDs line of an OTU table """
+    if len(sample_id_fields) == 0:
+            raise ValueError, \
+             'Error parsing sample ID line in OTU table. Fields are %s' \
+             % ' '.join(sample_id_fields)
+            
+    # Detect if a metadata column is included as the last column. This
+    # field will be named either 'Consensus Lineage' or 'OTU Metadata',
+    # but we don't care about case or spaces.
+    last_column_header = sample_id_fields[-1].strip().replace(' ','').lower()
+    if last_column_header in ['consensuslineage', 'otumetadata', 'taxonomy']:
+        has_metadata = True
+        sample_ids = sample_id_fields[:-1]
+    else:
+        has_metadata = False
+        sample_ids = sample_id_fields
+    
+    # Return the list of sample IDs and boolean indicating if a metadata
+    # column is included.
+    return sample_ids, has_metadata
+
+def parse_classic_otu_table(lines,count_map_f=int, remove_empty_rows=False):
+    """parses a classic otu table (sample ID x OTU ID map)
+
+    Returns tuple: sample_ids, otu_ids, matrix of OTUs(rows) x samples(cols),
+    and lineages from infile.
+    """
+    otu_table = []
+    otu_ids = []
+    metadata = []
+    sample_ids = []
+    # iterate over lines in the OTU table -- keep track of line number 
+    # to support legacy (Qiime 1.2.0 and earlier) OTU tables
+    for i, line in enumerate(lines):
+        line = line.strip()
+        if line:
+            if (i==1 or i==0) and line.startswith('#OTU ID') and not sample_ids:
+                # we've got a legacy OTU table
+                try:
+                    sample_ids, has_metadata = process_otu_table_sample_ids(
+                     line.strip().split('\t')[1:])
+                except ValueError:
+                    raise ValueError, \
+                     "Error parsing sample IDs in OTU table. Appears to be a"+\
+                     " legacy OTU table. Sample ID line:\n %s" % line
+            elif not line.startswith('#'):
+                if not sample_ids:
+                    # current line is the first non-space, non-comment line 
+                    # in OTU table, so contains the sample IDs
+                    try:
+                        sample_ids, has_metadata = process_otu_table_sample_ids(
+                         line.strip().split('\t')[1:])
+                    except ValueError:
+                        raise ValueError,\
+                         "Error parsing sample IDs in OTU table."+\
+                         " Sample ID line:\n %s" % line
+                else:
+                    # current line is OTU line in OTU table
+                    fields = line.split('\t')
+                    
+                    if has_metadata:
+                        # if there is OTU metadata the last column gets appended
+                        # to the metadata list
+                        # added in a try/except to handle OTU tables containing
+                        # floating numbers
+                        try:
+                            valid_fields = asarray(fields[1:-1], dtype=count_map_f)
+                        except ValueError:
+                            valid_fields = asarray(fields[1:-1], dtype=float)
+                        # validate that there are no empty rows
+                        if remove_empty_rows and (valid_fields>=0).all() and \
+                           sum(valid_fields)==0.0:
+                            continue
+                        metadata.append(map(strip, fields[-1].split(';')))
+                    else:
+                        # otherwise all columns are appended to otu_table
+                        # added in a try/except to handle OTU tables containing
+                        # floating numbers
+                        try:
+                            valid_fields = asarray(fields[1:], dtype=count_map_f)
+                        except ValueError:
+                            valid_fields = asarray(fields[1:], dtype=float)
+                        # validate that there are no empty rows
+                        if remove_empty_rows and (valid_fields>=0.0).all() and \
+                           sum(valid_fields)==0.0:
+                            continue
+                    otu_table.append(valid_fields)
+                    # grab the OTU ID    
+                    otu_id = fields[0].strip()
+                    otu_ids.append(otu_id)
+                        
+    return sample_ids, otu_ids, asarray(otu_table), metadata
+parse_otu_table = parse_classic_otu_table
+
+def parse_coords(lines):
+    """Parse unifrac coord file into coords, labels, eigvals, pct_explained.
+
+    Returns:
+    - list of sample labels in order
+    - array of coords (rows = samples, cols = axes in descending order)
+    - list of eigenvalues
+    - list of percent variance explained
+
+    File format is tab-delimited with following contents:
+    - header line (starts 'pc vector number')
+    - one-per-line per-sample coords
+    - two blank lines
+    - eigvals
+    - % variation explained
+
+    Strategy: just read the file into memory, find the lines we want
+    """
+
+    lines = list(lines)
+
+    # make sure these and the other checks below are true as they are what
+    # differentiate coordinates files from distance matrix files
+    if not lines[0].startswith('pc vector number'):
+        raise QiimeParseError("The line with the vector number was not found"
+            ", this information is required in coordinates files")
+
+    lines = map(strip, lines[1:])   #discard first line, which is a label
+    lines = filter(None, lines) #remove any blank lines
+
+    # check on this information post removal of blank lines
+    if not lines[-2].startswith('eigvals'):
+        raise QiimeParseError("The line containing the eigenvalues was not "
+            "found, this information is required in coordinates files")
+    if not lines[-1].startswith('% variation'):
+        raise QiimeParseError("The line with the percent of variation explained"
+            " was not found, this information is required in coordinates files")
+
+    #now last 2 lines are eigvals and % variation, so read them
+    eigvals = asarray(lines[-2].split('\t')[1:], dtype=float)
+    pct_var = asarray(lines[-1].split('\t')[1:], dtype=float)
+
+    #finally, dump the rest of the lines into a table
+    header, result = [], []
+    for line in lines[:-2]:
+        fields = map(strip, line.split('\t'))
+        header.append(fields[0])
+        result.append(map(float, fields[1:]))
+
+    return header, asarray(result), eigvals, pct_var
diff --git a/emperor/qiime_backports/util.py b/emperor/qiime_backports/util.py
new file mode 100644
index 0000000..d7f08c1
--- /dev/null
+++ b/emperor/qiime_backports/util.py
@@ -0,0 +1,423 @@
+#!/usr/bin/env python
+
+__author__ = "Daniel McDonald"
+__copyright__ = "Copyright 2011, The QIIME Project" 
+__credits__ = ["Catherine Lozupone", "Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshik89 at gmail.com"
+__status__ = "Development"
+
+from emperor.qiime_backports.parse import parse_mapping_file_to_dict
+from emperor.pycogent_backports.procrustes import procrustes
+
+from numpy.ma.extras import apply_along_axis
+from numpy.ma import MaskedArray
+from numpy import (shape, vstack, zeros, sum as numpy_sum, sort as numpy_sort,
+    nan as numpy_nan, array, median)
+
+def is_valid_git_refname(refname):
+    """check if a string is a valid branch-name/ref-name for git
+
+    Input:
+    refname: string to validate
+
+    Output:
+    True if 'refname' is a valid branch name in git. False if it fails to meet
+    any of the criteria described in the man page for 'git check-ref-format',
+    also see:
+
+    http://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
+    """
+    if len(refname) == 0:
+        return False
+
+    # git imposes a few requirements to accept a string as a refname/branch-name
+
+    # They can include slash / for hierarchical (directory) grouping, but no
+    # slash-separated component can begin with a dot . or end with the sequence
+    # .lock
+    if (len([True for element in refname.split('/')\
+            if element.startswith('.') or element.endswith('.lock')]) != 0):
+        return False
+
+    # They cannot have two consecutive dots .. anywhere
+    if '..' in refname:
+        return False
+
+    # They cannot have ASCII control characters (i.e. bytes whose values are
+    # lower than \040, or \177 DEL), space, tilde, caret ^, or colon : anywhere
+    if len([True for refname_char in refname if ord(refname_char) < 40 or\
+            ord(refname_char) == 177 ]) != 0:
+        return False
+    if ' ' in refname or '~' in refname or '^' in refname or ':' in refname:
+        return False
+
+    # They cannot have question-mark ?, asterisk *, or open bracket [ anywhere
+    if '?' in refname or '*' in refname or '[' in refname:
+        return False
+
+    # They cannot begin or end with a slash / or contain multiple consecutive
+    # slashes
+    if refname.startswith('/') or refname.endswith('/') or '//' in refname:
+        return False
+
+    # They cannot end with a dot ..
+    if refname.endswith('.'):
+        return False
+
+    # They cannot contain a sequence @{
+    if '@{' in refname:
+        return False
+
+    # They cannot contain a \
+    if '\\' in refname:
+        return False
+
+    return True
+
+def is_valid_git_sha1(hash):
+    """check if a string is a valid git sha1 string
+
+    Input:
+    hash: string to validate
+
+    Output:
+    True if the string has 40 characters and is an hexadecimal number, False
+    otherwise.
+
+    """
+
+    if len(hash) != 40:
+        return False
+    try:
+        value = int(hash, 16)
+    except ValueError:
+        return False
+
+    return True
+
+class MetadataMap():
+    """This class represents a QIIME metadata mapping file.
+    
+    Public attributes:
+        Comments - the comments associated with this metadata map (a list of
+            strings)
+    """
+
+    @staticmethod
+    def parseMetadataMap(lines):
+        """Parses a QIIME metadata mapping file into a MetadataMap object.
+
+        This static method is basically a factory that reads in the given
+        metadata mapping file contents and returns a MetadataMap instance. This
+        method is provided for convenience.
+
+        Arguments:
+            lines - a list of strings representing the file contents of a QIIME
+                metadata mapping file
+        """
+        return MetadataMap(*parse_mapping_file_to_dict(lines))
+
+    def __init__(self, sample_metadata, Comments):
+        """Instantiates a MetadataMap object.
+
+        Arguments:
+            sample_metadata - the output of parse_mapping_file_to_dict(). It
+                expects a python dict of dicts, where the top-level key is
+                sample ID, and the inner dict maps category name to category
+                value. This can be an empty dict altogether or the inner dict
+                can be empty
+            Comments - the output of parse_mapping_file_to_dict(). It expects a
+                list of strings for the comments in the mapping file. Can be an
+                empty list
+        """
+        self._metadata = sample_metadata
+        self.Comments = Comments
+
+    def __eq__(self, other):
+        """Test this instance for equality with another.
+
+        Note: This code was taken from http://stackoverflow.com/questions/
+            390250/elegant-ways-to-support-equivalence-equality-in-python-
+            classes.
+        """
+        if isinstance(other, self.__class__):
+            return self.__dict__ == other.__dict__
+        else:
+            return False
+
+    def __ne__(self, other):
+        """Test this instance for inequality with another.
+
+        Note: This code was taken from http://stackoverflow.com/questions/
+            390250/elegant-ways-to-support-equivalence-equality-in-python-
+            classes.
+        """
+        return not self.__eq__(other)
+
+    def getSampleMetadata(self, sample_id):
+        """Returns the metadata associated with a particular sample.
+
+        The metadata will be returned as a dict mapping category name to
+        category value.
+
+        Arguments:
+            sample_id - the sample ID (string) to retrieve metadata for
+        """
+        return self._metadata[sample_id]
+
+    def getCategoryValue(self, sample_id, category):
+        """Returns the category value associated with a sample's category.
+
+        The returned category value will be a string.
+
+        Arguments:
+            sample_id - the sample ID (string) to retrieve category information
+                for
+            category - the category name whose value will be returned
+        """
+        return self._metadata[sample_id][category]
+
+    def getCategoryValues(self, sample_ids, category):
+        """Returns all the values of a given category.
+
+        The return categories will be a list.
+
+        Arguments:
+            sample_ids - An ordered list of sample IDs (i.e., from a distance
+                matrix)
+            category - the category name whose values will be returned
+        """
+        return [self._metadata[sid][category] for sid in sample_ids]
+
+    def isNumericCategory(self, category):
+        """Returns True if the category is numeric and False otherwise.
+
+        A category is numeric if all values within the category can be
+        converted to a float.
+
+        Arguments:
+            category - the category that will be checked
+        """
+        category_values = self.getCategoryValues(self.SampleIds, category)
+
+        is_numeric = True
+        for category_value in category_values:
+            try:
+                float(category_value)
+            except ValueError:
+                is_numeric = False
+        return is_numeric
+
+    def hasUniqueCategoryValues(self, category):
+        """Returns True if the category's values are all unique.
+
+        Arguments:
+            category - the category that will be checked for uniqueness
+        """
+        category_values = self.getCategoryValues(self.SampleIds, category)
+
+        is_unique = False
+        if len(set(category_values)) == len(self.SampleIds):
+            is_unique = True
+        return is_unique
+
+    def hasSingleCategoryValue(self, category):
+        """Returns True if the category's values are all the same.
+
+        For example, the category 'Treatment' only has values 'Control' for the
+        entire column.
+
+        Arguments:
+            category - the category that will be checked
+        """
+        category_values = self.getCategoryValues(self.SampleIds, category)
+
+        single_value = False
+        if len(set(category_values)) == 1:
+            single_value = True
+        return single_value
+
+    @property
+    def SampleIds(self):
+        """Returns the IDs of all samples in the metadata map.
+
+        The sample IDs are returned as a list of strings in alphabetical order.
+        """
+        return sorted(self._metadata.keys())
+
+    @property
+    def CategoryNames(self):
+        """Returns the names of all categories in the metadata map.
+
+        The category names are returned as a list of strings in alphabetical
+        order.
+        """
+        return sorted(self.getSampleMetadata(self.SampleIds[0]).keys()) \
+            if len(self.SampleIds) > 0 else []
+
+    def filterSamples(self, sample_ids_to_keep, strict=True):
+        """Remove samples that are not in ``sample_ids_to_keep``.
+
+        If ``strict=True``, a ``ValueError`` will be raised if any of the
+        sample IDs in ``sample_ids_to_keep`` cannot be found in the metadata
+        map.
+        """
+        for sid in self.SampleIds:
+            if sid not in sample_ids_to_keep:
+                del self._metadata[sid]
+
+        if strict:
+            extra_samples = set(sample_ids_to_keep) - set(self.SampleIds)
+
+            if extra_samples:
+                raise ValueError("Could not find the following sample IDs in "
+                                 "metadata map: %s" % ', '.join(extra_samples))
+
+def summarize_pcoas(master_pcoa, support_pcoas, method='IQR', apply_procrustes=True):
+    """returns the average PCoA vector values for the support pcoas
+
+    Also returns the ranges as calculated with the specified method.
+    The choices are:
+        IQR: the Interquartile Range
+        ideal fourths: Ideal fourths method as implemented in scipy
+    """
+    if apply_procrustes:
+        # perform procrustes before averaging
+        support_pcoas = [list(sp) for sp in support_pcoas]
+        master_pcoa = list(master_pcoa)
+        for i, pcoa in enumerate(support_pcoas):
+            master_std, pcoa_std, m_squared = procrustes(master_pcoa[1],pcoa[1])
+            support_pcoas[i][1] = pcoa_std
+        master_pcoa[1] = master_std
+
+    m_matrix = master_pcoa[1]
+    m_eigvals = master_pcoa[2]
+    m_names = master_pcoa[0]
+    jn_flipped_matrices = []
+    all_eigvals = []
+    for rep in support_pcoas:
+        matrix = rep[1]
+        eigvals = rep[2]
+        all_eigvals.append(eigvals)
+        jn_flipped_matrices.append(_flip_vectors(matrix, m_matrix))
+    matrix_average, matrix_low, matrix_high = _compute_jn_pcoa_avg_ranges(\
+            jn_flipped_matrices, method)
+    #compute average eigvals
+    all_eigvals_stack = vstack(all_eigvals)
+    eigval_sum = numpy_sum(all_eigvals_stack, axis=0)
+    eigval_average = eigval_sum / float(len(all_eigvals))
+    return matrix_average, matrix_low, matrix_high, eigval_average, m_names
+
+def _flip_vectors(jn_matrix, m_matrix):
+    """transforms PCA vectors so that signs are correct"""
+    m_matrix_trans = m_matrix.transpose()
+    jn_matrix_trans = jn_matrix.transpose()
+    new_matrix= zeros(jn_matrix_trans.shape, float)
+    for i, m_vector in enumerate(m_matrix_trans):
+        jn_vector = jn_matrix_trans[i]
+        disT = list(m_vector - jn_vector)
+        disT = sum(map(abs, disT))
+        jn_flip = jn_vector*[-1]
+        disF = list(m_vector - jn_flip)
+        disF = sum(map(abs, disF))
+        if disT > disF:
+            new_matrix[i] = jn_flip
+        else:
+            new_matrix[i] = jn_vector
+    return new_matrix.transpose()
+
+def _compute_jn_pcoa_avg_ranges(jn_flipped_matrices, method):
+    """Computes PCoA average and ranges for jackknife plotting
+
+    returns 1) an array of jn_averages
+             2) an array of upper values of the ranges
+            3) an array of lower values for the ranges
+
+    method: the method by which to calculate the range
+        IQR: Interquartile Range
+        ideal fourths: Ideal fourths method as implemented in scipy
+    """
+    x,y = shape(jn_flipped_matrices[0])
+    all_flat_matrices = [matrix.ravel() for matrix in jn_flipped_matrices]
+    summary_matrix = vstack(all_flat_matrices)
+    matrix_sum = numpy_sum(summary_matrix, axis=0)
+    matrix_average = matrix_sum / float(len(jn_flipped_matrices))
+    matrix_average = matrix_average.reshape(x,y)
+    if method == 'IQR':
+        result = matrix_IQR(summary_matrix)
+        matrix_low = result[0].reshape(x,y)
+        matrix_high = result[1].reshape(x,y)
+    elif method == 'ideal_fourths':
+        result = idealfourths(summary_matrix, axis=0)
+        matrix_low = result[0].reshape(x,y)
+        matrix_high = result[1].reshape(x,y)
+    elif method == "sdev":
+        # calculate std error for each sample in each dimension
+        sdevs = zeros(shape=[x,y])
+        for j in xrange(y):
+            for i in xrange(x):
+                vals = array([pcoa[i][j] for pcoa in jn_flipped_matrices])
+                sdevs[i,j] = vals.std(ddof=1)
+        matrix_low = -sdevs/2
+        matrix_high = sdevs/2
+
+
+    return matrix_average, matrix_low, matrix_high
+
+def IQR(x):
+    """calculates the interquartile range of x
+
+    x can be a list or an array
+    
+    returns min_val and  max_val of the IQR"""
+
+    x.sort()
+    #split values into lower and upper portions at the median
+    odd = len(x) % 2
+    midpoint = int(len(x)/2)
+    if odd:
+        low_vals = x[:midpoint]
+        high_vals = x[midpoint+1:]
+    else: #if even
+        low_vals = x[:midpoint]
+        high_vals = x[midpoint:]
+    #find the median of the low and high values
+    min_val = median(low_vals)
+    max_val = median(high_vals)
+    return min_val, max_val
+
+
+def matrix_IQR(x):
+    """calculates the IQR for each column in an array
+    """
+    num_cols = x.shape[1]
+    min_vals = zeros(num_cols)
+    max_vals = zeros(num_cols)
+    for i in range(x.shape[1]):
+        col = x[:, i]
+        min_vals[i], max_vals[i] = IQR(col)
+    return min_vals, max_vals
+
+def idealfourths(data, axis=None):
+    """This function returns an estimate of the lower and upper quartiles of the data along
+    the given axis, as computed with the ideal fourths. This function was taken
+    from scipy.stats.mstat_extra.py (http://projects.scipy.org/scipy/browser/trunk/scipy/stats/mstats_extras.py?rev=6392)
+    """
+    def _idf(data):
+        x = data.compressed()
+        n = len(x)
+        if n < 3:
+            return [numpy_nan,numpy_nan]
+        (j,h) = divmod(n/4. + 5/12.,1)
+        qlo = (1-h)*x[j-1] + h*x[j]
+        k = n - j
+        qup = (1-h)*x[k] + h*x[k-1]
+        return [qlo, qup]
+    data = numpy_sort(data, axis=axis).view(MaskedArray)
+    if (axis is None):
+        return _idf(data)
+    else:
+        return apply_along_axis(_idf, axis, data)
\ No newline at end of file
diff --git a/emperor/sort.py b/emperor/sort.py
new file mode 100644
index 0000000..c2ade34
--- /dev/null
+++ b/emperor/sort.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+# File created on 20 Apr 2013
+from __future__ import division
+
+__author__ = "Yoshiki Vazquez Baeza"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+from numpy import zeros
+from re import compile, search
+
+def sort_taxa_table_by_pcoa_coords(coords_header, otu_table, otu_header):
+    """Sort and match the samples in the otu table and in the coordinates data
+
+    Inputs:
+    coords_header: sample ids that are present in principal coordinates data
+    otu_table: numpy array with the data for an otu table
+    otu_header: sample ids present in the otu table
+
+    Ouputs:
+    sorted_otu_headers: sample ids that were present in the coords_header list,
+    the order in this table matches the order of the coordinates data
+    sorted_otu_table: otu table data with columns belonging to the sample ids in
+    the sorted_otu_headers list
+
+    This function will sort the columns of an otu table as suggested by the
+    sample ids in the coords_header
+    """
+
+    sorted_otu_headers = []
+
+    # the size of the otu table can be pre-allocated for better memory usage
+    matching_headers = len(set(coords_header)&set(otu_header))
+    sorted_otu_table = zeros([otu_table.shape[0], matching_headers])
+
+    # iterate through the available sample ids in the coordinates file and work
+    # only with the ones that are present in the coords and the otu table; the
+    # order of the ids is important hence iterate through the original list
+    for i, element in enumerate(coords_header):
+        if element in otu_header:
+            current_index = otu_header.index(element)
+            sorted_otu_table[:,i] = otu_table[:,current_index]
+            sorted_otu_headers.append(element)
+
+    return sorted_otu_headers, sorted_otu_table
+
+def sort_comparison_filenames(coord_fps):
+    """Pass in a list of file names and sort them using the suffix
+
+    Input:
+    coord_fps: list of filenames with the format something_something_qX.txt
+    where X is the index of the file.
+
+    Output:
+    Returns a sorted version of the list that was passed in where the strings
+    are sorted according to the suffix they have, if the string doesn't have
+    a suffix it will be added to the beginning of the list.
+    """
+
+    if coord_fps == []:
+        return []
+
+    def _get_suffix(fp):
+        """Gets the number in the suffix for a string using a regex"""
+        # any alphanumeric set of characters proceeded by a 'q', a number, a dot
+        # & a txt extension at the end of the line. Take for example 
+        # bray_curtis_q1.txt or unifrac_q11.txt
+        re = compile(r'(\w+)_q([0-9]+).txt$')
+        tmatch = search(re, fp)
+
+        try:
+            number = tmatch.group(2)
+        # if the regex doesn't match then put it at the beginning
+        except (IndexError, AttributeError):
+            number = -1
+
+        return float(number)
+
+    # the key function retrieves the suffix number for the function to sort
+    # according to it's floating point representation i. e. the cast to float
+    return sorted(coord_fps, key=_get_suffix)
+
diff --git a/emperor/support_files/css/colorPicker.css b/emperor/support_files/css/colorPicker.css
new file mode 100755
index 0000000..b16b98c
--- /dev/null
+++ b/emperor/support_files/css/colorPicker.css
@@ -0,0 +1,31 @@
+div.colorPicker-picker {
+  height: 16px;
+  width: 16px;
+  padding: 0 !important;
+  border: 1px solid #ccc;
+  background: url(arrow.gif) no-repeat top right;
+  cursor: pointer;
+  line-height: 16px;
+}
+
+div.colorPicker-palette {
+  width: 110px;
+  position: absolute;
+  border: 1px solid #598FEF;
+  background-color: #EFEFEF;
+  padding: 2px;
+  z-index: 9999;
+}
+  div.colorPicker_hexWrap {width: 100%; float:left }
+  div.colorPicker_hexWrap label {font-size: 95%; color: #2F2F2F; margin: 5px 2px; width: 25%}
+  div.colorPicker_hexWrap input {margin: 5px 2px; padding: 0; font-size: 95%; border: 1px solid #000; width: 65%; }
+
+div.colorPicker-swatch {
+  height: 12px;
+  width: 12px;
+  border: 1px solid #000;
+  margin: 2px;
+  float: left;
+  cursor: pointer;
+  line-height: 12px;
+}
diff --git a/emperor/support_files/css/d3.parcoords.css b/emperor/support_files/css/d3.parcoords.css
new file mode 100644
index 0000000..7c443c6
--- /dev/null
+++ b/emperor/support_files/css/d3.parcoords.css
@@ -0,0 +1,40 @@
+.parcoords > svg, .parcoords > canvas { 
+  font: 14px sans-serif;
+  position: absolute;
+}
+.parcoords > canvas {
+  pointer-events: none;
+}
+.parcoords rect.background {
+  fill: transparent;
+}
+.parcoords rect.background:hover {
+  fill: rgba(120,120,120,0.2);
+}
+.parcoords .resize rect {
+  fill: rgba(0,0,0,0.1);
+}
+.parcoords rect.extent {
+  fill: rgba(255,255,255,0.25);
+  stroke: rgba(0,0,0,0.6);
+}
+.parcoords .axis line, .parcoords .axis path {
+  fill: none;
+  stroke: #fff;
+  shape-rendering: crispEdges;
+}
+
+.parcoords text {
+	stroke: #fff;
+	shape-rendering: crispEdges;
+}
+
+.parcoords canvas {
+  opacity: 1;
+  -moz-transition: opacity 0.3s;
+  -webkit-transition: opacity 0.3s;
+  -o-transition: opacity 0.3s;
+}
+.parcoords canvas.faded {
+  opacity: 0.25;
+}
diff --git a/emperor/support_files/css/images/ui-bg_flat_0_aaaaaa_40x100.png b/emperor/support_files/css/images/ui-bg_flat_0_aaaaaa_40x100.png
new file mode 100755
index 0000000..5b5dab2
Binary files /dev/null and b/emperor/support_files/css/images/ui-bg_flat_0_aaaaaa_40x100.png differ
diff --git a/emperor/support_files/css/images/ui-bg_flat_75_ffffff_40x100.png b/emperor/support_files/css/images/ui-bg_flat_75_ffffff_40x100.png
new file mode 100755
index 0000000..ac8b229
Binary files /dev/null and b/emperor/support_files/css/images/ui-bg_flat_75_ffffff_40x100.png differ
diff --git a/emperor/support_files/css/images/ui-bg_glass_55_fbf9ee_1x400.png b/emperor/support_files/css/images/ui-bg_glass_55_fbf9ee_1x400.png
new file mode 100755
index 0000000..ad3d634
Binary files /dev/null and b/emperor/support_files/css/images/ui-bg_glass_55_fbf9ee_1x400.png differ
diff --git a/emperor/support_files/css/images/ui-bg_glass_65_ffffff_1x400.png b/emperor/support_files/css/images/ui-bg_glass_65_ffffff_1x400.png
new file mode 100755
index 0000000..42ccba2
Binary files /dev/null and b/emperor/support_files/css/images/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/emperor/support_files/css/images/ui-bg_glass_75_dadada_1x400.png b/emperor/support_files/css/images/ui-bg_glass_75_dadada_1x400.png
new file mode 100755
index 0000000..5a46b47
Binary files /dev/null and b/emperor/support_files/css/images/ui-bg_glass_75_dadada_1x400.png differ
diff --git a/emperor/support_files/css/images/ui-bg_glass_75_e6e6e6_1x400.png b/emperor/support_files/css/images/ui-bg_glass_75_e6e6e6_1x400.png
new file mode 100755
index 0000000..86c2baa
Binary files /dev/null and b/emperor/support_files/css/images/ui-bg_glass_75_e6e6e6_1x400.png differ
diff --git a/emperor/support_files/css/images/ui-bg_glass_95_fef1ec_1x400.png b/emperor/support_files/css/images/ui-bg_glass_95_fef1ec_1x400.png
new file mode 100755
index 0000000..4443fdc
Binary files /dev/null and b/emperor/support_files/css/images/ui-bg_glass_95_fef1ec_1x400.png differ
diff --git a/emperor/support_files/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/emperor/support_files/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png
new file mode 100755
index 0000000..7c9fa6c
Binary files /dev/null and b/emperor/support_files/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ
diff --git a/emperor/support_files/css/images/ui-icons_222222_256x240.png b/emperor/support_files/css/images/ui-icons_222222_256x240.png
new file mode 100755
index 0000000..b273ff1
Binary files /dev/null and b/emperor/support_files/css/images/ui-icons_222222_256x240.png differ
diff --git a/emperor/support_files/css/images/ui-icons_2e83ff_256x240.png b/emperor/support_files/css/images/ui-icons_2e83ff_256x240.png
new file mode 100755
index 0000000..09d1cdc
Binary files /dev/null and b/emperor/support_files/css/images/ui-icons_2e83ff_256x240.png differ
diff --git a/emperor/support_files/css/images/ui-icons_454545_256x240.png b/emperor/support_files/css/images/ui-icons_454545_256x240.png
new file mode 100755
index 0000000..59bd45b
Binary files /dev/null and b/emperor/support_files/css/images/ui-icons_454545_256x240.png differ
diff --git a/emperor/support_files/css/images/ui-icons_888888_256x240.png b/emperor/support_files/css/images/ui-icons_888888_256x240.png
new file mode 100755
index 0000000..6d02426
Binary files /dev/null and b/emperor/support_files/css/images/ui-icons_888888_256x240.png differ
diff --git a/emperor/support_files/css/images/ui-icons_cd0a0a_256x240.png b/emperor/support_files/css/images/ui-icons_cd0a0a_256x240.png
new file mode 100755
index 0000000..2ab019b
Binary files /dev/null and b/emperor/support_files/css/images/ui-icons_cd0a0a_256x240.png differ
diff --git a/emperor/support_files/css/jquery-ui-1.8.16.custom.css b/emperor/support_files/css/jquery-ui-1.8.16.custom.css
new file mode 100755
index 0000000..9d83307
--- /dev/null
+++ b/emperor/support_files/css/jquery-ui-1.8.16.custom.css
@@ -0,0 +1,568 @@
+/*
+ * jQuery UI CSS Framework 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*
+ * jQuery UI CSS Framework 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass. [...]
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
+.ui-widget-header a { color: #222222; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight  {border: 1px solid #fcefa1; background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary,  .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/*
+ * jQuery UI Resizable 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizable#theming
+ */
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block; }
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*
+ * jQuery UI Selectable 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Selectable#theming
+ */
+.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
+/*
+ * jQuery UI Accordion 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Accordion#theming
+ */
+/* IE/Win - Fix animation bug - #4615 */
+.ui-accordion { width: 100%; }
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
+.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
+.ui-accordion .ui-accordion-content-active { display: block; }
+/*
+ * jQuery UI Autocomplete 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete#theming
+ */
+.ui-autocomplete { position: absolute; cursor: default; }	
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+
+/*
+ * jQuery UI Menu 1.8.16
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Menu#theming
+ */
+.ui-menu {
+	list-style:none;
+	padding: 2px;
+	margin: 0;
+	display:block;
+	float: left;
+}
+.ui-menu .ui-menu {
+	margin-top: -3px;
+}
+.ui-menu .ui-menu-item {
+	margin:0;
+	padding: 0;
+	zoom: 1;
+	float: left;
+	clear: left;
+	width: 100%;
+}
+.ui-menu .ui-menu-item a {
+	text-decoration:none;
+	display:block;
+	padding:.2em .4em;
+	line-height:1.5;
+	zoom:1;
+}
+.ui-menu .ui-menu-item a.ui-state-hover,
+.ui-menu .ui-menu-item a.ui-state-active {
+	font-weight: normal;
+	margin: -1px;
+}
+/*
+ * jQuery UI Button 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Button#theming
+ */
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; } 
+button.ui-button-icons-only { width: 3.7em; } 
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4;  }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+/*
+ * jQuery UI Dialog 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog#theming
+ */
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative;  }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } 
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/*
+ * jQuery UI Slider 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Slider#theming
+ */
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: .5em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/*
+ * jQuery UI Tabs 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs#theming
+ */
+.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
+/*
+ * jQuery UI Datepicker 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker#theming
+ */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px;  }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month, 
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0;  }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+    display: none; /*sorry for IE5*/
+    display/**/: block; /*sorry for IE5*/
+    position: absolute; /*must have*/
+    z-index: -1; /*must have*/
+    filter: mask(); /*must have*/
+    top: -4px; /*must have*/
+    left: -4px; /*must have*/
+    width: 200px; /*must have*/
+    height: 200px; /*must have*/
+}/*
+ * jQuery UI Progressbar 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Progressbar#theming
+ */
+.ui-progressbar { height:2em; text-align: left; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
\ No newline at end of file
diff --git a/emperor/support_files/css/jquery-ui2.css b/emperor/support_files/css/jquery-ui2.css
new file mode 100644
index 0000000..4ed1911
--- /dev/null
+++ b/emperor/support_files/css/jquery-ui2.css
@@ -0,0 +1,567 @@
+/*
+ * jQuery UI CSS Framework 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+/*
+ * jQuery UI Accordion 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Accordion#theming
+ */
+/* IE/Win - Fix animation bug - #4615 */
+.ui-accordion { width: 100%; }
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
+.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
+.ui-accordion .ui-accordion-content-active { display: block; }
+/*
+ * jQuery UI Autocomplete 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete#theming
+ */
+.ui-autocomplete { position: absolute; cursor: default; }	
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+
+/*
+ * jQuery UI Menu 1.8.16
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Menu#theming
+ */
+.ui-menu {
+	list-style:none;
+	padding: 2px;
+	margin: 0;
+	display:block;
+	float: left;
+}
+.ui-menu .ui-menu {
+	margin-top: -3px;
+}
+.ui-menu .ui-menu-item {
+	margin:0;
+	padding: 0;
+	zoom: 1;
+	float: left;
+	clear: left;
+	width: 100%;
+}
+.ui-menu .ui-menu-item a {
+	text-decoration:none;
+	display:block;
+	padding:.2em .4em;
+	line-height:1.5;
+	zoom:1;
+}
+.ui-menu .ui-menu-item a.ui-state-hover,
+.ui-menu .ui-menu-item a.ui-state-active {
+	font-weight: normal;
+	margin: -1px;
+}
+/*
+ * jQuery UI Button 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Button#theming
+ */
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; } 
+button.ui-button-icons-only { width: 3.7em; } 
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4;  }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+/*
+ * jQuery UI Datepicker 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker#theming
+ */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px;  }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month, 
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0;  }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+    display: none; /*sorry for IE5*/
+    display/**/: block; /*sorry for IE5*/
+    position: absolute; /*must have*/
+    z-index: -1; /*must have*/
+    filter: mask(); /*must have*/
+    top: -4px; /*must have*/
+    left: -4px; /*must have*/
+    width: 200px; /*must have*/
+    height: 200px; /*must have*/
+}/*
+ * jQuery UI Dialog 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog#theming
+ */
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative;  }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } 
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/*
+ * jQuery UI Progressbar 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Progressbar#theming
+ */
+.ui-progressbar { height:2em; text-align: left; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }/*
+ * jQuery UI Resizable 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizable#theming
+ */
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block; }
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*
+ * jQuery UI Selectable 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Selectable#theming
+ */
+.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
+/*
+ * jQuery UI Slider 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Slider#theming
+ */
+.ui-slider { position: relative; text-align: left; margin-top: 1em; margin-bottom: 1em; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; }
+
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; background: #6cf;}
+.ui-slider-horizontal .ui-slider-range-max { right: 0; background-position: 0 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/*
+ * jQuery UI Tabs 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs#theming
+ */
+.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
+/*
+ * jQuery UI CSS Framework 1.8.16
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1.1em/*{fsDefault}*/; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa; background: #6cf;}
+.ui-widget-content a { color: #222222/*{fcContent}*/; }
+.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold;}
+.ui-widget-header a { color: #222222/*{fcHeader}*/; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555/*{fcDefault}*/; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999/*{borderColorHover}*/; background: #dadada/*{bgColorHover}*/ url(images/ui-bg_glass_75_dadada_1x400.png)/*{bgImgUrlHover}*/ 50%/*{bgHoverXPos}*/ 50%/*{bgHoverYPos}*/ repeat-x/*{bgHoverRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcHover}*/; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #212121/*{fcHover}*/; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa/*{borderColorActive}*/; background: #ffffff/*{bgColorActive}*/ url(images/ui-bg_glass_65_ffffff_1x400.png)/*{bgImgUrlActive}*/ 50%/*{bgActiveXPos}*/ 50%/*{bgActiveYPos}*/ repeat-x/*{bgActiveRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcActive}*/; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121/*{fcActive}*/; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight  {border: 1px solid #fcefa1/*{borderColorHighlight}*/; background: #fbf9ee/*{bgColorHighlight}*/ url(images/ui-bg_glass_55_fbf9ee_1x400.png)/*{bgImgUrlHighlight}*/ 50%/*{bgHighlightXPos}*/ 50%/*{bgHighlightYPos}*/ repeat-x/*{bgHighlightRepeat}*/; color: #363636/*{fcHighlight}*/; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636/*{fcHighlight}*/; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a/*{borderColorError}*/; background: #fef1ec/*{bgColorError}*/ url(images/ui-bg_glass_95_fef1ec_1x400.png)/*{bgImgUrlError}*/ 50%/*{bgErrorXPos}*/ 50%/*{bgErrorYPos}*/ repeat-x/*{bgErrorRepeat}*/; color: #cd0a0a/*{fcError}*/; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a/*{fcError}*/; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a/*{fcError}*/; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary,  .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsHeader}*/; }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png)/*{iconsDefault}*/; }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsHover}*/; }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsActive}*/; }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png)/*{iconsHighlight}*/; }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png)/*{iconsError}*/; }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; -khtml-border-top-left-radius: 4px/*{cornerRadius}*/; border-top-left-radius: 4px/*{cornerRadius}*/; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; -khtml-border-top-right-radius: 4px/*{cornerRadius}*/; border-top-right-radius: 4px/*{cornerRadius}*/; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; -khtml-border-bottom-left-radius: 4px/*{cornerRadius}*/; border-bottom-left-radius: 4px/*{cornerRadius}*/; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; -khtml-border-bottom-right-radius: 4px/*{cornerRadius}*/; border-bottom-right-radius: 4px/*{cornerRadius}*/; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa/*{bgColorOverlay}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlOverlay}*/ 50%/*{bgOverlayXPos}*/ 50%/*{bgOverlayYPos}*/ repeat-x/*{bgOverlayRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityOverlay}*/; }
+.ui-widget-shadow { margin: -8px/*{offsetTopShadow}*/ 0 0 -8px/*{offsetLeftShadow}*/; padding: 8px/*{thicknessShadow}*/; background: #aaaaaa/*{bgColorShadow}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlShadow}*/ 50%/*{bgShadowXPos}*/ 50%/*{bgShadowYPos}*/ repeat-x/*{bgShadowRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityShadow}*/; -moz-border-radius: 8px/*{cornerRadiusShadow}*/; -khtml-border-radius: 8px/*{cornerRadiusShadow}*/; -webkit-border-radius: 8px/*{cornerRad [...]
\ No newline at end of file
diff --git a/emperor/support_files/css/spectrum.css b/emperor/support_files/css/spectrum.css
new file mode 100755
index 0000000..20bc21d
--- /dev/null
+++ b/emperor/support_files/css/spectrum.css
@@ -0,0 +1,408 @@
+
+
+/***
+Spectrum: The No Hassle Colorpicker
+https://github.com/bgrins/spectrum
+
+Author: Brian Grinstead
+License: MIT
+***/
+
+.sp-container { 
+    position:absolute; 
+    top:0; 
+    left:0; 
+    display:inline-block;
+    *display: inline;
+    *zoom: 1;
+    z-index: 100;
+    overflow: hidden;
+}
+.sp-container.sp-flat {
+    position: relative;
+}
+
+/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
+.sp-top {
+  position:relative; 
+  width: 100%;
+  display:inline-block;
+}
+.sp-top-inner {
+   position:absolute; 
+   top:0; 
+   left:0; 
+   bottom:0; 
+   right:0;
+}
+.sp-color { 
+    position: absolute;
+    top:0;
+    left:0;
+    bottom:0;
+    right:20%;
+}
+.sp-hue {
+    position: absolute;
+    top:0;
+    right:0;
+    bottom:0;
+    left:84%;
+    height: 100%;
+}
+.sp-fill { 
+    padding-top: 80%;
+}
+.sp-sat, .sp-val { 
+    position: absolute; 
+    top:0; 
+    left:0; 
+    right:0; 
+    bottom:0; 
+}
+
+/* Don't allow text selection */
+.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider , .sp-container.sp-dragging .sp-input, .sp-container button  { 
+    -webkit-user-select:none; 
+    -moz-user-select: none; 
+    -o-user-select:none; 
+    user-select: none; 
+}
+
+.sp-container.sp-input-disabled .sp-input-container {
+    display: none;
+}
+.sp-container.sp-buttons-disabled .sp-button-container {
+    display: none;
+}
+.sp-palette-only .sp-picker-container {
+    display: none;
+}
+.sp-palette-disabled .sp-palette-container {
+    display: none;
+}
+
+.sp-initial-disabled .sp-initial { 
+    display: none; 
+}
+
+
+/* Gradients for hue, saturation and value instead of images.  Not pretty... but it works */
+.sp-sat {
+    background-image: -webkit-gradient(linear,  0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0)));
+    background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
+    background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
+    background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
+    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
+    filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
+}
+.sp-val {
+    background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0)));
+    background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
+    background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
+    background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
+    -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
+    filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
+}
+
+.sp-hue {
+    background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+    background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+    background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+    background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
+    background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
+}
+ 
+/* IE filters do not support multiple color stops.  
+   Generate 6 divs, line them up, and do two color gradients for each.
+   Yes, really.
+ */
+ 
+.sp-1 { 
+    height:17%; 
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
+}
+.sp-2 { 
+    height:16%; 
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
+}
+.sp-3 { 
+    height:17%; 
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
+}
+.sp-4 { 
+    height:17%; 
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
+}
+.sp-5 { 
+    height:16%; 
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
+}
+.sp-6 { 
+    height:17%; 
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
+}
+
+/* Clearfix hack */
+.sp-cf:before, .sp-cf:after { content: ""; display: table; }
+.sp-cf:after { clear: both; }
+.sp-cf { *zoom: 1; }
+
+/* Mobile devices, make hue slider bigger so it is easier to slide */
+ at media (max-device-width: 480px) {
+    .sp-color { right: 40%; }
+    .sp-hue { left: 63%; }
+    .sp-fill { padding-top: 60%; } 
+}
+
+.sp-dragger {
+   border-radius: 5px; 
+   height: 5px; 
+   width: 5px; 
+   border: 1px solid #fff;
+   background: #000;
+   cursor: pointer;
+   position:absolute; 
+   top:0; 
+   left: 0;
+}
+.sp-slider { 
+    position: absolute; 
+    top:0; 
+    cursor:pointer;
+    height: 3px; 
+    left: -1px;
+    right: -1px;
+    border: 1px solid #000;
+    background: white; 
+    opacity: .8; 
+}
+
+/* Basic display options (colors, fonts, global widths) */
+.sp-container {
+    border-radius: 0;
+    background-color: #ECECEC;
+    border: solid 1px #f0c49B;
+    padding: 0;
+}
+.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue 
+{
+    font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -ms-box-sizing: border-box;
+    box-sizing: border-box;
+}
+.sp-top 
+{
+    margin-bottom: 3px;
+}
+.sp-color, .sp-hue 
+{
+    border: solid 1px #666;
+}
+
+/* Input */
+.sp-input-container {
+    float:right;
+    width: 100px;
+    margin-bottom: 4px;
+}
+.sp-initial-disabled  .sp-input-container {
+    width: 100%;
+}
+.sp-input {
+   font-size: 12px !important;
+   border: 1px inset;
+   padding: 4px 5px;
+   margin: 0;
+   width: 100%;
+   background:transparent;
+   border-radius: 3px;
+   color: #222;
+}
+.sp-input:focus  {
+    border: 1px solid orange; 
+}
+.sp-input.sp-validation-error  
+{
+    border: 1px solid red;
+    background: #fdd;
+}
+.sp-picker-container , .sp-palette-container
+{
+    float:left;
+    position: relative;
+    padding: 10px;
+    padding-bottom: 300px;
+    margin-bottom: -290px;
+}
+.sp-picker-container 
+{
+    width: 172px;
+    border-left: solid 1px #fff;
+}
+
+/* Palettes */
+.sp-palette-container 
+{
+    border-right: solid 1px #ccc;
+}
+
+.sp-palette span {
+    display: block;
+    position:relative;
+    float:left;
+    width: 24px;
+    height: 15px; 
+    margin: 3px;
+    cursor: pointer;
+    border:solid 2px transparent;
+}
+.sp-palette span:hover, .sp-palette span.sp-thumb-active {
+    border-color: orange;
+}
+
+/* Initial */
+.sp-initial 
+{
+    float: left;
+    border: solid 1px #333;
+}
+.sp-initial span {
+    width: 30px;
+    height: 25px;
+    border:none;
+    display:block;
+    float:left;
+    margin:0;
+}
+
+/* Buttons */
+.sp-button-container {
+    float: right;
+}
+
+/* Replacer (the little preview div that shows up instead of the <input>) */
+.sp-replacer {
+    margin:0;
+    overflow:hidden;
+    cursor:pointer;
+    padding: 4px;
+    display:inline-block;
+    *zoom: 1;
+    *display: inline;
+    border: solid 1px #91765d;
+    background: #eee;
+    color: #333;
+    vertical-align: middle;
+}
+.sp-replacer:hover, .sp-replacer.sp-active {
+    border-color: #F0C49B;
+    color: #111;
+}
+.sp-dd { 
+    padding: 2px 0;
+    height: 16px; 
+    line-height: 16px;
+    float:left;
+    font-size:10px;
+}
+.sp-preview {
+    width:25px;
+    height: 20px;
+    border: solid 1px #222;
+    margin-right: 5px;
+    float:left;
+}
+
+.sp-palette  
+{
+    *width: 220px;
+    max-width: 220px;
+}
+.sp-palette span  
+{
+    width:16px; 
+    height: 16px; 
+    margin:2px 1px;
+    border: solid 1px #d0d0d0; 
+}
+
+.sp-container 
+{
+    padding-bottom:0;
+}
+
+
+/* Buttons: http://hellohappy.org/css3-buttons/ */
+.sp-container button {
+  background-color: #eeeeee;
+  background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
+  background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
+  background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
+  background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
+  background-image: linear-gradient(top, #eeeeee, #cccccc);
+  border: 1px solid #ccc;
+  border-bottom: 1px solid #bbb;
+  border-radius: 3px;
+  color: #333;
+  font-size: 14px;
+  line-height: 1;
+  padding: 5px 4px;
+  text-align: center;
+  text-shadow: 0 1px 0 #eee;
+  vertical-align: middle;
+}
+.sp-container button:hover {
+    background-color: #dddddd;
+    background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
+    background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
+    background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
+    background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
+    background-image: linear-gradient(top, #dddddd, #bbbbbb);
+    border: 1px solid #bbb;
+    border-bottom: 1px solid #999;
+    cursor: pointer;
+    text-shadow: 0 1px 0 #ddd; 
+}
+.sp-container button:active {
+    border: 1px solid #aaa;
+    border-bottom: 1px solid #888;
+    -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
+    -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
+    -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
+    -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
+    box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
+}
+.sp-cancel
+{
+    font-size: 11px;
+    color: #d93f3f !important;
+    margin:0;
+    padding:2px;
+    margin-right: 5px;
+    vertical-align: middle;
+    text-decoration:none;
+    
+}
+.sp-cancel:hover 
+{
+    color: #d93f3f !important;
+    text-decoration: underline;
+} 
+
+
+.sp-palette span:hover, .sp-palette span.sp-thumb-active 
+{
+    border-color: #000;
+}
+.sp-palette span.sp-thumb-light.sp-thumb-active 
+{
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
+}
+
+.sp-palette span.sp-thumb-dark.sp-thumb-active 
+{
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkA [...]
+}
diff --git a/emperor/support_files/emperor/css/emperor.css b/emperor/support_files/emperor/css/emperor.css
new file mode 100644
index 0000000..fff5837
--- /dev/null
+++ b/emperor/support_files/emperor/css/emperor.css
@@ -0,0 +1,284 @@
+    body, html {
+        font-size: 10px;
+        height: 100%;
+        width: 100%;
+        margin: 0px;
+        overflow: hidden;
+        float: left;
+    }
+    
+	#smalllogo {
+		width:150px;
+		height:45px;
+	}
+	
+    #overlay {
+    	visibility: hidden;
+    	position: absolute;
+    	left: 0px;
+    	top: 0px;
+    	width:100%;
+    	height:100%;
+    	font-size: 13px;
+    	text-align:center;
+    	z-index: 1000;
+	}
+    
+    #overlay div {
+    	width:325px;
+    	margin: 75px auto;
+    	background-color: #fff;
+    	border:1px solid #000;
+    	padding:15px;
+    	text-align:center;
+	}
+	
+	#explanation {
+		text-align:left;
+	}
+	
+	#source {
+		font-size:11px;
+		text-align:left;
+	}
+      
+    div.plotWrapper {
+        position: relative;
+        overflow:hidden;
+        height: 100%;
+        width:73%;
+        float: left;
+        margin:0;
+    }
+      
+    #plotToggle {
+        z-index: 5;
+        width: 73%;
+        margin:0;
+        overflow:hidden;
+        bottom: 0;
+        position: absolute;
+        float: left;
+    }
+
+    div.separator {
+        height:100%;
+        width:1%;
+        float:left;
+        cursor:col-resize;
+        margin:0;
+        background-color:#333333;
+    }
+    div.separator:hover {
+    	background-color:#666666;
+    }
+
+    div#menu {
+        height: 100%;
+        font-family: Helvetica, sans-serif;
+        width:26%;
+        overflow:hidden;
+        float:left;
+        padding: 0px;
+        margin:0;
+    }
+    
+    div#menutabs {
+        background: #eee;
+        /*margin-before: 0px !important;*/
+        height: 100%;
+        margin:0;
+    }
+    
+    .ui-tabs .ui-widget .ui-widget-content .ui-corner-all {
+        /*  height: 95%;*/
+        padding-left: 0px !important;
+        padding-top: 0px;
+    }
+    div#colorby {
+        padding-top: 0px;
+        padding-bottom: 0px;
+        width:100%;
+        height:100%;
+        overflow:scroll;
+    }
+    
+    div#keytab {
+        height: 94%;
+        width:90%;
+        padding-top: 0px;
+        padding-bottom: 0px;
+    }
+    
+    div#key {
+        height: 85%;
+        overflow: auto;
+    }
+    
+    table {
+        font-family: Helvetica, sans-serif;
+        font-size: 12px !important;
+    }
+    
+    select {
+        /*  height: 20%;*/
+        /*width: 100%;*/
+    }
+    
+    div.colorbox {
+        border: 1px solid black;
+        height: 18px;
+        width: 18px;
+    }
+    
+    div.animationlist {
+        margin-top: 1em;
+        width: 100%;
+        overflow: auto;
+        height: 60%;
+    }
+    
+    div.list{
+        width: 100%;
+        height: 85%;
+        overflow: auto;
+    }
+    
+    div.list ul {
+        width: 100%;
+        padding-left: 0px !important;
+        list-style-type: none;
+        font-size: 12px !important;
+    }
+    
+    input.button {
+        height: 2em;
+        width: 100%;
+    }
+    
+    label {
+        border:0; 
+        font: 9pt Helvetica;
+}
+    
+    label.slidervalue {
+        float: right;
+        border:0; 
+        color:#6cf; 
+        font-weight:bold;
+        font: 9pt Helvetica;
+    }
+
+    .ui-tabs .ui-tabs-nav li a{
+        padding: 2px 5px !important;
+    }
+    
+    div#labelby {
+        height: 90%;
+    }
+    
+    div#labellist {
+        height: 60%;
+    }
+    
+    div.labelsTop {
+        height: 30%;
+    }
+    
+    div#labels {
+        display: none;
+        font-family: courier;
+        color: #fff;
+        height: 0px;
+        float: left;
+        position: absolute;
+        z-index: 1;
+    }
+    
+    div#taxalabels {
+        display: none;
+        font-family: courier;
+        color: #fff;
+        height: 0px;
+        float: left;
+        position: absolute;
+        z-index: 1;
+    }
+    
+    .axislabels {
+        color: #fff;
+    }
+    
+    div#labelColorHolder {
+        width: 100%;
+    }
+    
+    div#labelColorHolder div {
+        float: left;
+    }
+    
+    div#labelColorHolder label{
+        float: left;
+        width: 50%;
+    }
+    
+    form {
+        margin: 0px;
+    }
+    
+    input {
+      font-style: 10pt helvetica;
+    }
+    
+    a.mediabutton img {
+        background-color: #eee;
+    }
+    
+    a.mediabutton:hover img{
+        background-color: #cff;
+    }
+    
+    *.unselectable {
+        -moz-user-select: -moz-none;
+        -khtml-user-select: none;
+        -webkit-user-select: none;
+
+        /*
+            Introduced in IE 10.
+            See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/
+        */
+        -ms-user-select: none;
+        user-select: none;
+    }
+    
+    .ontop {
+        padding: 2px;
+        font-style: 9pt helvetica;
+        color: #fff;
+        border: 1px solid white;
+        position: absolute;
+    }
+    
+    .arrow-right {
+        opacity: 0;
+        position: absolute;
+        width: 0;
+        height: 0;
+        border-top: 5px solid transparent;
+        border-bottom: 5px solid transparent;
+        border-left: 10px solid white;
+    }
+    
+    .circle {
+        position: absolute;
+        opacity: 0;
+        border: 3px solid white;
+        border-radius: 50%;
+        width: 10px;
+        height: 10px; 
+        /* width and height can be anything, as long as they're equal */
+    }
+
+    .invisible {
+        display: none;
+    }
diff --git a/emperor/support_files/emperor/js/emperor.js b/emperor/support_files/emperor/js/emperor.js
new file mode 100644
index 0000000..cf24422
--- /dev/null
+++ b/emperor/support_files/emperor/js/emperor.js
@@ -0,0 +1,2430 @@
+/*
+ * __author__ = "Meg Pirrung"
+ * __copyright__ = "Copyright 2013, Emperor"
+ * __credits__ = ["Meg Pirrung","Antonio Gonzalez Pena","Yoshiki Vazquez Baeza",
+ *                "Jackson Chen", "Emily TerAvest"]
+ * __license__ = "BSD"
+ * __version__ = "0.9.3"
+ * __maintainer__ = "Meg Pirrung"
+ * __email__ = "meganap at gmail.com"
+ * __status__ = "Release"
+ */
+
+// spheres and ellipses that are being displayed on screen
+var g_plotSpheres = {};
+var g_plotEllipses = {};
+var g_plotTaxa = {};
+var g_plotVectors = {};
+var g_plotEdges = {};
+var g_parallelPlots = []
+
+// sample identifiers of all items that are plotted
+var g_plotIds = [];
+
+// line objects used to represent the axes
+var g_xAxisLine;
+var g_yAxisLine;
+var g_zAxisLine;
+var g_viewingAxes = [0, 1, 2];
+
+// scene elements for the webgl plot
+var g_mainScene;
+var g_sceneCamera;
+var g_sceneLight;
+var g_mainRenderer;
+var g_sceneControl;
+
+// general multipurpose variables
+var g_elementsGroup; // group that holds the plotted shapes
+var g_categoryIndex = 0; // current coloring category index
+var g_genericSphere; // generic sphere used for plots
+var g_categoryName = ""; // current coloring category
+var g_foundId = ""; // id of currently located point
+var g_time;
+var g_visiblePoints = 0;
+var g_sphereScaler = 1.0;
+var g_keyBuilt = false;
+var g_useDiscreteColors = true;
+var g_screenshotBind;
+var g_separator_left;
+var g_separator_history;
+
+// valid ascii codes for filename
+var g_validAsciiCodes = new Array();
+// adding: .-_
+g_validAsciiCodes = g_validAsciiCodes.concat([45,46,95]);
+// adding: 0->9
+g_validAsciiCodes = g_validAsciiCodes.concat([48,49,50,51,52,53,54,55,56,57]);
+// adding: A->Z
+g_validAsciiCodes = g_validAsciiCodes.concat([65,66,67,68,68,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90]);
+// adding: a->z
+g_validAsciiCodes = g_validAsciiCodes.concat([97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122]);
+
+// taken from the qiime/colors.py module; a total of 29 colors
+var k_QIIME_COLORS = [
+"0xFF0000", // red1
+"0x0000FF", // blue1
+"0xF27304", // orange1
+"0x008000", // green
+"0x91278D", // purple1
+"0xFFFF00", // yellow1
+"0x7CECF4", // cyan1
+"0xF49AC2", // pink1
+"0x5DA09E", // teal1
+"0x6B440B", // brown1
+"0x808080", // gray1
+"0xF79679", // red2
+"0x7DA9D8", // blue2
+"0xFCC688", // orange2
+"0x80C99B", // green2
+"0xA287BF", // purple2
+"0xFFF899", // yellow2
+"0xC49C6B", // brown2
+"0xC0C0C0", // gray2
+"0xED008A", // red3
+"0x00B6FF", // blue3
+"0xA54700", // orange3
+"0x808000", // green3
+"0x008080"] // teal3
+
+// Taken from http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric
+function isNumeric(n) {
+  return !isNaN(parseFloat(n)) && isFinite(n);
+}
+
+/*This function recenter the camera to the initial position it had*/
+function resetCamera() {
+	// We need to reset the camera controls first before modifying the values of the camera (this is the reset view!)
+	g_sceneControl.reset();
+	
+	g_sceneCamera.aspect = document.getElementById('pcoaPlotWrapper').offsetWidth/document.getElementById('pcoaPlotWrapper').offsetHeight;
+	g_sceneCamera.rotation.set( 0, 0, 0);
+	g_sceneCamera.updateProjectionMatrix();
+	
+	if ($('#scale_checkbox').is(':checked'))
+		g_sceneCamera.position.set(0 , 0, (g_maximum*4.2) + g_radius);
+	else
+		g_sceneCamera.position.set(0 , 0, (g_maximum*4.8) + g_radius);	
+}
+
+/*Removes duplicates from a list of samples*/
+function dedupe(list) {
+	var set = {};
+	for (var i = 0; i < list.length; i++){
+		set[list[i]] = true;
+	}
+	list = [];
+	for (var obj in set){
+		list.push(obj);
+	}
+	return list;
+}
+
+/*Toggle between the scaled and unscaled version of the plot
+
+  This function will change multiple elements in the plot, as described by the
+  percentage explained in each of the PC axes.
+
+  Note that this function will also change the position of the camera, light and
+  adds a scaling value to the sphere size slider to make the size consistent
+  between scaled and unscaled versions of the plot.
+*/
+function toggleScaleCoordinates(element) {
+	var axesLen;
+	var operation;
+
+	// used only for vector and edges re-drawing
+	var currentPosition = [], currentColor = 0x000000;
+
+	if (!isNumeric(g_fractionExplained[g_viewingAxes[0]])) {
+		alert("PC" + (g_viewingAxes[0]+1) + " is too small for this feature, change your selection.");
+		return;
+	} else if (!isNumeric(g_fractionExplained[g_viewingAxes[1]])) {
+		alert("PC" + (g_viewingAxes[1]+1) + " is too small for this feature, change your selection.");
+		return;
+	} else if (!isNumeric(g_fractionExplained[g_viewingAxes[2]])) {
+		alert("PC" + (g_viewingAxes[2]+1) + " is too small for this feature, change your selection.");
+		return;
+	}
+
+	// XOR operation for the checkbox widget, this will select an operation
+	// to perform over various properties, either a multiplication or a division
+	if(element.checked == true){
+		operation = function(a, b){return a*b};
+		g_sphereScaler = g_fractionExplained[g_viewingAxes[0]];
+	}
+	else{
+		operation = function(a, b){return a/b};
+		g_sphereScaler = 1;
+	}
+
+	// force an update of the size of the spheres
+	$("#sradiusslider").slider("value",$("#sradiusslider").slider("value"));
+
+	// scale other properties
+	g_xMaximumValue = operation(g_xMaximumValue,g_fractionExplained[g_viewingAxes[0]]);
+	g_yMaximumValue = operation(g_yMaximumValue,g_fractionExplained[g_viewingAxes[1]]);
+	g_zMaximumValue = operation(g_zMaximumValue,g_fractionExplained[g_viewingAxes[2]]);
+	g_xMinimumValue = operation(g_xMinimumValue,g_fractionExplained[g_viewingAxes[0]]);
+	g_yMinimumValue = operation(g_yMinimumValue,g_fractionExplained[g_viewingAxes[1]]);
+	g_zMinimumValue = operation(g_zMinimumValue,g_fractionExplained[g_viewingAxes[2]]);
+	g_maximum = operation(g_maximum, g_fractionExplained[g_viewingAxes[0]])
+
+	// scale the position of the light
+	g_sceneLight.position.set(
+		operation(g_sceneLight.position.x, g_fractionExplained[g_viewingAxes[0]]),
+		operation(g_sceneLight.position.y, g_fractionExplained[g_viewingAxes[0]]),
+		operation(g_sceneLight.position.z, g_fractionExplained[g_viewingAxes[0]]));
+
+	// scale the position of the camera according to pc1
+	g_sceneCamera.position.set(
+		operation(g_sceneCamera.position.x, g_fractionExplained[g_viewingAxes[0]]),
+		operation(g_sceneCamera.position.y, g_fractionExplained[g_viewingAxes[0]]),
+		operation(g_sceneCamera.position.z, g_fractionExplained[g_viewingAxes[0]]))
+	// scale the axis lines
+	drawAxisLines();
+
+	// set the new position of each of the sphere objects
+	for (sample_id in g_plotSpheres){
+		// scale the position of the spheres
+		g_plotSpheres[sample_id].position.set(
+			operation(g_plotSpheres[sample_id].position.x,g_fractionExplained[g_viewingAxes[0]]),
+			operation(g_plotSpheres[sample_id].position.y,g_fractionExplained[g_viewingAxes[1]]),
+			operation(g_plotSpheres[sample_id].position.z,g_fractionExplained[g_viewingAxes[2]]));
+	}
+
+	// ellipses won't always be available hence the two separate loops
+	for (sample_id in g_plotEllipses){
+		// scale the dimensions of the positions of each ellipse
+		g_plotEllipses[sample_id].position.set(
+			operation(g_plotEllipses[sample_id].position.x, g_fractionExplained[g_viewingAxes[0]]),
+			operation(g_plotEllipses[sample_id].position.y, g_fractionExplained[g_viewingAxes[1]]),
+			operation(g_plotEllipses[sample_id].position.z, g_fractionExplained[g_viewingAxes[2]]));
+
+		// scale the dimensions of the ellipse
+		g_plotEllipses[sample_id].scale.set(
+			operation(g_plotEllipses[sample_id].scale.x, g_fractionExplained[g_viewingAxes[0]]),
+			operation(g_plotEllipses[sample_id].scale.y, g_fractionExplained[g_viewingAxes[1]]),
+			operation(g_plotEllipses[sample_id].scale.z, g_fractionExplained[g_viewingAxes[2]]));
+	}
+
+	for (index in g_plotTaxa){
+		//scale the dimensions of the positions of each taxa-sphere
+		g_plotTaxa[index].position.set(
+			operation(g_plotTaxa[index].position.x, g_fractionExplained[g_viewingAxes[0]]),
+			operation(g_plotTaxa[index].position.y, g_fractionExplained[g_viewingAxes[1]]),
+			operation(g_plotTaxa[index].position.z, g_fractionExplained[g_viewingAxes[2]]));
+
+		//scale the dimensions of each taxa-sphere
+		g_plotTaxa[index].scale.set(
+			operation(g_plotTaxa[index].scale.x, g_fractionExplained[g_viewingAxes[0]]),
+			operation(g_plotTaxa[index].scale.y, g_fractionExplained[g_viewingAxes[0]]),
+			operation(g_plotTaxa[index].scale.z, g_fractionExplained[g_viewingAxes[0]]));
+	}
+
+	// each line is indexed by a sample, creating in turn TOTAL_SAMPLES-1 lines
+	for (sample_id in g_plotVectors){
+
+		// the color has to be formatted as an hex number for makeLine to work
+		currentColor = g_plotVectors[sample_id].material.color.getHex();
+
+		// updating the position of a vertex in a line is a really expensive
+		// operation, hence we just remove it from the group and create it again
+		g_elementsGroup.remove(g_plotVectors[sample_id]);
+
+		for (vertex in g_plotVectors[sample_id].geometry.vertices){
+			currentPosition[vertex] = g_plotVectors[sample_id].geometry.vertices[vertex];
+
+			// scale the position of each of the vertices
+			currentPosition[vertex].x = operation(currentPosition[vertex].x,
+				g_fractionExplained[g_viewingAxes[0]])
+			currentPosition[vertex].y = operation(currentPosition[vertex].y,
+				g_fractionExplained[g_viewingAxes[1]])
+			currentPosition[vertex].z = operation(currentPosition[vertex].z,
+				g_fractionExplained[g_viewingAxes[2]])
+
+			// create an array we can pass to makeLine
+			currentPosition[vertex] = [currentPosition[vertex].x,
+				currentPosition[vertex].y, currentPosition[vertex].z]
+		}
+
+		// add the element to the main vector array and to the group
+		g_plotVectors[sample_id] = makeLine(currentPosition[0],
+			currentPosition[1], currentColor, 2);
+		g_elementsGroup.add(g_plotVectors[sample_id]);
+	}
+
+	// support scaling of edges in plot comparisons
+	for(var sample_id in g_plotEdges){
+		// each edge is composed of two separate lines
+		for(var section in g_plotEdges[sample_id]){
+
+			// the color has to be formatted as an hex number for makeLine to work
+			currentColor = g_plotEdges[sample_id][section].material.color.getHex();
+
+			// remove them completely from the group and scene we no longer need
+			// these objects as re-creating them is as expensive as modifying
+			// most of their features
+			g_elementsGroup.remove(g_plotEdges[sample_id][section])
+			g_mainScene.remove(g_plotEdges[sample_id][section])
+
+			for (vertex in g_plotEdges[sample_id][section].geometry.vertices){
+				currentPosition[vertex] = g_plotEdges[sample_id][section].geometry.vertices[vertex];
+
+				// scale the position of each of the vertices
+				currentPosition[vertex].x = operation(currentPosition[vertex].x,
+					g_fractionExplained[g_viewingAxes[0]])
+				currentPosition[vertex].y = operation(currentPosition[vertex].y,
+					g_fractionExplained[g_viewingAxes[1]])
+				currentPosition[vertex].z = operation(currentPosition[vertex].z,
+					g_fractionExplained[g_viewingAxes[2]])
+
+				// create an array we can pass to makeLine
+				currentPosition[vertex] = [currentPosition[vertex].x,
+					currentPosition[vertex].y, currentPosition[vertex].z]
+			}
+
+			// add the element to the main vector array and to the group
+			g_plotEdges[sample_id][section] = makeLine(currentPosition[0],
+				currentPosition[1], currentColor, 2);
+			g_elementsGroup.add(g_plotEdges[sample_id][section]);
+			g_mainScene.add(g_plotEdges[sample_id][section]);
+
+		}
+	}
+}
+
+/* Toggle between discrete and continuous coloring for samples and labels */
+function toggleContinuousAndDiscreteColors(element){
+	g_useDiscreteColors = !element.checked;
+
+	// re-coloring the samples and labels now will use the appropriate coloring
+	colorByMenuChanged();
+	labelMenuChanged();
+}
+
+/*Generate a list of colors that corresponds to all the samples in the plot
+
+  This function will generate a list of coloring values depending on the
+  coloring scheme that the system is currently using (discrete or continuous).
+*/
+function getColorList(vals) {
+	var colors = {};
+
+	// cases with one or two categories are basically the same no matter if the
+	// coloring scheme is continuous or discrete; choose red or red and blue
+	if(vals.length == 1){
+		colors[vals[0]] = new THREE.Color();
+		colors[vals[0]].setHex("0xff0000");
+	}
+	else if (vals.length == 2) {
+		colors[vals[0]] = new THREE.Color();
+		colors[vals[0]].setHex("0xff0000");
+		colors[vals[1]] = new THREE.Color();
+		colors[vals[1]].setHex("0x0000ff");
+	}
+	else {
+		var numColors = vals.length;
+		for(var index in vals){
+			colors[vals[index]] = new THREE.Color();
+			if(g_useDiscreteColors){
+				// get the next available color
+				colors[vals[index]].setHex(getDiscreteColor(index)*1);
+			}
+			else{
+				//reverse the oder to standard default B->G->R
+				//changed what is multiplied by 0.66 to be 2,1,0 from 0,1,2
+				THREE.ColorConverter.setHSV(colors[vals[index]], 
+					   (numColors - index -1 )*.66/numColors, 1, 1);
+			}
+		}
+	}
+	return colors;
+}
+
+/* Retrieve one of the discrete colors from the list
+
+  This function will return the color at the requested index, if this value
+  value is greater than the number of colors available, the function will just
+  rollover and retrieve the next available color.
+*/
+function getDiscreteColor(index){
+	var size = k_QIIME_COLORS.length;
+	if(index >= size){
+		index = index - (Math.floor(index/size)*size)
+	}
+
+	return k_QIIME_COLORS[index]
+}
+
+/*Start timer (for debugging)*/
+function startTimer() {
+	var d=new Date()
+	g_time = d.getTime();
+}
+
+/*End timer (for debugging)*/
+function stopTimer(info) {
+	var d=new Date()
+	g_time = d.getTime() - g_time;
+	console.log("time to " +info +":"+g_time+"ms")
+}
+
+/* Sorting function that deals with alpha and numeric elements
+
+  This function takes a list of strings, divides it into two new lists, one
+  that's alpha-only and one that's numeric only. The resulting list will have
+  sorted all alpha elements at the beginning & all numeric elements at the end.
+ */
+function _splitAndSortNumericAndAlpha(list){
+	var numericPart = [], alphaPart = [], result = [];
+
+	// separate the numeric and the alpha elements of the array
+	for(var index = 0; index < list.length; index++){
+		if(isNaN(parseFloat(list[index]))){
+			alphaPart.push(list[index])
+		}
+		else{
+			numericPart.push(list[index])
+		}
+	}
+
+	// sort each of the two parts, numeric part is ascending order
+	alphaPart.sort();
+	numericPart.sort(function(a,b){return parseFloat(a)-parseFloat(b)})
+
+	return result.concat(alphaPart, numericPart);
+}
+
+/*This function is called when a new value is selected in the colorBy menu */
+function colorByMenuChanged() {
+	// set the new current category and index
+	g_categoryName = document.getElementById('colorbycombo')[document.getElementById('colorbycombo').selectedIndex].value;
+	g_categoryIndex = g_mappingFileHeaders.indexOf(g_categoryName);
+
+	// get all values of this category from the mapping
+	var vals = [];
+	for(var i in g_plotIds){
+		vals.push(g_mappingFileData[g_plotIds[i]][g_categoryIndex]);
+	}
+
+	vals = _splitAndSortNumericAndAlpha(dedupe(vals));
+	colors = getColorList(vals);
+	
+	// build the colorby table in HTML
+	var lines = "<table id='colorbylist_table'>";
+	for(var i in vals){
+		// each field is identified by the value it has in the deduplicated
+		// list of values and by the number of the column in the mapping file
+		// if this is done otherwise, weird characters have to be extemped etc.
+		var idString = "r"+i+"c"+g_categoryIndex;
+
+		// set the div id so that we can reference this div later
+		lines += "<tr><td><div id=\""+idString+"\"class=\"colorbox\" name=\""+vals[i]+"\"></div></td><td title=\""+vals[i]+"\">";
+
+		if(vals[i].length > 25){
+			lines+= vals[i].substring(0,25) + "..."
+		}
+		else{
+			lines += vals[i];
+		}
+
+		lines+= "</td></tr>";
+	}
+	lines += "</table>";
+	document.getElementById("colorbylist").innerHTML = lines;
+
+	for(var i in vals){
+		// each field is identified by the value it has in the deduplicated
+		// list of values and by the number of the column in the mapping
+		var idString = "r"+i+"c"+g_categoryIndex;
+
+		// get the div built earlier and turn it into a color picker
+		$('#'+idString).css('backgroundColor',"#"+colors[vals[i]].getHexString());
+		$("#"+idString).spectrum({
+			localStorageKey: 'key',
+			color: colors[vals[i]].getHexString(),
+			showInitial: true,
+			showInput: true,
+			preferredFormat: "hex6",
+			change:
+				function(color) {
+					$(this).css('backgroundColor', color.toHexString());
+					var c = color.toHexString();
+					if(c.length == 4){
+						c = "#"+c.charAt(1)+c.charAt(1)+c.charAt(2)+c.charAt(2)+c.charAt(3)+c.charAt(3);
+					}
+					colorChanged($(this).attr('name'), c);
+					colors[$(this).attr('name')] = c;
+					colorParallelPlots(vals, colors);
+				}
+		});
+	}
+
+	colorParallelPlots(vals, colors);
+	setKey(vals, colors);
+}
+
+function colorParallelPlots(vals,colors) 
+{
+	pwidth = document.getElementById('parallelPlotWrapper').offsetWidth
+	pheight = document.getElementById('parallelPlotWrapper').offsetHeight
+	
+	document.getElementById('parallelPlotWrapper').innerHTML = '<div id="parallelPlot" class="parcoords" style="width:'+pwidth+'px;height:'+pheight+'px"></div>'
+	
+	var color = function(d) {
+		var colorKey = "";
+		for (var i = 1; i < Object.keys(d).length+1; i++) {
+			colorKey += String(d[i]);
+		}
+		var catValue = color_map[colorKey];
+		var catColor = colors[catValue];
+
+		try {
+			var hex = '#'+catColor.getHexString();
+		}catch(TypeError) {
+			var hex = catColor;
+		}
+		return hex;
+	}
+
+	var num_axes = g_fractionExplained.length-g_number_of_custom_axes;
+	var data2 = new Array();
+	var color_map = {};
+	for (sid in g_spherePositions) {
+		var a_map = {};
+		var key = "";
+		var value = g_mappingFileData[sid][g_categoryIndex];
+		for (var i = 1; i < num_axes+1; i++) {
+			a_map[i] = g_spherePositions[sid]['P'+i];
+			key += String(a_map[i]);
+		}
+		color_map[key] = value;
+		data2.push(a_map);
+	}
+
+	var pc = d3.parcoords()("#parallelPlot");
+	pc
+	  .data(data2)
+	  .color(color)
+	  .margin({ top: 40, left: 50, bottom: 40, right: 0 })
+	  .mode("queue")
+	  .render()
+	  .brushable();
+	  
+	$('.parcoords text').css('stroke', $('#axeslabelscolor').css('backgroundColor'));
+	$('.parcoords .axis line, .parcoords .axis path').css('stroke', $('#axescolor').css('backgroundColor'));
+}
+
+/*Callback when the scaling by drop-down menu changes
+
+  This function will create one slider and a label for each category.
+*/
+function scalingByMenuChanged(){
+	var scalingByCategoryName = document.getElementById('scalingbycombo')[document.getElementById('scalingbycombo').selectedIndex].value;
+	var scalingByCategoryIndex = g_mappingFileHeaders.indexOf(scalingByCategoryName);
+	var values = [], lines, idString;
+
+	// get all values of this category from the mapping
+	for(var i in g_plotIds){
+		values.push(g_mappingFileData[g_plotIds[i]][scalingByCategoryIndex]);
+	}
+	values = _splitAndSortNumericAndAlpha(dedupe(values));
+
+	// the padding accounts for the slider handle that can move all to the left or right
+	lines = '<table width="100%" height="100%" style="padding-right:10px;padding-left:10px;">'
+	for(var i in values){
+		// construct a sanitized category name for all HTML elements
+		idString = "r"+i+"c"+scalingByCategoryIndex;
+
+		lines += "<tr><td>";
+		// add a label with the name of the category
+		lines +=" <label for=\""+values[i]+"\" class=\"text\">"
+		// do not make the category name too long
+		if(values[i].length > 25){
+			lines+= values[i].substring(0,25) + "..."
+		}
+		else{
+			lines += values[i];
+		}
+		lines +="</label>"
+
+		// // add a slider and a current-value-label to the table
+		lines += "<label id=\""+idString+"scalingvalue\" name=\""+values[i]+"\" class=\"slidervalue\"></label>"
+		lines += "<div id=\""+idString+"scalingslider\" name=\""+values[i]+"\" class=\"slider-range-max\"></div>"
+		lines +="</td></tr>";
+	}
+	lines += "</table>";
+	document.getElementById("scalingbylist").innerHTML = lines;
+
+	// set all sliders to the default value of 5, that's reflected as no scaling
+	for(var i in values){
+		var idString = "r"+i+"c"+scalingByCategoryIndex;
+		$("#"+idString+"scalingslider").slider({
+			range: "max",
+			min: 1,
+			max: 20,
+			value: 5,
+			slide: function( event, ui ) {
+				sphereRadiusChange(ui, $(this).attr('name'));
+			},
+			change: function( event, ui ) {
+				sphereRadiusChange(ui, $(this).attr('name'));
+			}
+		});
+		document.getElementById(idString+"scalingvalue").innerHTML = $("#"+idString+"scalingslider").slider("value")/5;
+	}
+}
+
+
+/*This function is called when a new value is selected in the showBy menu*/
+function showByMenuChanged() {
+	g_categoryName = document.getElementById('showbycombo')[document.getElementById('showbycombo').selectedIndex].value;
+	var showByMenuIndex = g_mappingFileHeaders.indexOf(g_categoryName);
+	var vals = [];
+
+	for(var i in g_plotIds){
+		var sid = g_plotIds[i];
+		var divid = sid.replace(/\./g,'');
+		// get all of the values for the selected category
+		vals.push(g_mappingFileData[sid][showByMenuIndex]);
+		// set everything to visible
+		try {
+			g_elementsGroup.add(g_plotEllipses[sid])
+		}
+		catch(TypeError){}
+		try {
+			g_elementsGroup.add(g_plotSpheres[sid])
+		}
+		catch(TypeError){}
+		try {
+			g_elementsGroup.add(g_plotVectors[sid])
+		}
+		catch(TypeError){}
+		$('#'+divid+"_label").css('display','block');
+	}
+
+	g_visiblePoints = g_plotIds.length;
+	changePointCount();
+
+	vals = _splitAndSortNumericAndAlpha(dedupe(vals));
+
+	// build the showby checkbox table in HTML; the padding to the right makes
+	// the slider fit great inside the table without ever showing scroll bars
+	var lines = '<form name="showbyform"><table height="100%" width="100%" style="padding-right:10px;padding-left:10px;">'
+
+	for(var i in vals){
+		// tag each slider & percent label with the idString to avoid conflicts
+		var idString = "r"+i+"c"+showByMenuIndex;
+
+		// make the size of the checkmark fixed so proportions don't look weird
+		lines += "<tr><td width=\"10px\">";
+		lines +="<input name=\""+vals[i]+"_show\" value=\""+vals[i]+"\" type=\"checkbox\" checked=\"yes\" onClick=\"toggleVisible(\'"+vals[i]+"\')\">";
+		lines +="</input></td><td title=\""+vals[i]+"\">";
+		if(vals[i].length > 25){
+			lines+= vals[i].substring(0,25) + "..."
+		}
+		else{
+			lines += vals[i];
+		}
+
+		// add a slider and a current-value-label to the table
+		lines +="</td></tr>";
+		lines += '<td colspan="2">';
+		lines += "<label id=\""+idString+"opacityvalue\" name=\""+vals[i]+"\" class=\"slidervalue\"></label>"
+		lines += "<div id=\""+idString+"opacityslider\" name=\""+vals[i]+"\" class=\"slider-range-max\"></div>"
+		lines +="</td></tr>";
+	}
+	lines += "</table></form>";
+	document.getElementById("showbylist").innerHTML = lines;
+
+	// set all the sliders to 100 % and to respond to the sphereOpacityChange
+	// with the name of the category they have in the mapping file
+	for(var i in vals){
+		var idString = "r"+i+"c"+showByMenuIndex;
+		$("#"+idString+"opacityslider").slider({
+			range: "max",
+			min: 0,
+			max: 100,
+			value: 100,
+			slide: function( event, ui ) {
+				sphereOpacityChange(ui, $(this).attr('name'));
+			},
+			change: function( event, ui ) {
+				sphereOpacityChange(ui, $(this).attr('name'));
+			}
+		});
+		document.getElementById(idString+"opacityvalue").innerHTML = $("#"+idString+"opacityslider").slider("value")+"%";
+	}
+
+	// change the value of the general opacity for all the spheres, this action
+	// has to take place after the creation of the sliders for all categories;
+	// that is the for loop right befor this statement, as this will produce
+	// a callback that will require to change all the sliders in available
+	$("#sopacityslider").slider("value", 100)
+}
+
+/*Toggle plot items by category selected in showby menu*/
+function toggleVisible(value) {
+
+	var hidden = !document.showbyform.elements[value+'_show'].checked;
+	g_categoryName = document.getElementById('showbycombo')[document.getElementById('showbycombo').selectedIndex].value;
+
+	//change visibility of points depending on metadata category
+	for(var i in g_plotIds){
+	var sid = g_plotIds[i];
+	var divid = sid.replace(/\./g,'');
+	var mappingVal = g_mappingFileData[sid][g_mappingFileHeaders.indexOf(g_categoryName)]
+		if(mappingVal == value && hidden){
+			try{
+				g_elementsGroup.remove(g_plotEllipses[sid]);
+			}
+			catch(TypeError){}
+			try{
+				g_elementsGroup.remove(g_plotSpheres[sid]);
+				g_visiblePoints--
+			}
+			catch(TypeError){}
+			try{
+				g_elementsGroup.remove(g_plotVectors[sid]);
+			}
+			catch(TypeError){}
+			$('#'+divid+"_label").css('display','none');
+		}
+		else if(mappingVal == value && !hidden)
+		{
+			try {
+				g_elementsGroup.add(g_plotEllipses[sid]);
+			}
+			catch(TypeError){}
+			try {
+				g_elementsGroup.add(g_plotSpheres[sid]);
+				g_visiblePoints++;
+			}
+			catch(TypeError){}
+			try{
+				g_elementsGroup.add(g_plotVectors[sid]);
+			}
+			catch(TypeError){}
+			$('#'+divid+"_label").css('display','block');
+		}
+	}
+	changePointCount()
+
+}
+
+/*Build the plot legend in HTML*/
+function setKey(values, colors) {
+	if(g_keyBuilt){
+		for(var i = 0; i < values.length; i++){
+			colorChanged(values[i], '#'+colors[values[i]].getHexString());
+		}
+	}
+	else {
+		var keyHTML = "<table class=\"key\">";
+		for(var i in g_plotIds){
+			var sid = g_plotIds[i];
+			var divid = sid.replace(/\./g,'')+"_key";
+			var catValue = g_mappingFileData[sid][g_categoryIndex];
+			var catColor = colors[catValue];
+			keyHTML += "<tr id=\""+divid+"row\"><td><div id=\""+divid+"\" name=\""+sid+"\" class=\"colorbox\" style=\"background-color:#";
+			keyHTML += catColor.getHexString();
+			keyHTML += ";\"></div>";
+			keyHTML +="</td><td>";
+			keyHTML += sid;
+			keyHTML += "</td></tr>";
+
+			try {
+				g_plotEllipses[g_plotIds[i]].material.color.setHex("0x"+catColor.getHexString());
+			}
+			catch(TypeError){}
+			try {
+				g_plotSpheres[g_plotIds[i]].material.color.setHex("0x"+catColor.getHexString());
+			}
+			catch(TypeError){}
+			try {
+				g_plotVectors[g_plotIds[i]].material.color.setHex("0x"+catColor.getHexString());
+			}
+			catch(TypeError){}
+		}
+		keyHTML += "</table>";
+		document.getElementById("key").innerHTML = keyHTML;
+
+		for(var i in g_plotIds){
+			var sid = g_plotIds[i];
+			var divid = sid.replace(/\./g,'')+"_key";
+			$('#'+divid).attr('name',sid);
+			$('#'+divid).dblclick(function () {
+			toggleFinder($(this), $(this).attr('name'));
+			});
+		}
+		g_keyBuilt = true;
+	}
+}
+
+/*Toggle an arrow to locate a sample by double clicking the box @ the key menu*/
+function toggleFinder(div, divName) {
+	if(g_foundId != divName) {
+		$('.colorbox').css('border','1px solid black');
+		div.css('border','1px solid white');
+		$('#finder').css('opacity',1);
+		var coords = toScreenXY(g_plotSpheres[divName].position, g_sceneCamera, $('#main_plot'));
+		$('#finder').css('left',coords['x']-15);
+		$('#finder').css('top',coords['y']-5);
+		g_foundId = divName;
+	}
+	else {
+		if($('#finder').css('opacity') == 1) {
+			$('#finder').css('opacity',0);
+			div.css('border','1px solid black');
+			g_foundId = null
+		}
+		else {
+			$('#finder').css('opacity',1);
+			div.css('border','1px solid white');
+		}
+	}
+}
+
+/*Callback for the colorChanged event as triggered by the color picker*/
+function colorChanged(catValue,color) {
+	for(var i in g_plotIds)
+	{
+		var sid = g_plotIds[i]
+		if(g_mappingFileData[g_plotIds[i]][g_categoryIndex] == catValue)
+		{
+			// get the valid divId for the key and set its color
+			$("#"+sid.replace(/\./g,'')+"_key").css('backgroundColor',color);
+			// set the color of the corresponding sphere and ellipse 
+			try {
+				g_plotEllipses[sid].material.color.setHex(color.replace('#','0x'));
+			}
+			catch(TypeError){}
+			try {
+				g_plotSpheres[sid].material.color.setHex(color.replace('#','0x'));
+			}
+			catch(TypeError){}
+			try{
+				g_plotVectors[sid].material.color.setHex(color.replace('#','0x'));
+			}
+			catch(TypeError){}
+		}
+	}
+}
+
+/* This function is called when q new color is selected for #taxaspherescolor */
+function colorChangedForTaxaSpheres(color){
+	for (index in g_plotTaxa){
+		g_plotTaxa[index].material.color.setHex(color)
+	}
+}
+
+/* This function is called when a new color is selected for the edges
+
+ The two input parameters are a hexadecimal formatted color and an index, the
+ index indicates which side of the edges are going to be re-colored.
+*/
+function colorChangedForEdges(color, index){
+	for(var sample_id in g_plotEdges){
+		currentColor = g_plotEdges[sample_id][index].material.color.setHex(color);
+	}
+}
+
+/*This function is called when a new value is selected in the label menu*/
+function labelMenuChanged() {
+	if(document.getElementById('labelcombo').selectedIndex == 0){
+		document.getElementById("labellist").innerHTML = "";
+		return;
+	}
+
+	// set the new current category and index
+	var labelCategory = document.getElementById('labelcombo')[document.getElementById('labelcombo').selectedIndex].value;
+	var labelCatIndex = g_mappingFileHeaders.indexOf(labelCategory);
+
+	// get all values of this category from the mapping
+	var vals = [];
+	for(var i in g_plotIds){
+		vals.push(g_mappingFileData[g_plotIds[i]][labelCatIndex]);
+	}
+
+	vals = _splitAndSortNumericAndAlpha(dedupe(vals));
+	colors = getColorList(vals);
+
+	// build the label table in HTML
+	var lines = "<form name=\"labels\" id=\"labelForm\"><table>";
+	for(var i in vals){
+		// each field is identified by the value it has in the deduplicated
+		// list of values and by the number of the column in the mapping file
+		// if this is done otherwise, weird characters have to be extemped etc.
+		var idString = "r"+i+"c"+g_categoryIndex;
+
+		// set the div id, checkbox name so that we can reference this later
+		lines += "<tr><td><input name=\""+vals[i]+"\" type=\"checkbox\" checked=\"true\" onClick=\"toggleLabels()\" ></input></td><td><div id=\""+idString+"Label\" class=\"colorbox\" name=\""+vals[i]+"\"></div></td><td title=\""+vals[i]+"\">";
+
+		if(vals[i].length > 25){
+			lines+= vals[i].substring(0,25) + "..."
+		}
+		else{
+			lines += vals[i];
+		}
+
+		lines+= "</td></tr>";
+	}
+
+	lines += "</table></form>";
+	document.getElementById("labellist").innerHTML = lines;
+
+	for(var i in vals){
+		// each field is identified by the value it has in the deduplicated
+		// list of values and by the number of the column in the mapping file
+		// if this is done otherwise, weird characters have to be extemped etc.
+		var idString = "r"+i+"c"+g_categoryIndex;
+
+		// get the div built earlier and turn it into a color picker
+		$('#'+idString+'Label').css('backgroundColor',"#"+colors[vals[i]].getHexString());
+		labelColorChanged(vals[i], "#"+colors[vals[i]].getHexString());
+
+		$("#"+idString+'Label').spectrum({
+			color: colors[vals[i]].getHexString(),
+			showInitial: true,
+			showPalette: true,
+			preferredFormat: "hex6",
+			palette: [['red', 'green', 'blue']],
+			change:
+				function(color) {
+					$(this).css('backgroundColor', color.toHexString());
+					labelColorChanged($(this).attr('name'), color.toHexString());
+				}
+		});
+	}
+}
+
+/*This function is called when a label color is changed*/
+function labelColorChanged(value, color) {
+	g_categoryName = document.getElementById('labelcombo')[document.getElementById('labelcombo').selectedIndex].value;
+	value = value.replace('_','');
+
+	for(var i in g_plotIds){
+		var sid = g_plotIds[i];
+		var divid = sid.replace(/\./g,'');
+		if(g_mappingFileData[sid][g_mappingFileHeaders.indexOf(g_categoryName)] == value){
+			$('#'+divid+"_label").css('color', color);
+		}
+	}
+}
+
+/*This function turns the labels on and off*/
+function toggleLabels() {
+	if(document.plotoptions.elements[0].checked){
+		$('#labelForm').css('display','block');
+		$('#labels').css('display','block');
+		$("#lopacityslider").slider('enable');
+		$("#labelColor").spectrum('enable');
+		document.getElementById('labelcombo').disabled = false;
+
+		if(document.labels == null){
+			return;
+		}
+
+		// get the current category name to show the labels
+		g_categoryName = document.getElementById('labelcombo')[document.getElementById('labelcombo').selectedIndex].value;
+
+		// for each of the labels check if they are enabled or not
+		for(var i = 0; i < document.labels.elements.length; i++){
+			var hidden = !document.labels.elements[i].checked;
+			var value = document.labels.elements[i].name;
+
+			for(var j in g_plotIds){
+				var sid = g_plotIds[j];
+				var divid = sid.replace(/\./g,'');
+
+				if(g_mappingFileData[sid][g_mappingFileHeaders.indexOf(g_categoryName)] == value && hidden){
+					$('#'+divid+"_label").css('display', 'none');
+				}
+				else if(g_mappingFileData[sid][g_mappingFileHeaders.indexOf(g_categoryName)] == value && !hidden){
+					$('#'+divid+"_label").css('display', 'block');
+				}
+			}
+		}
+	}
+	else{
+		$('#labels').css('display','none');
+	}
+}
+
+/*This function turns the labels with the lineages on and off*/
+function toggleTaxaLabels(){
+	// present labels if the visibility checkbox is marked
+	if(document.biplotoptions.elements[0].checked){
+		$('#taxalabels').css('display','block');
+
+		for(var key in g_taxaPositions){
+			var taxa_label = g_taxaPositions[key]['lineage'];
+			var divid = taxa_label.replace(/\./g,'');
+			$('#'+key+"_taxalabel").css('display', 'block');
+		}
+	}
+	else{
+		$('#taxalabels').css('display','none');
+	}
+}
+
+/* Turn on and off the spheres representing the biplots on screen */
+function toggleBiplotVisibility(){
+	// reduce the opacity to zero if the element should be off or to 0.5
+	// if the element is supposed to be present; 0.5 is the default value
+	if(!document.biplotsvisibility.elements[0].checked){
+		for (index in g_plotTaxa){
+			g_mainScene.remove(g_plotTaxa[index]);
+		}
+	}
+	else{
+		for (index in g_plotTaxa){
+			g_mainScene.add(g_plotTaxa[index])
+		}
+	}
+}
+
+/* Turn on and off the lines connecting the samples being compared */
+function toggleEdgesVisibility(){
+
+	// each edge is really composed of two lines and those elements are stored
+	// in each of the keys that are stored for each sample comparison
+	if(!document.edgesvisibility.elements[0].checked){
+		for (index in g_plotEdges){
+			g_mainScene.remove(g_plotEdges[index][0]);
+			g_mainScene.remove(g_plotEdges[index][1]);
+		}
+	}
+	else{
+		for (index in g_plotEdges){
+			g_mainScene.add(g_plotEdges[index][0]);
+			g_mainScene.add(g_plotEdges[index][1]);
+		}
+	}
+}
+
+/*This function finds the screen coordinates of any position in the current plot.
+
+  The main purpose of this function is to be used for calculating the placement
+  of the labels.
+*/
+function toScreenXY( position, camera, jqdiv ) {
+
+	var screenPosition = position.clone();
+	var screenProjectionMatrix = new THREE.Matrix4();
+
+	// multiply the matrices and aply the vector to the projection matrix
+	screenProjectionMatrix.multiplyMatrices( camera.projectionMatrix,
+		camera.matrixWorldInverse);
+	screenPosition.applyProjection(screenProjectionMatrix);
+
+	return { x: (screenPosition.x + 1)*jqdiv.width()/2 + jqdiv.offset().left,
+		y: (-screenPosition.y+1)*jqdiv.height()/2 + jqdiv.offset().top};
+}
+
+/*This function is used to filter the key to a user's provided search string*/
+function filterKey() {
+	var searchVal = document.keyFilter.filterBox.value.toLowerCase();
+
+	for(var i in g_plotIds){
+		var sid = g_plotIds[i];
+		var divid = sid.replace(/\./g,'')+"_keyrow";
+
+		if(sid.toLowerCase().indexOf(searchVal) != -1){
+			$('#'+divid).css('display','block');
+		}
+		else{
+			$('#'+divid).css('display','none');
+		}
+	}
+}
+
+/*This function handles events from the ellipse opacity slider*/
+function ellipseOpacityChange(ui) {
+	document.getElementById('ellipseopacity').innerHTML = ui.value + "%";
+	ellipseOpacity = ui.value/100;
+
+	for(var sid in g_plotEllipses){
+		g_plotEllipses[sid].material.opacity = ellipseOpacity;
+	}
+}
+
+/*This function handles events from the sphere opacity sliders
+
+  Note that there are two type of sliders, the master opacity slider in the
+  options tab and the 'per-category' slider in the visibility tab. The later
+  one controls the opacity only for the spheres belonging to a specific category
+  in the mapping file.
+
+  As for the parameters 'ui' is the jQuery slider element and category the
+  string with the value of that category of the mapping file or null when the
+  callback is originated from the master opacity slider.
+*/
+function sphereOpacityChange(ui, category) {
+	var sphereOpacity = ui.value/100;
+	var showByCategoryName = document.getElementById('showbycombo')[document.getElementById('showbycombo').selectedIndex].value;
+	var showByCategoryIndex = g_mappingFileHeaders.indexOf(showByCategoryName);
+	var vals = [], idString, newValue;
+
+	// get all values of this category from the mapping
+	for(var i in g_plotIds){
+		vals.push(g_mappingFileData[g_plotIds[i]][showByCategoryIndex]);
+	}
+	vals = _splitAndSortNumericAndAlpha(dedupe(vals));
+
+	// category as null means that it's the general opacity slider (the on in the options tab)
+	if (category == null) {
+		for (index in vals){
+			idString = "r"+index+"c"+showByCategoryIndex;
+			$("#"+idString+"opacityslider").slider("value", ui.value);
+			document.getElementById(idString+"opacityvalue").innerHTML = $("#"+idString+"opacityslider").slider("value")+"%";
+		}
+		document.getElementById('sphereopacity').innerHTML = ui.value + "%";
+	}
+	else{
+		// each field is identified by the value it has in the deduplicated
+		// list of values and by the number of the column in the mapping file
+		// if this is done otherwise, weird characters have to be extemped etc.
+		idString = "r"+vals.indexOf(category)+"c"+showByCategoryIndex;
+
+		for(var i in g_plotIds){
+			if(g_mappingFileData[g_plotIds[i]][showByCategoryIndex] == category){
+				g_plotSpheres[g_plotIds[i]].material.opacity = sphereOpacity;
+			}
+			document.getElementById(idString+"opacityvalue").innerHTML = $("#"+idString+"opacityslider").slider("value")+"%";
+		}
+	}
+}
+
+/*This function handles events from the vectors opacity slider*/
+function vectorsOpacityChange(ui) {
+	document.getElementById('vectorsopacity').innerHTML = ui.value + "%";
+	var vectorsOpacity = ui.value/100;
+
+	for(var sample_id in g_plotVectors){
+		g_plotVectors[sample_id].material.opacity = vectorsOpacity;
+	}
+}
+
+/*This function handles events from the label opacity slider*/
+function labelOpacityChange(ui) {
+	document.getElementById('labelopacity').innerHTML = ui.value + "%";
+	labelOpacity = ui.value/100;
+
+	$('#labels').css('opacity', labelOpacity);
+}
+
+/*This function handles events from the sphere radius slider
+
+  Note that this function will get a scaling value added depending on whether or
+  not the plot being displayed is scaled by the percent explained in each axis.
+
+  When the category argument is null, the callback comes from the master radius
+  slider in any other case it refers to a specific category.
+*/
+function sphereRadiusChange(ui, category) {
+	var scale = (ui.value/5.0)*g_sphereScaler;
+	var scalingByCategoryName = document.getElementById('scalingbycombo')[document.getElementById('scalingbycombo').selectedIndex].value;
+	var scalingByCategoryIndex = g_mappingFileHeaders.indexOf(scalingByCategoryName);
+	var values = [], idString;
+
+	// get all values of this category from the mapping
+	for(var i in g_plotIds){
+		values.push(g_mappingFileData[g_plotIds[i]][scalingByCategoryIndex]);
+	}
+	values = _splitAndSortNumericAndAlpha(dedupe(values));
+
+	if (category == null){
+		for (index in values){
+			idString = "r"+index+"c"+scalingByCategoryIndex;
+			$("#"+idString+"scalingslider").slider("value", ui.value);
+			document.getElementById(idString+"scalingvalue").innerHTML = $("#"+idString+"scalingslider").slider("value")/5;
+		}
+
+		// set the value for the master scaling slider
+		document.getElementById('sphereradius').innerHTML = ui.value/5;
+	}
+	else{
+		// each field is identified by the value it has in the deduplicated
+		// list of values and by the number of the column in the mapping file
+		// if this is done otherwise, weird characters have to be extemped etc.
+		idString = "r"+values.indexOf(category)+"c"+scalingByCategoryIndex;
+
+		for(var i in g_plotIds){
+			if(g_mappingFileData[g_plotIds[i]][scalingByCategoryIndex] == category){
+				g_plotSpheres[g_plotIds[i]].scale.set(scale, scale, scale);
+			}
+			document.getElementById(idString+"scalingvalue").innerHTML = $("#"+idString+"scalingslider").slider("value")/5;
+		}
+	}
+}
+
+/*Setup the interface elements required for the sidebar of the main interface*/
+function setJqueryUi() {
+	$("#menutabs").tabs();
+	$("#plottype").buttonset();
+	$("input[name='plottype']").change(togglePlots);
+	
+	$("#labelColor").css('backgroundColor', '#ffffff');
+
+	$("#labelColor").spectrum({
+		color: '#fffffff',
+		showInitial: true,
+		showPalette: true,
+		preferredFormat: "hex6",
+		palette: [['black', 'red', 'green', 'blue']],
+		change:
+			function(color) {
+				$(this).css('backgroundColor', color.toHexString());
+				$('#labels').css('color', color.toHexString());
+				for(var i in g_plotIds){
+					var sid = g_plotIds[i];
+					var divid = sid.replace(/\./g,'');
+					$('#'+divid+"_label").css('color', color.toHexString());
+				}
+				document.getElementById('labelcombo').selectedIndex = 0;
+				labelMenuChanged();
+			}
+	});
+
+	// check whether or not there is an ellipse opacity slider in the plot
+	if (document.getElementById('ellipseopacity')){
+		$("#eopacityslider").slider({
+			range: "max",
+			min: 0,
+			max: 100,
+			value: 20,
+			slide: function( event, ui ) {
+				ellipseOpacityChange(ui);
+			},
+			change: function( event, ui ) {
+				ellipseOpacityChange(ui);
+			}
+		});
+		document.getElementById('ellipseopacity').innerHTML = $( "#eopacityslider" ).slider( "value")+"%";
+	}
+
+	// check whether or not there is a vectors opacity slider in the plot
+	if (document.getElementById('vectorsopacity')){
+		$("#vopacityslider").slider({
+			range: "max",
+			min: 0,
+			max: 100,
+			value: 100,
+			slide: function( event, ui ) {
+				vectorsOpacityChange(ui);
+			},
+			change: function( event, ui ) {
+				vectorsOpacityChange(ui);
+			}
+		});
+		document.getElementById('vectorsopacity').innerHTML = $( "#vopacityslider" ).slider( "value")+"%";
+	}
+
+	// check if we are presenting biplots, to decide whether or not we should
+	// show the color picker for the biplot spheres, white is the default color
+	if(document.getElementById('taxaspherescolor')){
+		$('#taxaspherescolor').css('backgroundColor',"#FFFFFF");
+		$("#taxaspherescolor").spectrum({
+			localStorageKey: 'key',
+			color: "#FFFFFF",
+			preferredFormat: "hex6",
+			showInitial: true,
+			showInput: true,
+			change:
+				function(color) {
+					// pass a boolean flag to convert to hex6 string
+					var c = color.toHexString(true);
+					$(this).css('backgroundColor', c);
+					colorChangedForTaxaSpheres(c.replace('#', '0x'));
+				}
+		});
+	}
+	// set up the color selector for the taxa labels
+	if(document.getElementById('taxalabelcolor')){
+		$('#taxalabelcolor').css('backgroundColor',"#FFFFFF");
+		$("#taxalabelcolor").spectrum({
+			localStorageKey: 'key',
+			color: "#FFFFFF",
+			preferredFormat: "hex6",
+			showInitial: true,
+			showInput: true,
+			change:
+				function(color) {
+				// pass a boolean flag to convert to hex6 string
+				var c = color.toHexString(true);
+
+				// set the color for the box and for the renderer
+				$(this).css('backgroundColor', c);
+
+				// get the taxonomic assignments and append '_taxalabel' to
+				// retrieve all the labels belonging to a sphere in the plot
+				for(var key in g_taxaPositions) {
+					$('#'+key+"_taxalabel").css('color', c);
+				}
+			}// on-change callback
+		});
+	}
+
+	// check if the plot is a comparison plot if so, setup the elements that
+	// will allow the user to change the color of the two sides of the edges
+	if(document.getElementById('edgecolorselector_a')){
+		$('#edgecolorselector_a').css('backgroundColor',"#FFFFFF");
+		$("#edgecolorselector_a").spectrum({
+			localStorageKey: 'key',
+			color: "#FFFFFF",
+			preferredFormat: "hex6",
+			showInitial: true,
+			showInput: true,
+			change:
+				function(color) {
+					// pass a boolean flag to convert to hex6 string
+					var c = color.toHexString(true);
+					$(this).css('backgroundColor', c);
+					colorChangedForEdges(c.replace('#', '0x'), 0);
+				}
+		});
+	}
+	if(document.getElementById('edgecolorselector_b')){
+		$('#edgecolorselector_b').css('backgroundColor',"#FF0000");
+		$("#edgecolorselector_b").spectrum({
+			localStorageKey: 'key',
+			color: "#FF0000",
+			preferredFormat: "hex6",
+			showInitial: true,
+			showInput: true,
+			change:
+				function(color) {
+					// pass a boolean flag to convert to hex6 string
+					var c = color.toHexString(true);
+					$(this).css('backgroundColor', c);
+					colorChangedForEdges(c.replace('#', '0x'), 1);
+				}
+		});
+	}
+
+	$("#sopacityslider").slider({
+		range: "max",
+		min: 0,
+		max: 100,
+		value: 100,
+		slide: function( event, ui ) {
+			sphereOpacityChange(ui, null);
+		},
+		change: function( event, ui ) {
+			sphereOpacityChange(ui, null);
+		}
+	});
+	document.getElementById('sphereopacity').innerHTML = $( "#sopacityslider" ).slider( "value").toString()+"%";
+
+	$("#sradiusslider" ).slider({
+		range: "max",
+		min: 1,
+		max: 20,
+		value: 5,
+		slide: function( event, ui ) {
+			sphereRadiusChange(ui, null);
+		},
+		change: function( event, ui ) {
+			sphereRadiusChange(ui, null);
+		}
+	});
+	document.getElementById('sphereradius').innerHTML = $( "#sradiusslider" ).slider( "value")/5;
+
+	$("#lopacityslider").slider({
+		range: "max",
+		min: 0,
+		max: 100,
+		value: 100,
+		slide: function( event, ui ) {
+			labelOpacityChange(ui);
+		},
+		change: function( event, ui ) {
+			labelOpacityChange(ui);
+		}
+	});
+	document.getElementById('labelopacity').innerHTML = $( "#lopacityslider" ).slider( "value")+"%"
+
+	//default color for axes labels is white
+	$('#axeslabelscolor').css('backgroundColor',"#FFFFFF");
+	$("#axeslabelscolor").spectrum({
+		localStorageKey: 'key',
+		color: "#FFFFFF",
+		showInitial: true,
+		showInput: true,
+		showPalette: true,
+		preferredFormat: "hex6",
+		palette: [['white', 'black']],
+		change:
+			function(color) {
+				// pass a boolean flag to convert to hex6 string
+				var c = color.toHexString(true);
+				// set the color for the box and for the renderer
+				$(this).css('backgroundColor', c);
+
+				//set css for the text in the parallel plot
+				$('.parcoords text').css('stroke', c);
+
+				// change the css color of the 3d plot labels
+				$("#pc1_label").css('color', c);
+				$("#pc2_label").css('color', c);
+				$("#pc3_label").css('color', c);
+
+			}
+	});
+
+	//default color for the axes is white
+	$('#axescolor').css('backgroundColor',"#ffffff");
+	$("#axescolor").spectrum({
+		localStorageKey: 'key',
+		color: "#ffffff",
+		showInitial: true,
+		showInput: true,
+		showPalette: true,
+		preferredFormat: "hex6",
+		palette: [['white', 'black']],
+		change:
+			function(color) {
+				// pass a boolean flag to convert to hex6 string
+				var c = color.toHexString(true);
+				// create a new three.color from the string
+				var axesColor = new THREE.Color();
+				axesColor.setHex(c.replace('#','0x'));
+				g_xAxisLine.material.color = axesColor;
+				g_yAxisLine.material.color = axesColor;
+				g_zAxisLine.material.color = axesColor;
+
+
+				// set the color for the box and for the renderer
+				$(this).css('backgroundColor', c);
+
+				//set css for the lines of the parallel cords
+				$('.parcoords .axis line, .parcoords .axis path').css('stroke', c);
+			}
+	});
+
+	// the default color palette for the background is black and white
+	$('#rendererbackgroundcolor').css('backgroundColor',"#000000");
+	$('#parallelPlotWrapper').css('backgroundColor',"#000000");
+	$("#rendererbackgroundcolor").spectrum({
+		localStorageKey: 'key',
+		color: "#000000",
+		showInitial: true,
+		showInput: true,
+		showPalette: true,
+		preferredFormat: "hex6",
+		palette: [['white', 'black']],
+		change:
+			function(color) {
+				// pass a boolean flag to convert to hex6 string
+				var c = color.toHexString(true);
+
+				// create a new three.color from the string
+				var rendererBackgroundColor = new THREE.Color();
+				rendererBackgroundColor.setHex(c.replace('#','0x'));
+
+				// set the color for the box and for the renderer
+				$(this).css('backgroundColor', c);
+				g_mainRenderer.setClearColor(rendererBackgroundColor, 1);
+				
+				$('#parallelPlotWrapper').css('backgroundColor', c);
+			}
+	});
+}
+
+/*Draw the ellipses in the plot as described by the g_ellipsesDimensions array
+
+  Note that this is a function that won't always get executed since this should
+  only happen when plotting a jaccknifed principal coordinates analysis
+*/
+function drawEllipses() {
+	for(var sid in g_ellipsesDimensions) {
+		//draw ellipsoid
+		var emesh = new THREE.Mesh( g_genericSphere,new THREE.MeshLambertMaterial() );
+		emesh.scale.x = g_ellipsesDimensions[sid]['width']/g_radius;
+		emesh.scale.y = g_ellipsesDimensions[sid]['height']/g_radius;
+		emesh.scale.z = g_ellipsesDimensions[sid]['length']/g_radius;
+		emesh.position.set(g_ellipsesDimensions[sid]['x'],g_ellipsesDimensions[sid]['y'],g_ellipsesDimensions[sid]['z']);
+		emesh.material.color = new THREE.Color()
+		emesh.material.transparent = true;
+		emesh.material.depthWrite = false;
+		emesh.material.opacity = 0.2;
+		emesh.updateMatrix();
+		emesh.matrixAutoUpdate = true;
+		if(g_mappingFileData[sid] != undefined){
+			g_elementsGroup.add( emesh );
+			g_plotEllipses[sid] = emesh;
+		}
+	}
+}
+
+/*Draw the spheres in the plot as described by the g_spherePositions array*/
+function drawSpheres() {
+	for(var sid in g_spherePositions){
+		//draw ball
+		var mesh = new THREE.Mesh( g_genericSphere, new THREE.MeshLambertMaterial() );
+		mesh.material.color = new THREE.Color()
+		mesh.material.transparent = true;
+		mesh.material.depthWrite = false;
+		mesh.material.opacity = 1;
+		mesh.position.set(g_spherePositions[sid]['x'], g_spherePositions[sid]['y'], g_spherePositions[sid]['z']);
+		mesh.updateMatrix();
+		mesh.matrixAutoUpdate = true;
+		if(g_mappingFileData[sid] != undefined){
+			g_elementsGroup.add( mesh );
+			g_plotSpheres[sid] = mesh;
+			g_plotIds.push(sid);
+		}
+	}
+}
+
+/*Draw the taxa spheres in the plot as described by the g_taxaPositions array
+
+  Note that this is a function that won't always have an effect because the
+  g_taxaPositions array must have elements stored in it.
+*/
+function drawTaxa(){
+	// All taxa spheres are white by default
+	var whiteColor = new THREE.Color();
+	whiteColor.setHex("0xFFFFFF");
+
+	for (var key in g_taxaPositions){
+		var mesh = new THREE.Mesh(g_genericSphere,
+			new THREE.MeshLambertMaterial());
+
+		// set the volume of the sphere
+		mesh.scale.x = g_taxaPositions[key]['radius'];
+		mesh.scale.y = g_taxaPositions[key]['radius'];
+		mesh.scale.z = g_taxaPositions[key]['radius'];
+
+		// set the position
+		mesh.position.set(g_taxaPositions[key]['x'],
+			g_taxaPositions[key]['y'],
+			g_taxaPositions[key]['z']);
+
+		// the legacy color of these spheres is white
+		mesh.material.color = whiteColor;
+		mesh.material.transparent = true;
+		mesh.material.opacity = 0.5;
+		mesh.updateMatrix();
+		mesh.matrixAutoUpdate = true;
+
+		// add the element to the scene and to the g_plotTaxa dictionary
+		g_elementsGroup.add(mesh)
+		g_mainScene.add(mesh);
+		g_plotTaxa[key] = mesh;
+	}
+}
+
+/*Draw the lines for the plot as described in the g_vectorPositions array
+
+  Note that this will draw a single line between each of the samples, hence
+  resulting in N-1 lines being drawn where N is the total number of samples,
+  similarly to spheres or ellipsoids these elements are added to the
+  g_elementsGroup variable.
+*/
+function drawVectors(){
+	var current_vector, previous = null;
+
+	// There are as many vectors as categories were specified
+	for (var categoryKey in g_vectorPositions){
+		for (var sampleKey in g_vectorPositions[categoryKey]){
+
+			// retrieve the initial position on the first loop
+			if (previous == null){
+				previous = g_vectorPositions[categoryKey][sampleKey];
+			}
+			// if we already have the initial position, draw the line with
+			// the current position and the value of previous (initial position)
+			else{
+				current = g_vectorPositions[categoryKey][sampleKey];
+				current_vector = makeLine(previous, current, 0xFFFFFF, 2)
+				previous = current;
+				g_plotVectors[sampleKey] = current_vector;
+
+				if(g_mappingFileData[sampleKey] != undefined){
+					g_elementsGroup.add(current_vector);
+					g_plotVectors[sampleKey] = current_vector;
+				}
+			}
+		}
+
+		// reset previous so the algorithms work on the next category
+		previous = null;
+	}
+}
+
+/*Draw the lines that connect samples being compared (see g_comparePositiosn)
+
+  This will draw two lines between each compared sample, one with color red and
+  the other one with color white, what must be noted here is that, these two
+  lines visually compose a single line and are both stored in the g_plotEdges
+  array in arrays of two elements where the first element is the red line and
+  the second element is the white line.
+  
+  A dynamic value that contains the coordinates of the spheres is passed in, that way 
+  to allow the negating of the values.
+
+  In the case of a non-serial comparison plot, all edges will originate in the
+  same point.
+*/
+function drawEdges(spherepositions){
+	var previous = null, origin = null, current, middle_point, index=0, line_a, line_b;
+					
+	// note that this function is composed of an if-else statement with a loop
+	// that's almost identical under each case. This approach was taken as
+	// otherwise the comparison would need to happen N times instead of 1 time
+	// (N is the number of edges*2).
+
+	// if the comparison is serial draw one edge after the other
+	if (g_isSerialComparisonPlot == true){
+		for (var sampleKey in spherepositions){
+			for (var edgePosition in spherepositions[sampleKey]){
+			
+				// if we don't have a start point store it and move along
+				if (previous == null) {
+					previous = spherepositions[sampleKey][edgePosition];
+				}
+				// if we already have a start point then draw the edge
+				else{
+					current = spherepositions[sampleKey][edgePosition];
+					
+					// the edge is composed by two lines so calculate the middle
+					// point between these two lines and end the first line in this
+					// point and start the second line in this point
+					middle_point = [(previous[0]+current[0])/2,
+						(previous[1]+current[1])/2, (previous[2]+current[2])/2];
+
+					currentColorA_hex = $("#edgecolorselector_a").spectrum("get").toHexString(true);
+					currentColorB_hex = $("#edgecolorselector_b").spectrum("get").toHexString(true);
+					line_a = makeLine(previous, middle_point, currentColorA_hex, 2);
+					line_b = makeLine(middle_point, current, currentColorB_hex, 2);
+					line_a.transparent = false;
+					line_b.transparent = false;
+
+					// index the two lines by the name of the sample plus a suffix
+					g_plotEdges[sampleKey+'_'+index.toString()] = [line_a, line_b];
+
+					g_elementsGroup.add(line_a);
+					g_elementsGroup.add(line_b);
+					g_mainScene.add(line_a);
+					g_mainScene.add(line_b);
+
+					// the current line becomes the previous line for the next
+					// iteration as all samples must be connected
+					previous = spherepositions[sampleKey][edgePosition];
+				}
+				index = index+1;
+			}
+
+			// if we've finished with the connecting lines let a new line start
+			previous = null;
+		}
+	}
+	// if the comparison is not serial, originate all edges in the same coords
+	else{
+		for (var sampleKey in spherepositions){
+			for (var edgePosition in spherepositions[sampleKey]){
+				if (origin == null) {
+					origin = spherepositions[sampleKey][edgePosition];
+				}
+				else{
+					current = spherepositions[sampleKey][edgePosition];
+
+					// edges are composed of two lines so use the start and
+					// the end point to calculate the position of the vertices
+					middle_point = [(origin[0]+current[0])/2,
+						(origin[1]+current[1])/2, (origin[2]+current[2])/2];
+
+					// in the case of centered comparisons the origins are
+					// painted in color white one one side and red on the other
+					currentColorA_hex = $("#edgecolorselector_a").spectrum("get").toHexString(true);
+					currentColorB_hex = $("#edgecolorselector_b").spectrum("get").toHexString(true);
+					line_a = makeLine(origin, middle_point, currentColorA_hex, 2);
+					line_b = makeLine(middle_point, current, currentColorB_hex, 2);
+					line_a.transparent = false;
+					line_b.transparent = false;
+
+					// given that these are just sample repetitions just
+					// just add a suffix at the end of the sample id
+					g_plotEdges[sampleKey+'_'+index.toString()] = [line_a, line_b];
+
+					g_elementsGroup.add(line_a);
+					g_elementsGroup.add(line_b);
+					g_mainScene.add(line_a);
+					g_mainScene.add(line_b);
+
+				}
+				index = index + 1;
+			}
+			origin = null;
+		}
+	}
+}
+
+/*Save the current view to SVG
+  This will take the current webGL renderer, convert it to SVG and then generate 
+  a file to download. Additionally it will create the labels if this option is selected.
+*/
+function saveSVG(button){
+    // add a name subfix for the filenames
+    if ((g_segments<=8 && g_visiblePoints>=10000) || (g_segments>8 && g_visiblePoints>=5000)) {
+        var res = confirm("The number of segments (" + g_segments + ") combined with the number " +
+            "of samples could take a long time and in some computers the browser will crash. " +
+            "If this happens we suggest to lower the number of segments or use the png " +
+            "implementation. Do you want to continue?");
+        if (res==false) return;
+    }
+ 
+    $('body').css('cursor','progress');
+    
+    var width = document.getElementById('pcoaPlotWrapper').offsetWidth;
+    var height = document.getElementById('pcoaPlotWrapper').offsetHeight;
+    
+    var color = $("#rendererbackgroundcolor").spectrum("get").toHexString(true);
+    var rendererBackgroundColor = new THREE.Color();
+    rendererBackgroundColor.setHex(color.replace('#','0x'));
+
+	var svgRenderer = new THREE.SVGRenderer({ antialias: true, preserveDrawingBuffer: true }); 
+	// this is the proper way to set the color of the background but it doesn't work   
+    svgRenderer.setClearColor(rendererBackgroundColor, 1);
+    svgRenderer.setSize(width, height);
+    svgRenderer.render(g_mainScene, g_sceneCamera);
+    svgRenderer.sortObjects = true;
+        
+    // converting svgRenderer to string: http://stackoverflow.com/questions/17398134/three-svgrenderer-save-text-of-image
+    var XMLS = new XMLSerializer(); 
+    var svgfile = XMLS.serializeToString(svgRenderer.domElement);
+    
+    // hacking the color to the svg
+    var index = svgfile.indexOf('viewBox="')+9;
+    var viewBox = svgfile.substring(index, svgfile.indexOf('"',index))
+    viewBox = viewBox.split(" ");
+    var background = '<rect id="background" height="' + viewBox[3] + '" width="' + viewBox[2] + '" y="' + 
+        viewBox[1] + '" x="' + viewBox[0] + '" stroke-width="0" stroke="#000000" fill="' + color + '"/>'
+    index = svgfile.indexOf('>',index)+1;
+    svgfile = svgfile.substr(0, index) + background + svgfile.substr(index);
+    
+    // adding xmlns header to open in the browser 
+    svgfile = svgfile.replace('viewBox=', 'xmlns="http://www.w3.org/2000/svg" viewBox=')
+    saveAs(new Blob([svgfile], {type: "text/plain;charset=utf-8"}), 
+         $('#saveas_name').val() + ".svg");
+    
+    if ($('#saveas_legends').is(':checked')) {
+        var labels_text = '', pos_y = 1, increment = 40, max_len = 0, font_size = 12;
+        $('#colorbylist_table tr div').each(function() {
+            if ($(this).attr('name').length > max_len) max_len = $(this).attr('name').length 
+            
+            // adding rectangle
+            labels_text += '<rect height="27" width="27" y="' + pos_y + 
+                '" x="1" stroke-width="1" ' + 'stroke="#FFFFFF" fill="' + 
+                $("#" + $(this).attr('id')).spectrum("get").toHexString(true) + '"/>';
+            // adding text
+            labels_text += '<text xml:space="preserve" y="' + (pos_y+20) + '" x="35" ' + 
+                'font-family="Monospace" font-size="' + font_size + '" stroke-width="0" ' +
+                'stroke="#000000" fill="#000000">' + $(this).attr('name') + '</text>';
+            pos_y += increment;
+        });
+        labels_text = '<svg width="' + ((font_size*max_len) + 10) + '" height="' + 
+            (pos_y-10) + '" xmlns="http://www.w3.org/2000/svg"><g>' + labels_text + 
+            '</g></svg>';
+        
+        saveAs(new Blob([labels_text], {type: "text/plain;charset=utf-8"}), 
+            $('#saveas_name').val() + "_labels.svg");
+    }
+    
+    $('body').css('cursor','default');
+}
+
+/*Utility function to draw two-vertices lines at a time
+
+  This function allows you to create a line with only two vertices i. e. the
+  start point and the end point, plus the color and width of the line. The
+  start and end point must be 3 elements array. The color must be a hex-string
+  or a hex number.
+*/
+function makeLine(coords_a, coords_b, color, width){
+	// based on the example described in:
+	// https://github.com/mrdoob/three.js/wiki/Drawing-lines
+	var material, geometry, line;
+
+	// make the material transparent and with full opacity
+	material = new THREE.LineBasicMaterial({color:color, linewidth:width});
+	material.matrixAutoUpdate = true;
+	material.transparent = true;
+	material.opacity = 1.0;
+
+	// add the two vertices to the geometry
+	geometry = new THREE.Geometry();
+	geometry.vertices.push(new THREE.Vector3(coords_a[0], coords_a[1], coords_a[2]));
+	geometry.vertices.push(new THREE.Vector3(coords_b[0], coords_b[1], coords_b[2]));
+
+	// the line will contain the two vertices and the described material
+	line = new THREE.Line(geometry, material);
+
+	return line;
+}
+
+/*Draw each of the lines that represent the X, Y and Z axes in the plot
+
+  The length of each of these axes depend on the ranges that the data being
+  displayed uses.
+*/
+function drawAxisLines() {
+	var axesColorFromColorPicker;
+
+	// removing axes, if they do not exist the scene doesn't complain
+	g_mainScene.remove(g_xAxisLine);
+	g_mainScene.remove(g_yAxisLine);
+	g_mainScene.remove(g_zAxisLine);
+
+	// value should be retrieved from the picker every time the axes are drawn
+	axesColorFromColorPicker = $("#axescolor").spectrum("get").toHexString(true);
+	axesColorFromColorPicker = axesColorFromColorPicker.replace('#','0x')
+	axesColorFromColorPicker = parseInt(axesColorFromColorPicker, 16)
+
+	// one line for each of the axes
+	g_xAxisLine = makeLine([g_xMinimumValue, g_yMinimumValue, g_zMinimumValue],
+		[g_xMaximumValue, g_yMinimumValue, g_zMinimumValue], axesColorFromColorPicker, 3);
+	g_yAxisLine = makeLine([g_xMinimumValue, g_yMinimumValue, g_zMinimumValue],
+		[g_xMinimumValue, g_yMaximumValue, g_zMinimumValue], axesColorFromColorPicker, 3);
+	g_zAxisLine = makeLine([g_xMinimumValue, g_yMinimumValue, g_zMinimumValue],
+		[g_xMinimumValue, g_yMinimumValue, g_zMaximumValue], axesColorFromColorPicker, 3);
+
+	// axes shouldn't be transparent
+	g_xAxisLine.material.transparent = false;
+	g_yAxisLine.material.transparent = false;
+	g_zAxisLine.material.transparent = false;
+
+	g_mainScene.add(g_xAxisLine)
+	g_mainScene.add(g_yAxisLine)
+	g_mainScene.add(g_zAxisLine)
+};
+
+/* update point count label */
+function changePointCount() {
+	document.getElementById('pointCount').innerHTML = g_visiblePoints+'/'+g_plotIds.length+' points'
+}
+
+/* Validating and modifying the view axes */
+function changeAxesDisplayed() {
+	if (!jQuery.isEmptyObject(g_vectorPositions) || !jQuery.isEmptyObject(g_taxaPositions) ||
+			!jQuery.isEmptyObject(g_ellipsesDimensions) || g_number_of_custom_axes!=0) {
+			resetCamera();
+			return;
+	}
+	
+	// HACK: this is a work around for cases when the scale is on
+	if ($('#scale_checkbox').is(':checked')) toggleScaleCoordinates({'checked': false});
+
+	var pc1_axis = $("#pc1_axis").val(), pc2_axis = $("#pc2_axis").val(),
+		pc3_axis = $("#pc3_axis").val();
+	var pc1_value = parseInt(pc1_axis.substring(1))-1,
+		pc2_value = parseInt(pc2_axis.substring(1))-1,
+		pc3_value = parseInt(pc3_axis.substring(1))-1;
+
+	//g_fractionExplained
+	if (pc1_axis==pc2_axis || pc1_axis==pc3_axis || pc2_axis==pc3_axis) {
+		$("#refresh_axes_label").html('<font color="red">Not valid values, try again.</font>');
+		return;
+	}
+	if (pc1_value>pc2_value || pc1_value>pc3_value || pc2_value>pc3_value) {
+		$("#refresh_axes_label").html('<font color="red">PC3 should be > than P2, and P2 than PC1.</font>');
+		return;
+	}
+
+	for (var sid in g_spherePositions) {
+		g_spherePositions[sid]['x'] = g_spherePositions[sid][pc1_axis];
+		g_spherePositions[sid]['y'] = g_spherePositions[sid][pc2_axis];
+		g_spherePositions[sid]['z'] = g_spherePositions[sid][pc3_axis];
+	}
+	
+	comparisonPositionlength = Object.keys(g_comparisonPositions).length
+	spherePositionslength = Object.keys(g_spherePositions).length/comparisonPositionlength
+	for (var sampleKey in g_comparisonPositions) {
+		for (var j=0;j<spherePositionslength;j++) {
+			var sid = sampleKey + "_" + j
+				g_comparisonPositions[sampleKey][j][0] = g_spherePositions[sid]['x']
+				g_comparisonPositions[sampleKey][j][1] = g_spherePositions[sid]['y']
+				g_comparisonPositions[sampleKey][j][2] = g_spherePositions[sid]['z']
+ 		}
+	}
+	
+	checkedboxes = []
+    if ($('#flip_axes_1').is(':checked')) {
+		for(var sid in g_spherePositions){
+			g_spherePositions[sid]['x'] = g_spherePositions[sid][pc1_axis]*(-1);
+		}
+ 		checkedboxes.push(0);
+	}
+    if ($('#flip_axes_2').is(':checked')) {
+		for(var sid in g_spherePositions){
+			g_spherePositions[sid]['y'] = g_spherePositions[sid][pc2_axis]*(-1);
+		}		
+ 		checkedboxes.push(1);
+	}
+    if ($('#flip_axes_3').is(':checked')) {
+		for(var sid in g_spherePositions){
+			g_spherePositions[sid]['z'] = g_spherePositions[sid][pc3_axis]*(-1);
+		}		
+ 		checkedboxes.push(2);
+	}
+	flipEdges(checkedboxes);
+	
+	// Setting up new positions
+	var max_x = Number.NEGATIVE_INFINITY, max_y = Number.NEGATIVE_INFINITY,
+		max_z = Number.NEGATIVE_INFINITY, min_x = Number.POSITIVE_INFINITY,
+		min_y = Number.POSITIVE_INFINITY, min_z = Number.POSITIVE_INFINITY;
+	for (var sid in g_spherePositions) {
+		if (g_spherePositions[sid]['x']>max_x)
+			max_x=g_spherePositions[sid]['x'];
+		if (g_spherePositions[sid]['y']>max_y)
+			max_y=g_spherePositions[sid]['y'];
+		if (g_spherePositions[sid]['z']>max_z)
+			max_z=g_spherePositions[sid]['z'];
+		if (g_spherePositions[sid]['x']<min_x)
+			min_x=g_spherePositions[sid]['x'];
+		if (g_spherePositions[sid]['y']<min_y)
+			min_y=g_spherePositions[sid]['y'];
+		if (g_spherePositions[sid]['z']<min_z)
+			min_z=g_spherePositions[sid]['z'];
+	}
+		
+	for (var sample_id in g_plotSpheres){
+		g_plotSpheres[sample_id].position.set(g_spherePositions[sample_id]['x'],
+			g_spherePositions[sample_id]['y'], g_spherePositions[sample_id]['z']);
+	}
+
+	// Setting up new axes for axes by coords explained
+	g_viewingAxes = [pc1_value, pc2_value, pc3_value]
+	g_pc1Label = "PC" + (g_viewingAxes[0]+1) + " (" + g_fractionExplainedRounded[g_viewingAxes[0]] + " %)";
+	g_pc2Label = "PC" + (g_viewingAxes[1]+1) + " (" + g_fractionExplainedRounded[g_viewingAxes[1]] + " %)";
+	g_pc3Label = "PC" + (g_viewingAxes[2]+1) + " (" + g_fractionExplainedRounded[g_viewingAxes[2]] + " %)";
+			
+	g_xMaximumValue = max_x + (max_x>=0 ? 6*g_radius : -6*g_radius);
+	g_yMaximumValue = max_y + (max_y>=0 ? 6*g_radius : -6*g_radius);
+	g_zMaximumValue = max_z + (max_z>=0 ? 6*g_radius : -6*g_radius);
+	g_xMinimumValue = min_x + (min_x>=0 ? 6*g_radius : -6*g_radius);
+	g_yMinimumValue = min_y + (min_y>=0 ? 6*g_radius : -6*g_radius);
+	g_zMinimumValue = min_z + (min_z>=0 ? 6*g_radius : -6*g_radius);
+	drawAxisLines();
+	buildAxisLabels();
+
+	// HACK: this is a work around for cases when the scale is on
+	if ($('#scale_checkbox').is(':checked')) toggleScaleCoordinates({'checked': true});
+	
+	// Change the css color of the 3d plot labels, set colors here because buildAxesLabels reverts color to default
+	axeslabelscolor = $('#axeslabelscolor').css( "background-color" );
+	axeslabelscolor_hex = $("#axeslabelscolor").spectrum("get").toHexString(true);
+	$("#pc1_label").css('color', axeslabelscolor);
+	$("#pc2_label").css('color', axeslabelscolor);
+	$("#pc3_label").css('color', axeslabelscolor);
+
+	resetCamera();
+}
+
+/*This function flips the lines in comparison plots when the user selects the option to negate the axes.*/
+function flipEdges(axis) {
+		var flippedPositions2d = new Array();
+		for (var sampleKey in g_comparisonPositions){
+			flippedPositions1d = []
+			for (var edgePosition in g_comparisonPositions[sampleKey]){
+				flippedPositions = [g_comparisonPositions[sampleKey][edgePosition][0], 
+									g_comparisonPositions[sampleKey][edgePosition][1],
+									g_comparisonPositions[sampleKey][edgePosition][2]]
+				for (var i=0;i<axis.length;i++) {
+					flippedPositions[axis[i]] *= (-1);
+				}
+				flippedPositions1d.push(flippedPositions);
+			}
+			flippedPositions2d[sampleKey] = flippedPositions1d;
+		}
+		removeEdges();
+		drawEdges(flippedPositions2d);
+}
+
+/*Removes the lines in comparison plots so the negated lines can be drawn*/
+function removeEdges() {
+	for(var sample_id in g_plotEdges){
+		for(var section in g_plotEdges[sample_id]){
+			g_mainScene.remove(g_plotEdges[sample_id][section]);
+		}
+	}
+}
+
+function clean_label_refresh_axes() {
+	$("#refresh_axes_label").html("");
+}
+
+function togglePlots() {
+
+	// set some interface changes for 3D visualizations
+	if(document.getElementById('pcoa').checked)
+	{
+		document.getElementById('pcoaPlotWrapper').className = 'plotWrapper';
+		document.getElementById('pcoaoptions').className = '';
+		document.getElementById('pcoaviewoptions').className = '';
+		document.getElementById('pcoaaxes').className = '';
+		document.getElementById('parallelPlotWrapper').className += ' invisible'
+		document.getElementById('paralleloptions').className += ' invisible'
+
+		// key menu is the default
+		$("#menutabs").tabs('select',0);
+
+		// make all tabs usable
+		$("#menutabs").tabs({disabled: []});
+		
+		// adding ctrl-p
+		g_screenshotBind = THREEx.Screenshot.bindKey(g_mainRenderer, {charCode: 16});
+	}
+	// changes for parallel plots
+	else{
+		document.getElementById('parallelPlotWrapper').className = document.getElementById('parallelPlotWrapper').className.replace(/(?:^|\s)invisible(?!\S)/ , '');
+		document.getElementById('paralleloptions').className = document.getElementById('paralleloptions').className.replace(/(?:^|\s)invisible(?!\S)/ , '');
+		document.getElementById('pcoaPlotWrapper').className += ' invisible'
+		document.getElementById('pcoaoptions').className += ' invisible'
+		document.getElementById('pcoaviewoptions').className += ' invisible'
+		document.getElementById('pcoaaxes').className += ' invisible'
+
+		// switch back to the key menu
+		$("#menutabs").tabs('select',0);
+
+		// make the visibility, scaling, labels and axes tabs un-usable
+		// they have no contextualized meaning in when lookin at parallel plots
+		// 0 = Key, 1 = Colors, 2 = Visibility, 3 = Scaling, 4 = Labels, 5 = Axes, 6 = View, 7 = Options
+		$("#menutabs").tabs({disabled: [2,3,4,5,7]});
+		
+		// removing the ctrl-p 
+        g_screenshotBind.unbind();
+		
+		colorByMenuChanged();
+	}
+}
+
+function setParallelPlots() {
+	g_parallelPlots = []
+
+	// get the number of axes being presented on screen but remove the ones
+	// that are represented by all of the custom axes (if there are any)
+	var num_axes = g_fractionExplained.length-g_number_of_custom_axes;
+
+	for(p in g_spherePositions){
+		var dataline = []
+		dataline.push(g_spherePositions[p].name)
+		for(var i = 1; i < num_axes+1; i++){
+			dataline.push(g_spherePositions[p]['P'+i])
+		}
+		g_parallelPlots.push(dataline)
+	}
+
+	pwidth = document.getElementById('pcoaPlotWrapper').offsetWidth
+	pheight = document.getElementById('pcoaPlotWrapper').offsetHeight
+
+	document.getElementById('parallelPlotWrapper').innerHTML = '<div id="parallelPlot" class="parcoords" style="width:'+pwidth+'px;height:'+pheight+'px"></div>'
+}
+
+// Resets the aspect ratio after dragging and window resize
+function aspectReset() {
+	winWidth = Math.min(document.getElementById('pcoaPlotWrapper').offsetWidth,document.getElementById('pcoaPlotWrapper').offsetHeight);
+	winAspect = document.getElementById('pcoaPlotWrapper').offsetWidth/document.getElementById('pcoaPlotWrapper').offsetHeight;                               
+	resetDivSizes(g_separator_left*$(window).width());
+	containmentLeft = $(window).width()*0.5;
+	containmentRight = $(window).width()*0.99;
+	g_sceneCamera.aspect = winAspect;
+	g_sceneCamera.updateProjectionMatrix();		
+
+}
+
+// Makes separator draggable and implements drag function
+function separator_draggable() {
+	$('.separator').draggable({
+		axis: 'x',
+		containment: [containmentLeft, 0, containmentRight, $(window).height()],
+		helper: 'clone',
+		drag: function (event, ui) {
+			offset = ui.offset.left;
+			if (offset > $(window).width()) {
+				offset = $(window).width()*0.99;
+			}
+			aspectReset();
+			resetDivSizes(offset);
+			if (offset < $(window).width()*0.93) {
+				g_separator_history = offset;
+			} 
+		}
+	});
+}
+         
+// Resizes plot and menu widths            
+function resetDivSizes(width_left) {
+	$('#plotToggle').width(width_left);
+	$('#parallelPlotWrapper').width(width_left);
+	$('#pcoaPlotWrapper').width(width_left);
+	if(document.getElementById('parallel').checked) {
+		togglePlots();
+	}
+	var width_right = $(window).width() - width_left - $('.separator').width()-1;                       
+	$('#menu').width(width_right);
+	g_separator_left = width_left/$(window).width();               
+	if (g_separator_left > 1) {
+		g_separator_left = 1;
+	}
+}
+
+/*Builds the axes labels from ground up after changing the axes*/
+function buildAxisLabels() {
+	//build axis labels
+	var axislabelhtml = "";
+	var xcoords = toScreenXY(new THREE.Vector3(g_xMaximumValue, g_yMinimumValue, g_zMinimumValue),g_sceneCamera,$('#main_plot'));
+	axislabelhtml += "<label id=\"pc1_label\" class=\"unselectable labels\" style=\"position:absolute; left:"+parseInt(xcoords['x'])+"px; top:"+parseInt(xcoords['y'])+"px;\">";
+	axislabelhtml += g_pc1Label;
+	axislabelhtml += "</label>";
+	var ycoords = toScreenXY(new THREE.Vector3(g_xMinimumValue, g_yMaximumValue, g_zMinimumValue),g_sceneCamera,$('#main_plot'));
+	axislabelhtml += "<label id=\"pc2_label\" class=\"unselectable labels\" style=\"position:absolute; left:"+parseInt(ycoords['x'])+"px; top:"+parseInt(ycoords['y'])+"px;\">";
+	axislabelhtml += g_pc2Label;
+	axislabelhtml += "</label>";
+	var zcoords = toScreenXY(new THREE.Vector3(g_xMinimumValue, g_yMinimumValue, g_zMaximumValue),g_sceneCamera,$('#main_plot'));
+	axislabelhtml += "<label id=\"pc3_label\" class=\"unselectable labels\" style=\"position:absolute; left:"+parseInt(zcoords['x'])+"px; top:"+parseInt(zcoords['y'])+"px;\">";
+	axislabelhtml += g_pc3Label;
+	axislabelhtml += "</label>";
+	document.getElementById("axislabels").innerHTML = axislabelhtml;
+}
+
+//Unhides the info box if WebGL is disabled
+function overlay() {
+	overlay = document.getElementById("overlay");
+	overlay.style.visibility = (overlay.style.visibility == "visible") ? "hidden" : "visible";
+	parallel = document.getElementById("menu");
+	parallel.style.visibility = (parallel.style.visibility == "invisible") ? "visible" : "hidden";
+	separator = document.getElementById("separator");
+	separator.style.visibility = (separator.style.visibility == "invisible") ? "visible" : "hidden";
+	plotToggle = document.getElementById("plotToggle");
+	plotToggle.style.visibility = (plotToggle.style.visibility == "invisible") ? "visible" : "hidden";
+}
+
+//Toggles fullscreen when double-clicking the separator
+function separatorDoubleClick() {
+	if (g_separator_left > 0.98) {
+		if (g_separator_history/$(window).width() < .5) {
+			g_separator_history = $(window).width()*.5;
+			resetDivSizes(g_separator_history);
+		}
+		else {
+			resetDivSizes(g_separator_history);
+		}
+	}
+	else {
+		resetDivSizes($(window).width()*0.99);
+	}
+	aspectReset();	
+}
+
+/*Setup and initialization function for the whole system
+
+  This function will set all of the WebGL elements that are required to exist
+  for the plot to work properly. This in turn will draw the ellipses, spheres
+  and all the other elements that could be part of a plot.
+*/
+$(document).ready(function() {
+	setJqueryUi()
+	
+	// Default sizes: g_separator_left is in percent and the others are in decimal
+	g_separator_left = 0.73;
+	g_separator_history = $(window).width()*0.73;
+	containmentLeft = $(window).width()*0.5;
+	containmentRight = $(window).width()*0.99;
+	// Detecting that webgl is activated
+	if ( ! Detector.webgl ) {
+		overlay();
+	}
+	var main_plot = $('#main_plot');
+	var particles, geometry, parameters, i, h, color;
+	var mouseX = 0, mouseY = 0;
+
+	var winWidth = Math.min(document.getElementById('pcoaPlotWrapper').offsetWidth,document.getElementById('pcoaPlotWrapper').offsetHeight), view_angle = 35, view_near = 0.0000001, view_far = 10000;
+	var winAspect = document.getElementById('pcoaPlotWrapper').offsetWidth/document.getElementById('pcoaPlotWrapper').offsetHeight;
+
+	$(window).resize(function() {	
+		aspectReset();
+		separator_draggable();
+	});
+	
+	separator_draggable();
+	
+	// Validating the string for the saveas filename = taken from http://stackoverflow.com/questions/6741175/trim-input-field-value-to-only-alphanumeric-characters-separate-spaces-with-wi
+    $('#saveas_name').keypress(function(event) {
+        var code = (event.keyCode ? event.keyCode : event.which);
+        if (g_validAsciiCodes.indexOf(code)==-1)
+            event.preventDefault();
+    });
+    
+    // Disables the enter key in the search bar
+    $('#searchBox').keypress(function(event) {
+    	if (event.keyCode == 13) {
+        	event.preventDefault();
+    	}
+	});
+
+	init();
+	animate();
+
+	function init() {
+		// assign a position to the camera befor associating it with other
+		// objects, else the original position will be lost and not make sense
+		g_sceneCamera = new THREE.PerspectiveCamera(view_angle, winAspect, view_near, view_far);
+		g_sceneCamera.position.set(0, 0, 0);
+
+		$('#main_plot canvas').attr('width',document.getElementById('pcoaPlotWrapper').offsetWidth);
+		$('#main_plot canvas').attr('height',document.getElementById('pcoaPlotWrapper').offsetHeight);
+
+		g_mainScene = new THREE.Scene();
+		g_mainScene.fog = new THREE.FogExp2( 0x000000, 0.0009);
+		g_mainScene.add(g_sceneCamera);
+
+		g_genericSphere = new THREE.SphereGeometry(g_radius, g_segments, g_rings);
+		g_elementsGroup = new THREE.Object3D();
+		g_mainScene.add(g_elementsGroup);
+
+		drawSpheres();
+		drawEllipses();
+		drawTaxa();
+		drawVectors();
+		drawEdges(g_comparisonPositions);
+
+		// set some of the scene properties
+		g_plotIds = g_plotIds.sort();
+		g_visiblePoints = g_plotIds.length;
+		changePointCount(g_visiblePoints)
+
+		// given that labels are turned off by default, leave a place holder
+		var line = "";
+		$("#labelcombo").append("<option>Select A Category...</option>");
+
+		// this sorted list of headers is only used in the following loop
+		// to create the 'color by', 'show by' and 'label by' drop-down menus
+		sortedMappingFileHeaders = _splitAndSortNumericAndAlpha(g_mappingFileHeaders)
+		for(var i in sortedMappingFileHeaders){
+			var temp = [];
+			for(var j in g_plotIds) {
+				if(g_mappingFileData[g_plotIds[j]] == undefined){
+					console.warning(g_plotIds[j] +" not in mapping")
+					continue
+				}
+				temp.push(g_mappingFileData[g_plotIds[j]][i])
+			}
+			temp = dedupe(temp);
+			
+			// note that each category is added to all the dropdown menus in the
+			// user interface, these are declared in _EMPEROR_FOOTER_HTML_STRING
+			if (i==0) {
+			    line = "<option selected value=\""+sortedMappingFileHeaders[i]+"\">"+sortedMappingFileHeaders[i]+"</option>"
+			} else {
+			    line = "<option value=\""+sortedMappingFileHeaders[i]+"\">"+sortedMappingFileHeaders[i]+"</option>"
+			}
+			$("#colorbycombo").append(line);
+			$("#scalingbycombo").append(line);
+			$("#showbycombo").append(line);
+			$("#labelcombo").append(line);
+		}
+
+		setParallelPlots();
+
+		colorByMenuChanged();
+		showByMenuChanged();
+		scalingByMenuChanged();
+
+		togglePlots();
+
+		// the light is attached to the camera to provide a 3d perspective
+		g_sceneLight = new THREE.DirectionalLight(0x999999, 2);
+		g_sceneLight.position.set(1,1,1).normalize();
+		g_sceneCamera.add(g_sceneLight);
+
+		// Adding camera
+		g_sceneControl = new THREE.TrackballControls(g_sceneCamera, document.getElementById('main_plot'));
+		g_sceneControl.rotateSpeed = 1.0;
+		g_sceneControl.zoomSpeed = 1.2;
+		g_sceneControl.panSpeed = 0.8;
+		g_sceneControl.noZoom = false;
+		g_sceneControl.noPan = false;
+		g_sceneControl.staticMoving = true;
+		g_sceneControl.dynamicDampingFactor = 0.3;
+		g_sceneControl.keys = [ 65, 83, 68 ];
+
+		// black is the default background color for the scene
+		var rendererBackgroundColor = new THREE.Color();
+		rendererBackgroundColor.setHex("0x000000");
+
+		// renderer, the default background color is black
+		g_mainRenderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
+        
+        // adding 'ctrl+p' to print screenshot
+        g_screenshotBind = THREEx.Screenshot.bindKey(g_mainRenderer, {charCode: 16});
+        
+		g_mainRenderer.setClearColor(rendererBackgroundColor, 1);
+		g_mainRenderer.setSize( document.getElementById('pcoaPlotWrapper').offsetWidth, document.getElementById('pcoaPlotWrapper').offsetHeight );
+		g_mainRenderer.sortObjects = true;
+		main_plot.append(g_mainRenderer.domElement);
+
+		// build divs to hold point labels and position them
+		var labelshtml = "";
+		for(var i in g_plotIds) {
+			var sid = g_plotIds[i];
+			var divid = sid.replace(/\./g,'');
+			mesh = g_plotSpheres[sid];
+			var coords = toScreenXY(mesh.position,g_sceneCamera,$('#main_plot'));
+			labelshtml += "<label id=\""+divid+"_label\" class=\"unselectable labels\" style=\"position:absolute; left:"+parseInt(coords['x'])+"px; top:"+parseInt(coords['y'])+"px;\">";
+			labelshtml += sid;
+			labelshtml += "</label>";
+		}
+		document.getElementById("labels").innerHTML = labelshtml;
+
+		labelshtml = "";
+		// add the labels with the taxonomic lineages to the taxalabels div
+		for(var key in g_taxaPositions){
+
+			// get the coordinate of this taxa sphere
+			var coords = toScreenXY(g_plotTaxa[key].position,g_sceneCamera,$('#main_plot'));
+
+			// labels are identified by the key they have in g_taxaPositions
+			labelshtml += "<label id=\""+key+"_taxalabel\" class=\"unselectable labels\" style=\"position:absolute; left:"+parseInt(coords['x'])+"px; top:"+parseInt(coords['y'])+"px;\">";
+			labelshtml += g_taxaPositions[key]['lineage'];
+			labelshtml += "</label>";
+		}
+		document.getElementById("taxalabels").innerHTML = labelshtml
+		
+		// adding values for axes to display
+		drawMenuAxesDisplayed();
+		changeAxesDisplayed();
+		drawAxisLines();
+
+		buildAxisLabels();
+	}
+
+	function drawMenuAxesDisplayed() {
+		if (!jQuery.isEmptyObject(g_vectorPositions) || !jQuery.isEmptyObject(g_taxaPositions) ||
+			!jQuery.isEmptyObject(g_ellipsesDimensions) || g_number_of_custom_axes!=0) {
+			text = '<table width="100%%">';
+			text += '<tr><td><font color="red">This is disabled for custom axes, biplots, vectors, and jackknifed</font></td></tr>';
+			text += '</table>';
+			document.getElementById("axeslist").innerHTML = text;
+			return;
+		}
+
+		text = '<table border="0" width="80%">';
+
+		// Adding 1st axis
+		text += '<tr>'
+		text += '<td width="40px" class="unselectable lables">Axis 1:</td>'
+		text += '<td><select id="pc1_axis" onchange="clean_label_refresh_axes();">';
+
+		for (var i=1; i < g_fractionExplainedRounded.length + 1; i++) {
+			if (i==1) {
+				text += '<option selected value="P' + i + '">P' + i + " (" + g_fractionExplainedRounded[i-1] + "%)" + '</option>';
+			} else {
+				text += '<option value="P' + i + '">P' + i + " (" + g_fractionExplainedRounded[i-1] + "%)" + '</option>';
+			}
+		}
+		text += '</select></td>'
+		text += '<td>Negate values:<input id="flip_axes_1" class="checkbox" type="checkbox" style=""></td>';
+		text += '</tr>';
+
+		// Adding 2nd axis
+		text += '<tr>'
+		text += '<td width="40px" class="unselectable lables">Axis 2:</td>'
+		text += '<td><select id="pc2_axis" onchange="clean_label_refresh_axes();">';
+		for (var i=1; i < g_fractionExplained.length + 1; i++) {
+			if (i==2) {
+				text += '<option selected value="P' + i + '">P' + i + " (" + g_fractionExplainedRounded[i-1] + "%)" + '</option>';
+			} else {
+				text += '<option value="P' + i + '">P' + i + " (" + g_fractionExplainedRounded[i-1] + "%)" + '</option>';
+			}
+		}
+		text += '</select></td>'
+		text += '<td>Negate values:<input id="flip_axes_2" class="checkbox" type="checkbox" style=""></td>';
+		text += '</tr>';
+
+		// Adding 3rd axis
+		text += '<tr>'
+		text += '<td width="40px" class="unselectable lables">Axis 3:</td>'
+		text += '<td><select id="pc3_axis" onchange="clean_label_refresh_axes();">';
+		for (var i=1; i < g_fractionExplained.length + 1; i++) {
+			if (i==3) {
+				text += '<option selected value="P' + i + '">P' + i + " (" + g_fractionExplainedRounded[i-1] + "%)" + '</option>';
+			} else {
+				text += '<option value="P' + i + '">P' + i + " (" + g_fractionExplainedRounded[i-1] + "%)" + '</option>';
+			}
+		}
+		text += '</select></td>'
+		text += '<td>Negate values:<input id="flip_axes_3" class="checkbox" type="checkbox" style=""></td>';
+		text += '</tr>';
+		text += '</table>';
+
+		// Adding button
+		text += '<table width="100%%"><tr>';
+		text += '<td width="20px"><input type="button" value="Refresh" onclick="changeAxesDisplayed();"></td>';
+		text += '<td id="refresh_axes_label"></td></tr>';
+		text += '</table>';
+		document.getElementById("axeslist").innerHTML = text;
+	}
+
+	function animate() {
+		requestAnimationFrame( animate );
+
+		render();
+
+		var labelCoordinates;
+
+		// reposition the labels for the axes in the 3D plot
+		labelCoordinates = toScreenXY(new THREE.Vector3(g_xMaximumValue, g_yMinimumValue, g_zMinimumValue), g_sceneCamera,$('#main_plot'));
+		$("#pc1_label").css('left', labelCoordinates['x'])
+		$("#pc1_label").css('top', labelCoordinates['y'])
+		labelCoordinates = toScreenXY(new THREE.Vector3(g_xMinimumValue, g_yMaximumValue, g_zMinimumValue), g_sceneCamera,$('#main_plot'));
+		$("#pc2_label").css('left', labelCoordinates['x'])
+		$("#pc2_label").css('top', labelCoordinates['y'])
+		labelCoordinates = toScreenXY(new THREE.Vector3(g_xMinimumValue, g_yMinimumValue, g_zMaximumValue), g_sceneCamera,$('#main_plot'));
+		$("#pc3_label").css('left', labelCoordinates['x'])
+		$("#pc3_label").css('top', labelCoordinates['y'])
+
+
+		// move labels when the plot is moved
+		if(document.plotoptions.elements[0].checked){
+			for(var i in g_plotIds) {
+				var sid = g_plotIds[i];
+				mesh = g_plotSpheres[sid];
+				var coords = toScreenXY(mesh.position, g_sceneCamera, $('#main_plot'));
+				var divid = sid.replace(/\./g,'');
+				$('#'+divid+"_label").css('left',coords['x']);
+				$('#'+divid+"_label").css('top',coords['y']);
+			}
+		}
+		// check if you have to reposition the taxa labels for each frame
+		// this is something that will only happen when drawing biplots
+		if(document.biplotoptions){
+			if(document.biplotoptions.elements[0].checked){
+				for(var key in g_taxaPositions) {
+					// retrieve the position of the taxa on screen
+					var coords = toScreenXY(g_plotTaxa[key].position,
+						g_sceneCamera, $('#main_plot'));
+
+					// add the label at the appropriate position
+					$('#'+key+"_taxalabel").css('left',coords['x']);
+					$('#'+key+"_taxalabel").css('top',coords['y']);
+				}
+			}
+		}
+		if(g_foundId) {
+			var coords = toScreenXY(g_plotSpheres[g_foundId].position, g_sceneCamera, $('#main_plot'));
+			$('#finder').css('left',coords['x']-15);
+			$('#finder').css('top',coords['y']-5);
+		}
+	}
+   
+	function render() {
+		g_sceneControl.update();
+		g_mainRenderer.setSize( document.getElementById('pcoaPlotWrapper').offsetWidth, document.getElementById('pcoaPlotWrapper').offsetHeight );
+		g_mainRenderer.render( g_mainScene, g_sceneCamera);
+	}
+	
+});
diff --git a/emperor/support_files/img/emperor.png b/emperor/support_files/img/emperor.png
new file mode 100644
index 0000000..652c549
Binary files /dev/null and b/emperor/support_files/img/emperor.png differ
diff --git a/emperor/support_files/img/favicon.ico b/emperor/support_files/img/favicon.ico
new file mode 100644
index 0000000..f2a98a3
Binary files /dev/null and b/emperor/support_files/img/favicon.ico differ
diff --git a/emperor/support_files/img/pause.png b/emperor/support_files/img/pause.png
new file mode 100644
index 0000000..e7968ae
Binary files /dev/null and b/emperor/support_files/img/pause.png differ
diff --git a/emperor/support_files/img/play.png b/emperor/support_files/img/play.png
new file mode 100644
index 0000000..4a00458
Binary files /dev/null and b/emperor/support_files/img/play.png differ
diff --git a/emperor/support_files/img/reset.png b/emperor/support_files/img/reset.png
new file mode 100644
index 0000000..699d42d
Binary files /dev/null and b/emperor/support_files/img/reset.png differ
diff --git a/emperor/support_files/js/FileSaver.min.js b/emperor/support_files/js/FileSaver.min.js
new file mode 100755
index 0000000..f69f857
--- /dev/null
+++ b/emperor/support_files/js/FileSaver.min.js
@@ -0,0 +1,2 @@
+/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
+var saveAs=saveAs||navigator.msSaveBlob&&navigator.msSaveBlob.bind(navigator)||function(e){"use strict";var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=e.URL||e.webkitURL||e,i=t.createElementNS("http://www.w3.org/1999/xhtml","a"),s="download"in i,o=function(n){var r=t.createEvent("MouseEvents");r.initMouseEvent("click",true,false,e,0,0,0,0,0,false,false,false,false,0,null);n.dispatchEvent(r)},u=e.webkitRequestFileSystem,a=e.requestFileSystem||u||e.mozRequestFileSystem,f=fun [...]
diff --git a/emperor/support_files/js/THREEx.screenshot.js b/emperor/support_files/js/THREEx.screenshot.js
new file mode 100644
index 0000000..180f531
--- /dev/null
+++ b/emperor/support_files/js/THREEx.screenshot.js
@@ -0,0 +1,130 @@
+/** @namespace */
+var THREEx	= THREEx 		|| {};
+
+// TODO http://29a.ch/2011/9/11/uploading-from-html5-canvas-to-imgur-data-uri
+// able to upload your screenshot without running servers
+
+// forced closure
+(function(){
+
+	/**
+	 * Take a screenshot of a renderer
+	 * - require WebGLRenderer to have "preserveDrawingBuffer: true" to be set
+	 * - TODO is it possible to check if this variable is set ? if so check it
+	 *   and make advice in the console.log
+	 *   - maybe with direct access to the gl context...
+	 * 
+	 * @param {Object} renderer to use
+	 * @param {String} mimetype of the output image. default to "image/png"
+	 * @param {String} dataUrl of the image
+	*/
+	var toDataURL	= function(renderer, mimetype)
+	{
+		mimetype	= mimetype	|| "image/png";
+		var dataUrl	= renderer.domElement.toDataURL(mimetype);
+		return dataUrl;
+	}
+
+	/**
+	 * resize an image to another resolution while preserving aspect
+	 *
+	 * @param {String} srcUrl the url of the image to resize
+	 * @param {Number} dstWidth the destination width of the image
+	 * @param {Number} dstHeight the destination height of the image
+	 * @param {Number} callback the callback to notify once completed with callback(newImageUrl)
+	*/
+	var _aspectResize	= function(srcUrl, dstW, dstH, callback){
+		// to compute the width/height while keeping aspect
+		var cpuScaleAspect	= function(maxW, maxH, curW, curH){
+			var ratio	= curH / curW;
+			if( curW >= maxW && ratio <= 1 ){ 
+				curW	= maxW;
+				curH	= maxW * ratio;
+			}else if(curH >= maxH){
+				curH	= maxH;
+				curW	= maxH / ratio;
+			}
+			return { width: curW, height: curH };
+		}
+		// callback once the image is loaded
+		var onLoad	= function(){
+			// init the canvas
+			var canvas	= document.createElement('canvas');
+			canvas.width	= dstW;	canvas.height	= dstH;
+			var ctx		= canvas.getContext('2d');
+
+			// TODO is this needed
+			ctx.fillStyle	= "black";
+			ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+			// scale the image while preserving the aspect
+			var scaled	= cpuScaleAspect(canvas.width, canvas.height, image.width, image.height);
+
+			// actually draw the image on canvas
+			var offsetX	= (canvas.width  - scaled.width )/2;
+			var offsetY	= (canvas.height - scaled.height)/2;
+			ctx.drawImage(image, offsetX, offsetY, scaled.width, scaled.height);
+
+			// dump the canvas to an URL		
+			var mimetype	= "image/png";
+			var newDataUrl	= canvas.toDataURL(mimetype);
+			// notify the url to the caller
+			callback && callback(newDataUrl)
+		}.bind(this);
+
+		// Create new Image object
+		var image 	= new Image();
+		image.onload	= onLoad;
+		image.src	= srcUrl;
+	}
+	
+
+	// Super cooked function: THREEx.Screenshot.bindKey(renderer)
+	// and you are done to get screenshot on your demo
+
+	/**
+	 * Bind a key to renderer screenshot
+	*/
+	var bindKey	= function(renderer, opts){
+		// handle parameters
+		opts		= opts		|| {};
+		var charCode	= opts.charCode	|| 'p'.charCodeAt(0);
+		var width	= opts.width;
+		var height	= opts.height;
+		var callback	= opts.callback	|| function(url){
+			window.open(url, "name-"+Math.random());
+		};
+
+		// callback to handle keypress
+		var onKeyPress	= function(event){
+		    // return now if the KeyPress isnt for the proper charCode
+			if( event.which !== charCode )	return;
+			// get the renderer output
+			var dataUrl	= this.toDataURL(renderer);
+			
+			if( width === undefined && height === undefined ){
+				callback( dataUrl )
+			}else{
+				// resize it and notify the callback
+				// * resize == async so if callback is a window open, it triggers the pop blocker
+				_aspectResize(dataUrl, width, height, callback);				
+			}
+		}.bind(this);
+
+		// listen to keypress
+		// NOTE: for firefox it seems mandatory to listen to document directly
+		document.addEventListener('keypress', onKeyPress, false);
+
+		return {
+			unbind	: function(){
+				document.removeEventListener('keypress', onKeyPress, false);
+			}
+		};
+	}
+
+	// export it	
+	THREEx.Screenshot	= {
+		toDataURL	: toDataURL,
+		bindKey		: bindKey
+	};
+})();
\ No newline at end of file
diff --git a/emperor/support_files/js/Three.js b/emperor/support_files/js/Three.js
new file mode 100644
index 0000000..3d249dd
--- /dev/null
+++ b/emperor/support_files/js/Three.js
@@ -0,0 +1,37637 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author Larry Battle / http://bateru.com/news
+ */
+
+var THREE = THREE || { REVISION: '58' };
+
+self.console = self.console || {
+
+	info: function () {},
+	log: function () {},
+	debug: function () {},
+	warn: function () {},
+	error: function () {}
+
+};
+
+self.Int32Array = self.Int32Array || Array;
+self.Float32Array = self.Float32Array || Array;
+
+String.prototype.trim = String.prototype.trim || function () {
+
+	return this.replace( /^\s+|\s+$/g, '' );
+
+};
+
+// based on https://github.com/documentcloud/underscore/blob/bf657be243a075b5e72acc8a83e6f12a564d8f55/underscore.js#L767
+THREE.extend = function ( obj, source ) {
+
+	// ECMAScript5 compatibility based on: http://www.nczonline.net/blog/2012/12/11/are-your-mixins-ecmascript-5-compatible/
+	if ( Object.keys ) {
+
+		var keys = Object.keys( source );
+
+		for (var i = 0, il = keys.length; i < il; i++) {
+
+			var prop = keys[i];
+			Object.defineProperty( obj, prop, Object.getOwnPropertyDescriptor( source, prop ) );
+
+		}
+
+	} else {
+
+		var safeHasOwnProperty = {}.hasOwnProperty;
+
+		for ( var prop in source ) {
+
+			if ( safeHasOwnProperty.call( source, prop ) ) {
+
+				obj[prop] = source[prop];
+
+			}
+
+		}
+
+	}
+
+	return obj;
+
+};
+
+// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
+
+// requestAnimationFrame polyfill by Erik Möller
+// fixes from Paul Irish and Tino Zijdel
+
+( function () {
+
+	var lastTime = 0;
+	var vendors = [ 'ms', 'moz', 'webkit', 'o' ];
+
+	for ( var x = 0; x < vendors.length && !window.requestAnimationFrame; ++ x ) {
+
+		window.requestAnimationFrame = window[ vendors[ x ] + 'RequestAnimationFrame' ];
+		window.cancelAnimationFrame = window[ vendors[ x ] + 'CancelAnimationFrame' ] || window[ vendors[ x ] + 'CancelRequestAnimationFrame' ];
+
+	}
+
+	if ( window.requestAnimationFrame === undefined ) {
+
+		window.requestAnimationFrame = function ( callback ) {
+
+			var currTime = Date.now(), timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
+			var id = window.setTimeout( function() { callback( currTime + timeToCall ); }, timeToCall );
+			lastTime = currTime + timeToCall;
+			return id;
+
+		};
+
+	}
+
+	window.cancelAnimationFrame = window.cancelAnimationFrame || function ( id ) { window.clearTimeout( id ) };
+
+}() );
+
+// GL STATE CONSTANTS
+
+THREE.CullFaceNone = 0;
+THREE.CullFaceBack = 1;
+THREE.CullFaceFront = 2;
+THREE.CullFaceFrontBack = 3;
+
+THREE.FrontFaceDirectionCW = 0;
+THREE.FrontFaceDirectionCCW = 1;
+
+// SHADOWING TYPES
+
+THREE.BasicShadowMap = 0;
+THREE.PCFShadowMap = 1;
+THREE.PCFSoftShadowMap = 2;
+
+// MATERIAL CONSTANTS
+
+// side
+
+THREE.FrontSide = 0;
+THREE.BackSide = 1;
+THREE.DoubleSide = 2;
+
+// shading
+
+THREE.NoShading = 0;
+THREE.FlatShading = 1;
+THREE.SmoothShading = 2;
+
+// colors
+
+THREE.NoColors = 0;
+THREE.FaceColors = 1;
+THREE.VertexColors = 2;
+
+// blending modes
+
+THREE.NoBlending = 0;
+THREE.NormalBlending = 1;
+THREE.AdditiveBlending = 2;
+THREE.SubtractiveBlending = 3;
+THREE.MultiplyBlending = 4;
+THREE.CustomBlending = 5;
+
+// custom blending equations
+// (numbers start from 100 not to clash with other
+//  mappings to OpenGL constants defined in Texture.js)
+
+THREE.AddEquation = 100;
+THREE.SubtractEquation = 101;
+THREE.ReverseSubtractEquation = 102;
+
+// custom blending destination factors
+
+THREE.ZeroFactor = 200;
+THREE.OneFactor = 201;
+THREE.SrcColorFactor = 202;
+THREE.OneMinusSrcColorFactor = 203;
+THREE.SrcAlphaFactor = 204;
+THREE.OneMinusSrcAlphaFactor = 205;
+THREE.DstAlphaFactor = 206;
+THREE.OneMinusDstAlphaFactor = 207;
+
+// custom blending source factors
+
+//THREE.ZeroFactor = 200;
+//THREE.OneFactor = 201;
+//THREE.SrcAlphaFactor = 204;
+//THREE.OneMinusSrcAlphaFactor = 205;
+//THREE.DstAlphaFactor = 206;
+//THREE.OneMinusDstAlphaFactor = 207;
+THREE.DstColorFactor = 208;
+THREE.OneMinusDstColorFactor = 209;
+THREE.SrcAlphaSaturateFactor = 210;
+
+
+// TEXTURE CONSTANTS
+
+THREE.MultiplyOperation = 0;
+THREE.MixOperation = 1;
+THREE.AddOperation = 2;
+
+// Mapping modes
+
+THREE.UVMapping = function () {};
+
+THREE.CubeReflectionMapping = function () {};
+THREE.CubeRefractionMapping = function () {};
+
+THREE.SphericalReflectionMapping = function () {};
+THREE.SphericalRefractionMapping = function () {};
+
+// Wrapping modes
+
+THREE.RepeatWrapping = 1000;
+THREE.ClampToEdgeWrapping = 1001;
+THREE.MirroredRepeatWrapping = 1002;
+
+// Filters
+
+THREE.NearestFilter = 1003;
+THREE.NearestMipMapNearestFilter = 1004;
+THREE.NearestMipMapLinearFilter = 1005;
+THREE.LinearFilter = 1006;
+THREE.LinearMipMapNearestFilter = 1007;
+THREE.LinearMipMapLinearFilter = 1008;
+
+// Data types
+
+THREE.UnsignedByteType = 1009;
+THREE.ByteType = 1010;
+THREE.ShortType = 1011;
+THREE.UnsignedShortType = 1012;
+THREE.IntType = 1013;
+THREE.UnsignedIntType = 1014;
+THREE.FloatType = 1015;
+
+// Pixel types
+
+//THREE.UnsignedByteType = 1009;
+THREE.UnsignedShort4444Type = 1016;
+THREE.UnsignedShort5551Type = 1017;
+THREE.UnsignedShort565Type = 1018;
+
+// Pixel formats
+
+THREE.AlphaFormat = 1019;
+THREE.RGBFormat = 1020;
+THREE.RGBAFormat = 1021;
+THREE.LuminanceFormat = 1022;
+THREE.LuminanceAlphaFormat = 1023;
+
+// Compressed texture formats
+
+THREE.RGB_S3TC_DXT1_Format = 2001;
+THREE.RGBA_S3TC_DXT1_Format = 2002;
+THREE.RGBA_S3TC_DXT3_Format = 2003;
+THREE.RGBA_S3TC_DXT5_Format = 2004;
+
+/*
+// Potential future PVRTC compressed texture formats
+THREE.RGB_PVRTC_4BPPV1_Format = 2100;
+THREE.RGB_PVRTC_2BPPV1_Format = 2101;
+THREE.RGBA_PVRTC_4BPPV1_Format = 2102;
+THREE.RGBA_PVRTC_2BPPV1_Format = 2103;
+*/
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Color = function ( value ) {
+
+	if ( value !== undefined ) this.set( value );
+
+	return this;
+
+};
+
+THREE.Color.prototype = {
+
+	constructor: THREE.Color,
+
+	r: 1, g: 1, b: 1,
+
+	set: function ( value ) {
+
+		if ( value instanceof THREE.Color ) {
+
+			this.copy( value );
+
+		} else if ( typeof value === 'number' ) {
+
+			this.setHex( value );
+
+		} else if ( typeof value === 'string' ) {
+
+			this.setStyle( value );
+
+		}
+
+		return this;
+
+	},
+
+	setHex: function ( hex ) {
+
+		hex = Math.floor( hex );
+
+		this.r = ( hex >> 16 & 255 ) / 255;
+		this.g = ( hex >> 8 & 255 ) / 255;
+		this.b = ( hex & 255 ) / 255;
+
+		return this;
+
+	},
+
+	setRGB: function ( r, g, b ) {
+
+		this.r = r;
+		this.g = g;
+		this.b = b;
+
+		return this;
+
+	},
+
+	setHSL: function ( h, s, l ) {
+
+		// h,s,l ranges are in 0.0 - 1.0
+
+		if ( s === 0 ) {
+
+			this.r = this.g = this.b = l;
+
+		} else {
+
+			var hue2rgb = function ( p, q, t ) {
+
+				if ( t < 0 ) t += 1;
+				if ( t > 1 ) t -= 1;
+				if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t;
+				if ( t < 1 / 2 ) return q;
+				if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t );
+				return p;
+
+			};
+
+			var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );
+			var q = ( 2 * l ) - p;
+
+			this.r = hue2rgb( q, p, h + 1 / 3 );
+			this.g = hue2rgb( q, p, h );
+			this.b = hue2rgb( q, p, h - 1 / 3 );
+
+		}
+
+		return this;
+
+	},
+
+	setStyle: function ( style ) {
+
+		// rgb(255,0,0)
+
+		if ( /^rgb\((\d+),(\d+),(\d+)\)$/i.test( style ) ) {
+
+			var color = /^rgb\((\d+),(\d+),(\d+)\)$/i.exec( style );
+
+			this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255;
+			this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255;
+			this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255;
+
+			return this;
+
+		}
+
+		// rgb(100%,0%,0%)
+
+		if ( /^rgb\((\d+)\%,(\d+)\%,(\d+)\%\)$/i.test( style ) ) {
+
+			var color = /^rgb\((\d+)\%,(\d+)\%,(\d+)\%\)$/i.exec( style );
+
+			this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100;
+			this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100;
+			this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100;
+
+			return this;
+
+		}
+
+		// #ff0000
+
+		if ( /^\#([0-9a-f]{6})$/i.test( style ) ) {
+
+			var color = /^\#([0-9a-f]{6})$/i.exec( style );
+
+			this.setHex( parseInt( color[ 1 ], 16 ) );
+
+			return this;
+
+		}
+
+		// #f00
+
+		if ( /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test( style ) ) {
+
+			var color = /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec( style );
+
+			this.setHex( parseInt( color[ 1 ] + color[ 1 ] + color[ 2 ] + color[ 2 ] + color[ 3 ] + color[ 3 ], 16 ) );
+
+			return this;
+
+		}
+
+		// red
+
+		if ( /^(\w+)$/i.test( style ) ) {
+
+			this.setHex( THREE.ColorKeywords[ style ] );
+
+			return this;
+
+		}
+
+
+	},
+
+	copy: function ( color ) {
+
+		this.r = color.r;
+		this.g = color.g;
+		this.b = color.b;
+
+		return this;
+
+	},
+
+	copyGammaToLinear: function ( color ) {
+
+		this.r = color.r * color.r;
+		this.g = color.g * color.g;
+		this.b = color.b * color.b;
+
+		return this;
+
+	},
+
+	copyLinearToGamma: function ( color ) {
+
+		this.r = Math.sqrt( color.r );
+		this.g = Math.sqrt( color.g );
+		this.b = Math.sqrt( color.b );
+
+		return this;
+
+	},
+
+	convertGammaToLinear: function () {
+
+		var r = this.r, g = this.g, b = this.b;
+
+		this.r = r * r;
+		this.g = g * g;
+		this.b = b * b;
+
+		return this;
+
+	},
+
+	convertLinearToGamma: function () {
+
+		this.r = Math.sqrt( this.r );
+		this.g = Math.sqrt( this.g );
+		this.b = Math.sqrt( this.b );
+
+		return this;
+
+	},
+
+	getHex: function () {
+
+		return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0;
+
+	},
+
+	getHexString: function () {
+
+		return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
+
+	},
+
+	getHSL: function () {
+
+		var hsl = { h: 0, s: 0, l: 0 };
+
+		return function () {
+
+			// h,s,l ranges are in 0.0 - 1.0
+
+			var r = this.r, g = this.g, b = this.b;
+
+			var max = Math.max( r, g, b );
+			var min = Math.min( r, g, b );
+
+			var hue, saturation;
+			var lightness = ( min + max ) / 2.0;
+
+			if ( min === max ) {
+
+				hue = 0;
+				saturation = 0;
+
+			} else {
+
+				var delta = max - min;
+
+				saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min );
+
+				switch ( max ) {
+
+					case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break;
+					case g: hue = ( b - r ) / delta + 2; break;
+					case b: hue = ( r - g ) / delta + 4; break;
+
+				}
+
+				hue /= 6;
+
+			}
+
+			hsl.h = hue;
+			hsl.s = saturation;
+			hsl.l = lightness;
+
+			return hsl;
+
+		};
+
+	}(),
+
+	getStyle: function () {
+
+		return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')';
+
+	},
+
+	offsetHSL: function ( h, s, l ) {
+
+		var hsl = this.getHSL();
+
+		hsl.h += h; hsl.s += s; hsl.l += l;
+
+		this.setHSL( hsl.h, hsl.s, hsl.l );
+
+		return this;
+
+	},
+
+	add: function ( color ) {
+
+		this.r += color.r;
+		this.g += color.g;
+		this.b += color.b;
+
+		return this;
+
+	},
+
+	addColors: function ( color1, color2 ) {
+
+		this.r = color1.r + color2.r;
+		this.g = color1.g + color2.g;
+		this.b = color1.b + color2.b;
+
+		return this;
+
+	},
+
+	addScalar: function ( s ) {
+
+		this.r += s;
+		this.g += s;
+		this.b += s;
+
+		return this;
+
+	},
+
+	multiply: function ( color ) {
+
+		this.r *= color.r;
+		this.g *= color.g;
+		this.b *= color.b;
+
+		return this;
+
+	},
+
+	multiplyScalar: function ( s ) {
+
+		this.r *= s;
+		this.g *= s;
+		this.b *= s;
+
+		return this;
+
+	},
+
+	lerp: function ( color, alpha ) {
+
+		this.r += ( color.r - this.r ) * alpha;
+		this.g += ( color.g - this.g ) * alpha;
+		this.b += ( color.b - this.b ) * alpha;
+
+		return this;
+
+	},
+
+	equals: function ( c ) {
+
+		return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b );
+
+	},
+
+	clone: function () {
+
+		return new THREE.Color().setRGB( this.r, this.g, this.b );
+
+	}
+
+};
+
+THREE.ColorKeywords = { "aliceblue": 0xF0F8FF, "antiquewhite": 0xFAEBD7, "aqua": 0x00FFFF, "aquamarine": 0x7FFFD4, "azure": 0xF0FFFF,
+"beige": 0xF5F5DC, "bisque": 0xFFE4C4, "black": 0x000000, "blanchedalmond": 0xFFEBCD, "blue": 0x0000FF, "blueviolet": 0x8A2BE2,
+"brown": 0xA52A2A, "burlywood": 0xDEB887, "cadetblue": 0x5F9EA0, "chartreuse": 0x7FFF00, "chocolate": 0xD2691E, "coral": 0xFF7F50,
+"cornflowerblue": 0x6495ED, "cornsilk": 0xFFF8DC, "crimson": 0xDC143C, "cyan": 0x00FFFF, "darkblue": 0x00008B, "darkcyan": 0x008B8B,
+"darkgoldenrod": 0xB8860B, "darkgray": 0xA9A9A9, "darkgreen": 0x006400, "darkgrey": 0xA9A9A9, "darkkhaki": 0xBDB76B, "darkmagenta": 0x8B008B,
+"darkolivegreen": 0x556B2F, "darkorange": 0xFF8C00, "darkorchid": 0x9932CC, "darkred": 0x8B0000, "darksalmon": 0xE9967A, "darkseagreen": 0x8FBC8F,
+"darkslateblue": 0x483D8B, "darkslategray": 0x2F4F4F, "darkslategrey": 0x2F4F4F, "darkturquoise": 0x00CED1, "darkviolet": 0x9400D3,
+"deeppink": 0xFF1493, "deepskyblue": 0x00BFFF, "dimgray": 0x696969, "dimgrey": 0x696969, "dodgerblue": 0x1E90FF, "firebrick": 0xB22222,
+"floralwhite": 0xFFFAF0, "forestgreen": 0x228B22, "fuchsia": 0xFF00FF, "gainsboro": 0xDCDCDC, "ghostwhite": 0xF8F8FF, "gold": 0xFFD700,
+"goldenrod": 0xDAA520, "gray": 0x808080, "green": 0x008000, "greenyellow": 0xADFF2F, "grey": 0x808080, "honeydew": 0xF0FFF0, "hotpink": 0xFF69B4,
+"indianred": 0xCD5C5C, "indigo": 0x4B0082, "ivory": 0xFFFFF0, "khaki": 0xF0E68C, "lavender": 0xE6E6FA, "lavenderblush": 0xFFF0F5, "lawngreen": 0x7CFC00,
+"lemonchiffon": 0xFFFACD, "lightblue": 0xADD8E6, "lightcoral": 0xF08080, "lightcyan": 0xE0FFFF, "lightgoldenrodyellow": 0xFAFAD2, "lightgray": 0xD3D3D3,
+"lightgreen": 0x90EE90, "lightgrey": 0xD3D3D3, "lightpink": 0xFFB6C1, "lightsalmon": 0xFFA07A, "lightseagreen": 0x20B2AA, "lightskyblue": 0x87CEFA,
+"lightslategray": 0x778899, "lightslategrey": 0x778899, "lightsteelblue": 0xB0C4DE, "lightyellow": 0xFFFFE0, "lime": 0x00FF00, "limegreen": 0x32CD32,
+"linen": 0xFAF0E6, "magenta": 0xFF00FF, "maroon": 0x800000, "mediumaquamarine": 0x66CDAA, "mediumblue": 0x0000CD, "mediumorchid": 0xBA55D3,
+"mediumpurple": 0x9370DB, "mediumseagreen": 0x3CB371, "mediumslateblue": 0x7B68EE, "mediumspringgreen": 0x00FA9A, "mediumturquoise": 0x48D1CC,
+"mediumvioletred": 0xC71585, "midnightblue": 0x191970, "mintcream": 0xF5FFFA, "mistyrose": 0xFFE4E1, "moccasin": 0xFFE4B5, "navajowhite": 0xFFDEAD,
+"navy": 0x000080, "oldlace": 0xFDF5E6, "olive": 0x808000, "olivedrab": 0x6B8E23, "orange": 0xFFA500, "orangered": 0xFF4500, "orchid": 0xDA70D6,
+"palegoldenrod": 0xEEE8AA, "palegreen": 0x98FB98, "paleturquoise": 0xAFEEEE, "palevioletred": 0xDB7093, "papayawhip": 0xFFEFD5, "peachpuff": 0xFFDAB9,
+"peru": 0xCD853F, "pink": 0xFFC0CB, "plum": 0xDDA0DD, "powderblue": 0xB0E0E6, "purple": 0x800080, "red": 0xFF0000, "rosybrown": 0xBC8F8F,
+"royalblue": 0x4169E1, "saddlebrown": 0x8B4513, "salmon": 0xFA8072, "sandybrown": 0xF4A460, "seagreen": 0x2E8B57, "seashell": 0xFFF5EE,
+"sienna": 0xA0522D, "silver": 0xC0C0C0, "skyblue": 0x87CEEB, "slateblue": 0x6A5ACD, "slategray": 0x708090, "slategrey": 0x708090, "snow": 0xFFFAFA,
+"springgreen": 0x00FF7F, "steelblue": 0x4682B4, "tan": 0xD2B48C, "teal": 0x008080, "thistle": 0xD8BFD8, "tomato": 0xFF6347, "turquoise": 0x40E0D0,
+"violet": 0xEE82EE, "wheat": 0xF5DEB3, "white": 0xFFFFFF, "whitesmoke": 0xF5F5F5, "yellow": 0xFFFF00, "yellowgreen": 0x9ACD32 };
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Quaternion = function( x, y, z, w ) {
+
+	this.x = x || 0;
+	this.y = y || 0;
+	this.z = z || 0;
+	this.w = ( w !== undefined ) ? w : 1;
+
+};
+
+THREE.Quaternion.prototype = {
+
+	constructor: THREE.Quaternion,
+
+	set: function ( x, y, z, w ) {
+
+		this.x = x;
+		this.y = y;
+		this.z = z;
+		this.w = w;
+
+		return this;
+
+	},
+
+	copy: function ( q ) {
+
+		this.x = q.x;
+		this.y = q.y;
+		this.z = q.z;
+		this.w = q.w;
+
+		return this;
+
+	},
+
+	setFromEuler: function ( v, order ) {
+
+		// http://www.mathworks.com/matlabcentral/fileexchange/
+		// 	20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
+		//	content/SpinCalc.m
+
+		var c1 = Math.cos( v.x / 2 );
+		var c2 = Math.cos( v.y / 2 );
+		var c3 = Math.cos( v.z / 2 );
+		var s1 = Math.sin( v.x / 2 );
+		var s2 = Math.sin( v.y / 2 );
+		var s3 = Math.sin( v.z / 2 );
+
+		if ( order === undefined || order === 'XYZ' ) {
+
+			this.x = s1 * c2 * c3 + c1 * s2 * s3;
+			this.y = c1 * s2 * c3 - s1 * c2 * s3;
+			this.z = c1 * c2 * s3 + s1 * s2 * c3;
+			this.w = c1 * c2 * c3 - s1 * s2 * s3;
+
+		} else if ( order === 'YXZ' ) {
+
+			this.x = s1 * c2 * c3 + c1 * s2 * s3;
+			this.y = c1 * s2 * c3 - s1 * c2 * s3;
+			this.z = c1 * c2 * s3 - s1 * s2 * c3;
+			this.w = c1 * c2 * c3 + s1 * s2 * s3;
+
+		} else if ( order === 'ZXY' ) {
+
+			this.x = s1 * c2 * c3 - c1 * s2 * s3;
+			this.y = c1 * s2 * c3 + s1 * c2 * s3;
+			this.z = c1 * c2 * s3 + s1 * s2 * c3;
+			this.w = c1 * c2 * c3 - s1 * s2 * s3;
+
+		} else if ( order === 'ZYX' ) {
+
+			this.x = s1 * c2 * c3 - c1 * s2 * s3;
+			this.y = c1 * s2 * c3 + s1 * c2 * s3;
+			this.z = c1 * c2 * s3 - s1 * s2 * c3;
+			this.w = c1 * c2 * c3 + s1 * s2 * s3;
+
+		} else if ( order === 'YZX' ) {
+
+			this.x = s1 * c2 * c3 + c1 * s2 * s3;
+			this.y = c1 * s2 * c3 + s1 * c2 * s3;
+			this.z = c1 * c2 * s3 - s1 * s2 * c3;
+			this.w = c1 * c2 * c3 - s1 * s2 * s3;
+
+		} else if ( order === 'XZY' ) {
+
+			this.x = s1 * c2 * c3 - c1 * s2 * s3;
+			this.y = c1 * s2 * c3 - s1 * c2 * s3;
+			this.z = c1 * c2 * s3 + s1 * s2 * c3;
+			this.w = c1 * c2 * c3 + s1 * s2 * s3;
+
+		}
+
+		return this;
+
+	},
+
+	setFromAxisAngle: function ( axis, angle ) {
+
+		// from http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
+		// axis have to be normalized
+
+		var halfAngle = angle / 2,
+			s = Math.sin( halfAngle );
+
+		this.x = axis.x * s;
+		this.y = axis.y * s;
+		this.z = axis.z * s;
+		this.w = Math.cos( halfAngle );
+
+		return this;
+
+	},
+
+	setFromRotationMatrix: function ( m ) {
+
+		// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+
+		// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+		var te = m.elements,
+
+			m11 = te[0], m12 = te[4], m13 = te[8],
+			m21 = te[1], m22 = te[5], m23 = te[9],
+			m31 = te[2], m32 = te[6], m33 = te[10],
+
+			trace = m11 + m22 + m33,
+			s;
+
+		if ( trace > 0 ) {
+
+			s = 0.5 / Math.sqrt( trace + 1.0 );
+
+			this.w = 0.25 / s;
+			this.x = ( m32 - m23 ) * s;
+			this.y = ( m13 - m31 ) * s;
+			this.z = ( m21 - m12 ) * s;
+
+		} else if ( m11 > m22 && m11 > m33 ) {
+
+			s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
+
+			this.w = (m32 - m23 ) / s;
+			this.x = 0.25 * s;
+			this.y = (m12 + m21 ) / s;
+			this.z = (m13 + m31 ) / s;
+
+		} else if ( m22 > m33 ) {
+
+			s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
+
+			this.w = (m13 - m31 ) / s;
+			this.x = (m12 + m21 ) / s;
+			this.y = 0.25 * s;
+			this.z = (m23 + m32 ) / s;
+
+		} else {
+
+			s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
+
+			this.w = ( m21 - m12 ) / s;
+			this.x = ( m13 + m31 ) / s;
+			this.y = ( m23 + m32 ) / s;
+			this.z = 0.25 * s;
+
+		}
+
+		return this;
+
+	},
+
+	inverse: function () {
+
+		this.conjugate().normalize();
+
+		return this;
+
+	},
+
+	conjugate: function () {
+
+		this.x *= -1;
+		this.y *= -1;
+		this.z *= -1;
+
+		return this;
+
+	},
+
+	lengthSq: function () {
+
+		return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
+
+	},
+
+	length: function () {
+
+		return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
+
+	},
+
+	normalize: function () {
+
+		var l = this.length();
+
+		if ( l === 0 ) {
+
+			this.x = 0;
+			this.y = 0;
+			this.z = 0;
+			this.w = 1;
+
+		} else {
+
+			l = 1 / l;
+
+			this.x = this.x * l;
+			this.y = this.y * l;
+			this.z = this.z * l;
+			this.w = this.w * l;
+
+		}
+
+		return this;
+
+	},
+
+	multiply: function ( q, p ) {
+
+		if ( p !== undefined ) {
+
+			console.warn( 'DEPRECATED: Quaternion\'s .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
+			return this.multiplyQuaternions( q, p );
+
+		}
+
+		return this.multiplyQuaternions( this, q );
+
+	},
+
+	multiplyQuaternions: function ( a, b ) {
+
+		// from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
+
+		var qax = a.x, qay = a.y, qaz = a.z, qaw = a.w;
+		var qbx = b.x, qby = b.y, qbz = b.z, qbw = b.w;
+
+		this.x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
+		this.y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
+		this.z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
+		this.w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
+
+		return this;
+
+	},
+
+	multiplyVector3: function ( vector ) {
+
+		console.warn( 'DEPRECATED: Quaternion\'s .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
+		return vector.applyQuaternion( this );
+
+	},
+
+	slerp: function ( qb, t ) {
+
+		var x = this.x, y = this.y, z = this.z, w = this.w;
+
+		// http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
+
+		var cosHalfTheta = w * qb.w + x * qb.x + y * qb.y + z * qb.z;
+
+		if ( cosHalfTheta < 0 ) {
+
+			this.w = -qb.w;
+			this.x = -qb.x;
+			this.y = -qb.y;
+			this.z = -qb.z;
+
+			cosHalfTheta = -cosHalfTheta;
+
+		} else {
+
+			this.copy( qb );
+
+		}
+
+		if ( cosHalfTheta >= 1.0 ) {
+
+			this.w = w;
+			this.x = x;
+			this.y = y;
+			this.z = z;
+
+			return this;
+
+		}
+
+		var halfTheta = Math.acos( cosHalfTheta );
+		var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );
+
+		if ( Math.abs( sinHalfTheta ) < 0.001 ) {
+
+			this.w = 0.5 * ( w + this.w );
+			this.x = 0.5 * ( x + this.x );
+			this.y = 0.5 * ( y + this.y );
+			this.z = 0.5 * ( z + this.z );
+
+			return this;
+
+		}
+
+		var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
+		ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
+
+		this.w = ( w * ratioA + this.w * ratioB );
+		this.x = ( x * ratioA + this.x * ratioB );
+		this.y = ( y * ratioA + this.y * ratioB );
+		this.z = ( z * ratioA + this.z * ratioB );
+
+		return this;
+
+	},
+
+	equals: function ( v ) {
+
+		return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );
+
+	},
+
+	fromArray: function ( array ) {
+
+		this.x = array[ 0 ];
+		this.y = array[ 1 ];
+		this.z = array[ 2 ];
+		this.w = array[ 3 ];
+
+		return this;
+
+	},
+
+	toArray: function () {
+
+		return [ this.x, this.y, this.z, this.w ];
+
+	},
+
+	clone: function () {
+
+		return new THREE.Quaternion( this.x, this.y, this.z, this.w );
+
+	}
+
+};
+
+THREE.Quaternion.slerp = function ( qa, qb, qm, t ) {
+
+	return qm.copy( qa ).slerp( qb, t );
+
+}
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author philogb / http://blog.thejit.org/
+ * @author egraether / http://egraether.com/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ */
+
+THREE.Vector2 = function ( x, y ) {
+
+	this.x = x || 0;
+	this.y = y || 0;
+
+};
+
+THREE.Vector2.prototype = {
+
+	constructor: THREE.Vector2,
+
+	set: function ( x, y ) {
+
+		this.x = x;
+		this.y = y;
+
+		return this;
+
+	},
+
+	setX: function ( x ) {
+
+		this.x = x;
+
+		return this;
+
+	},
+
+	setY: function ( y ) {
+
+		this.y = y;
+
+		return this;
+
+	},
+
+
+	setComponent: function ( index, value ) {
+
+		switch ( index ) {
+
+			case 0: this.x = value; break;
+			case 1: this.y = value; break;
+			default: throw new Error( "index is out of range: " + index );
+
+		}
+
+	},
+
+	getComponent: function ( index ) {
+
+		switch ( index ) {
+
+			case 0: return this.x;
+			case 1: return this.y;
+			default: throw new Error( "index is out of range: " + index );
+
+		}
+
+	},
+
+	copy: function ( v ) {
+
+		this.x = v.x;
+		this.y = v.y;
+
+		return this;
+
+	},
+
+	add: function ( v, w ) {
+
+		if ( w !== undefined ) {
+
+			console.warn( 'DEPRECATED: Vector2\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+			return this.addVectors( v, w );
+
+		}
+
+		this.x += v.x;
+		this.y += v.y;
+
+		return this;
+
+	},
+
+	addVectors: function ( a, b ) {
+
+		this.x = a.x + b.x;
+		this.y = a.y + b.y;
+
+		return this;
+
+	},
+
+	addScalar: function ( s ) {
+
+		this.x += s;
+		this.y += s;
+
+		return this;
+
+	},
+
+	sub: function ( v, w ) {
+
+		if ( w !== undefined ) {
+
+			console.warn( 'DEPRECATED: Vector2\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+			return this.subVectors( v, w );
+
+		}
+
+		this.x -= v.x;
+		this.y -= v.y;
+
+		return this;
+
+	},
+
+	subVectors: function ( a, b ) {
+
+		this.x = a.x - b.x;
+		this.y = a.y - b.y;
+
+		return this;
+
+	},
+
+	multiplyScalar: function ( s ) {
+
+		this.x *= s;
+		this.y *= s;
+
+		return this;
+
+	},
+
+	divideScalar: function ( s ) {
+
+		if ( s !== 0 ) {
+
+			this.x /= s;
+			this.y /= s;
+
+		} else {
+
+			this.set( 0, 0 );
+
+		}
+
+		return this;
+
+	},
+
+	min: function ( v ) {
+
+		if ( this.x > v.x ) {
+
+			this.x = v.x;
+
+		}
+
+		if ( this.y > v.y ) {
+
+			this.y = v.y;
+
+		}
+
+		return this;
+
+	},
+
+	max: function ( v ) {
+
+		if ( this.x < v.x ) {
+
+			this.x = v.x;
+
+		}
+
+		if ( this.y < v.y ) {
+
+			this.y = v.y;
+
+		}
+
+		return this;
+
+	},
+
+	clamp: function ( min, max ) {
+
+		// This function assumes min < max, if this assumption isn't true it will not operate correctly
+
+		if ( this.x < min.x ) {
+
+			this.x = min.x;
+
+		} else if ( this.x > max.x ) {
+
+			this.x = max.x;
+
+		}
+
+		if ( this.y < min.y ) {
+
+			this.y = min.y;
+
+		} else if ( this.y > max.y ) {
+
+			this.y = max.y;
+
+		}
+
+		return this;
+
+	},
+
+	negate: function() {
+
+		return this.multiplyScalar( - 1 );
+
+	},
+
+	dot: function ( v ) {
+
+		return this.x * v.x + this.y * v.y;
+
+	},
+
+	lengthSq: function () {
+
+		return this.x * this.x + this.y * this.y;
+
+	},
+
+	length: function () {
+
+		return Math.sqrt( this.x * this.x + this.y * this.y );
+
+	},
+
+	normalize: function () {
+
+		return this.divideScalar( this.length() );
+
+	},
+
+	distanceTo: function ( v ) {
+
+		return Math.sqrt( this.distanceToSquared( v ) );
+
+	},
+
+	distanceToSquared: function ( v ) {
+
+		var dx = this.x - v.x, dy = this.y - v.y;
+		return dx * dx + dy * dy;
+
+	},
+
+	setLength: function ( l ) {
+
+		var oldLength = this.length();
+
+		if ( oldLength !== 0 && l !== oldLength ) {
+
+			this.multiplyScalar( l / oldLength );
+		}
+
+		return this;
+
+	},
+
+	lerp: function ( v, alpha ) {
+
+		this.x += ( v.x - this.x ) * alpha;
+		this.y += ( v.y - this.y ) * alpha;
+
+		return this;
+
+	},
+
+	equals: function( v ) {
+
+		return ( ( v.x === this.x ) && ( v.y === this.y ) );
+
+	},
+
+	fromArray: function ( array ) {
+
+		this.x = array[ 0 ];
+		this.y = array[ 1 ];
+
+		return this;
+
+	},
+
+	toArray: function () {
+
+		return [ this.x, this.y ];
+
+	},
+
+	clone: function () {
+
+		return new THREE.Vector2( this.x, this.y );
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author *kile / http://kile.stravaganza.org/
+ * @author philogb / http://blog.thejit.org/
+ * @author mikael emtinger / http://gomo.se/
+ * @author egraether / http://egraether.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Vector3 = function ( x, y, z ) {
+
+	this.x = x || 0;
+	this.y = y || 0;
+	this.z = z || 0;
+
+};
+
+THREE.Vector3.prototype = {
+
+	constructor: THREE.Vector3,
+
+	set: function ( x, y, z ) {
+
+		this.x = x;
+		this.y = y;
+		this.z = z;
+
+		return this;
+
+	},
+
+	setX: function ( x ) {
+
+		this.x = x;
+
+		return this;
+
+	},
+
+	setY: function ( y ) {
+
+		this.y = y;
+
+		return this;
+
+	},
+
+	setZ: function ( z ) {
+
+		this.z = z;
+
+		return this;
+
+	},
+
+	setComponent: function ( index, value ) {
+
+		switch ( index ) {
+
+			case 0: this.x = value; break;
+			case 1: this.y = value; break;
+			case 2: this.z = value; break;
+			default: throw new Error( "index is out of range: " + index );
+
+		}
+
+	},
+
+	getComponent: function ( index ) {
+
+		switch ( index ) {
+
+			case 0: return this.x;
+			case 1: return this.y;
+			case 2: return this.z;
+			default: throw new Error( "index is out of range: " + index );
+
+		}
+
+	},
+
+	copy: function ( v ) {
+
+		this.x = v.x;
+		this.y = v.y;
+		this.z = v.z;
+
+		return this;
+
+	},
+
+	add: function ( v, w ) {
+
+		if ( w !== undefined ) {
+
+			console.warn( 'DEPRECATED: Vector3\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+			return this.addVectors( v, w );
+
+		}
+
+		this.x += v.x;
+		this.y += v.y;
+		this.z += v.z;
+
+		return this;
+
+	},
+
+	addScalar: function ( s ) {
+
+		this.x += s;
+		this.y += s;
+		this.z += s;
+
+		return this;
+
+	},
+
+	addVectors: function ( a, b ) {
+
+		this.x = a.x + b.x;
+		this.y = a.y + b.y;
+		this.z = a.z + b.z;
+
+		return this;
+
+	},
+
+	sub: function ( v, w ) {
+
+		if ( w !== undefined ) {
+
+			console.warn( 'DEPRECATED: Vector3\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+			return this.subVectors( v, w );
+
+		}
+
+		this.x -= v.x;
+		this.y -= v.y;
+		this.z -= v.z;
+
+		return this;
+
+	},
+
+	subVectors: function ( a, b ) {
+
+		this.x = a.x - b.x;
+		this.y = a.y - b.y;
+		this.z = a.z - b.z;
+
+		return this;
+
+	},
+
+	multiply: function ( v, w ) {
+
+		if ( w !== undefined ) {
+
+			console.warn( 'DEPRECATED: Vector3\'s .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' );
+			return this.multiplyVectors( v, w );
+
+		}
+
+		this.x *= v.x;
+		this.y *= v.y;
+		this.z *= v.z;
+
+		return this;
+
+	},
+
+	multiplyScalar: function ( s ) {
+
+		this.x *= s;
+		this.y *= s;
+		this.z *= s;
+
+		return this;
+
+	},
+
+	multiplyVectors: function ( a, b ) {
+
+		this.x = a.x * b.x;
+		this.y = a.y * b.y;
+		this.z = a.z * b.z;
+
+		return this;
+
+	},
+
+	applyMatrix3: function ( m ) {
+
+		var x = this.x;
+		var y = this.y;
+		var z = this.z;
+
+		var e = m.elements;
+
+		this.x = e[0] * x + e[3] * y + e[6] * z;
+		this.y = e[1] * x + e[4] * y + e[7] * z;
+		this.z = e[2] * x + e[5] * y + e[8] * z;
+
+		return this;
+
+	},
+
+	applyMatrix4: function ( m ) {
+
+		// input: THREE.Matrix4 affine matrix
+
+		var x = this.x, y = this.y, z = this.z;
+
+		var e = m.elements;
+
+		this.x = e[0] * x + e[4] * y + e[8]  * z + e[12];
+		this.y = e[1] * x + e[5] * y + e[9]  * z + e[13];
+		this.z = e[2] * x + e[6] * y + e[10] * z + e[14];
+
+		return this;
+
+	},
+
+	applyProjection: function ( m ) {
+
+		// input: THREE.Matrix4 projection matrix
+
+		var x = this.x, y = this.y, z = this.z;
+
+		var e = m.elements;
+		var d = 1 / ( e[3] * x + e[7] * y + e[11] * z + e[15] ); // perspective divide
+
+		this.x = ( e[0] * x + e[4] * y + e[8]  * z + e[12] ) * d;
+		this.y = ( e[1] * x + e[5] * y + e[9]  * z + e[13] ) * d;
+		this.z = ( e[2] * x + e[6] * y + e[10] * z + e[14] ) * d;
+
+		return this;
+
+	},
+
+	applyQuaternion: function ( q ) {
+
+		var x = this.x;
+		var y = this.y;
+		var z = this.z;
+
+		var qx = q.x;
+		var qy = q.y;
+		var qz = q.z;
+		var qw = q.w;
+
+		// calculate quat * vector
+
+		var ix =  qw * x + qy * z - qz * y;
+		var iy =  qw * y + qz * x - qx * z;
+		var iz =  qw * z + qx * y - qy * x;
+		var iw = -qx * x - qy * y - qz * z;
+
+		// calculate result * inverse quat
+
+		this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+		this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+		this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+
+		return this;
+
+	},
+
+	transformDirection: function ( m ) {
+
+		// input: THREE.Matrix4 affine matrix
+		// vector interpreted as a direction
+
+		var x = this.x, y = this.y, z = this.z;
+
+		var e = m.elements;
+
+		this.x = e[0] * x + e[4] * y + e[8]  * z;
+		this.y = e[1] * x + e[5] * y + e[9]  * z;
+		this.z = e[2] * x + e[6] * y + e[10] * z;
+
+		this.normalize();
+
+		return this;
+
+	},
+
+	divide: function ( v ) {
+
+		this.x /= v.x;
+		this.y /= v.y;
+		this.z /= v.z;
+
+		return this;
+
+	},
+
+	divideScalar: function ( s ) {
+
+		if ( s !== 0 ) {
+
+			this.x /= s;
+			this.y /= s;
+			this.z /= s;
+
+		} else {
+
+			this.x = 0;
+			this.y = 0;
+			this.z = 0;
+
+		}
+
+		return this;
+
+	},
+
+	min: function ( v ) {
+
+		if ( this.x > v.x ) {
+
+			this.x = v.x;
+
+		}
+
+		if ( this.y > v.y ) {
+
+			this.y = v.y;
+
+		}
+
+		if ( this.z > v.z ) {
+
+			this.z = v.z;
+
+		}
+
+		return this;
+
+	},
+
+	max: function ( v ) {
+
+		if ( this.x < v.x ) {
+
+			this.x = v.x;
+
+		}
+
+		if ( this.y < v.y ) {
+
+			this.y = v.y;
+
+		}
+
+		if ( this.z < v.z ) {
+
+			this.z = v.z;
+
+		}
+
+		return this;
+
+	},
+
+	clamp: function ( min, max ) {
+
+		// This function assumes min < max, if this assumption isn't true it will not operate correctly
+
+		if ( this.x < min.x ) {
+
+			this.x = min.x;
+
+		} else if ( this.x > max.x ) {
+
+			this.x = max.x;
+
+		}
+
+		if ( this.y < min.y ) {
+
+			this.y = min.y;
+
+		} else if ( this.y > max.y ) {
+
+			this.y = max.y;
+
+		}
+
+		if ( this.z < min.z ) {
+
+			this.z = min.z;
+
+		} else if ( this.z > max.z ) {
+
+			this.z = max.z;
+
+		}
+
+		return this;
+
+	},
+
+	negate: function () {
+
+		return this.multiplyScalar( - 1 );
+
+	},
+
+	dot: function ( v ) {
+
+		return this.x * v.x + this.y * v.y + this.z * v.z;
+
+	},
+
+	lengthSq: function () {
+
+		return this.x * this.x + this.y * this.y + this.z * this.z;
+
+	},
+
+	length: function () {
+
+		return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
+
+	},
+
+	lengthManhattan: function () {
+
+		return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );
+
+	},
+
+	normalize: function () {
+
+		return this.divideScalar( this.length() );
+
+	},
+
+	setLength: function ( l ) {
+
+		var oldLength = this.length();
+
+		if ( oldLength !== 0 && l !== oldLength  ) {
+
+			this.multiplyScalar( l / oldLength );
+		}
+
+		return this;
+
+	},
+
+	lerp: function ( v, alpha ) {
+
+		this.x += ( v.x - this.x ) * alpha;
+		this.y += ( v.y - this.y ) * alpha;
+		this.z += ( v.z - this.z ) * alpha;
+
+		return this;
+
+	},
+
+	cross: function ( v, w ) {
+
+		if ( w !== undefined ) {
+
+			console.warn( 'DEPRECATED: Vector3\'s .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );
+			return this.crossVectors( v, w );
+
+		}
+
+		var x = this.x, y = this.y, z = this.z;
+
+		this.x = y * v.z - z * v.y;
+		this.y = z * v.x - x * v.z;
+		this.z = x * v.y - y * v.x;
+
+		return this;
+
+	},
+
+	crossVectors: function ( a, b ) {
+
+		this.x = a.y * b.z - a.z * b.y;
+		this.y = a.z * b.x - a.x * b.z;
+		this.z = a.x * b.y - a.y * b.x;
+
+		return this;
+
+	},
+
+	angleTo: function ( v ) {
+
+		var theta = this.dot( v ) / ( this.length() * v.length() );
+
+		// clamp, to handle numerical problems
+
+		return Math.acos( THREE.Math.clamp( theta, -1, 1 ) );
+
+	},
+
+	distanceTo: function ( v ) {
+
+		return Math.sqrt( this.distanceToSquared( v ) );
+
+	},
+
+	distanceToSquared: function ( v ) {
+
+		var dx = this.x - v.x;
+		var dy = this.y - v.y;
+		var dz = this.z - v.z;
+
+		return dx * dx + dy * dy + dz * dz;
+
+	},
+
+	setEulerFromRotationMatrix: function ( m, order ) {
+
+		// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+		// clamp, to handle numerical problems
+
+		function clamp( x ) {
+
+			return Math.min( Math.max( x, -1 ), 1 );
+
+		}
+
+		var te = m.elements;
+		var m11 = te[0], m12 = te[4], m13 = te[8];
+		var m21 = te[1], m22 = te[5], m23 = te[9];
+		var m31 = te[2], m32 = te[6], m33 = te[10];
+
+		if ( order === undefined || order === 'XYZ' ) {
+
+			this.y = Math.asin( clamp( m13 ) );
+
+			if ( Math.abs( m13 ) < 0.99999 ) {
+
+				this.x = Math.atan2( - m23, m33 );
+				this.z = Math.atan2( - m12, m11 );
+
+			} else {
+
+				this.x = Math.atan2( m32, m22 );
+				this.z = 0;
+
+			}
+
+		} else if ( order === 'YXZ' ) {
+
+			this.x = Math.asin( - clamp( m23 ) );
+
+			if ( Math.abs( m23 ) < 0.99999 ) {
+
+				this.y = Math.atan2( m13, m33 );
+				this.z = Math.atan2( m21, m22 );
+
+			} else {
+
+				this.y = Math.atan2( - m31, m11 );
+				this.z = 0;
+
+			}
+
+		} else if ( order === 'ZXY' ) {
+
+			this.x = Math.asin( clamp( m32 ) );
+
+			if ( Math.abs( m32 ) < 0.99999 ) {
+
+				this.y = Math.atan2( - m31, m33 );
+				this.z = Math.atan2( - m12, m22 );
+
+			} else {
+
+				this.y = 0;
+				this.z = Math.atan2( m21, m11 );
+
+			}
+
+		} else if ( order === 'ZYX' ) {
+
+			this.y = Math.asin( - clamp( m31 ) );
+
+			if ( Math.abs( m31 ) < 0.99999 ) {
+
+				this.x = Math.atan2( m32, m33 );
+				this.z = Math.atan2( m21, m11 );
+
+			} else {
+
+				this.x = 0;
+				this.z = Math.atan2( - m12, m22 );
+
+			}
+
+		} else if ( order === 'YZX' ) {
+
+			this.z = Math.asin( clamp( m21 ) );
+
+			if ( Math.abs( m21 ) < 0.99999 ) {
+
+				this.x = Math.atan2( - m23, m22 );
+				this.y = Math.atan2( - m31, m11 );
+
+			} else {
+
+				this.x = 0;
+				this.y = Math.atan2( m13, m33 );
+
+			}
+
+		} else if ( order === 'XZY' ) {
+
+			this.z = Math.asin( - clamp( m12 ) );
+
+			if ( Math.abs( m12 ) < 0.99999 ) {
+
+				this.x = Math.atan2( m32, m22 );
+				this.y = Math.atan2( m13, m11 );
+
+			} else {
+
+				this.x = Math.atan2( - m23, m33 );
+				this.y = 0;
+
+			}
+
+		}
+
+		return this;
+
+	},
+
+	setEulerFromQuaternion: function ( q, order ) {
+
+		// q is assumed to be normalized
+
+		// clamp, to handle numerical problems
+
+		function clamp( x ) {
+
+			return Math.min( Math.max( x, -1 ), 1 );
+
+		}
+
+		// http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m
+
+		var sqx = q.x * q.x;
+		var sqy = q.y * q.y;
+		var sqz = q.z * q.z;
+		var sqw = q.w * q.w;
+
+		if ( order === undefined || order === 'XYZ' ) {
+
+			this.x = Math.atan2( 2 * ( q.x * q.w - q.y * q.z ), ( sqw - sqx - sqy + sqz ) );
+			this.y = Math.asin(  clamp( 2 * ( q.x * q.z + q.y * q.w ) ) );
+			this.z = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw + sqx - sqy - sqz ) );
+
+		} else if ( order ===  'YXZ' ) {
+
+			this.x = Math.asin(  clamp( 2 * ( q.x * q.w - q.y * q.z ) ) );
+			this.y = Math.atan2( 2 * ( q.x * q.z + q.y * q.w ), ( sqw - sqx - sqy + sqz ) );
+			this.z = Math.atan2( 2 * ( q.x * q.y + q.z * q.w ), ( sqw - sqx + sqy - sqz ) );
+
+		} else if ( order === 'ZXY' ) {
+
+			this.x = Math.asin(  clamp( 2 * ( q.x * q.w + q.y * q.z ) ) );
+			this.y = Math.atan2( 2 * ( q.y * q.w - q.z * q.x ), ( sqw - sqx - sqy + sqz ) );
+			this.z = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw - sqx + sqy - sqz ) );
+
+		} else if ( order === 'ZYX' ) {
+
+			this.x = Math.atan2( 2 * ( q.x * q.w + q.z * q.y ), ( sqw - sqx - sqy + sqz ) );
+			this.y = Math.asin(  clamp( 2 * ( q.y * q.w - q.x * q.z ) ) );
+			this.z = Math.atan2( 2 * ( q.x * q.y + q.z * q.w ), ( sqw + sqx - sqy - sqz ) );
+
+		} else if ( order === 'YZX' ) {
+
+			this.x = Math.atan2( 2 * ( q.x * q.w - q.z * q.y ), ( sqw - sqx + sqy - sqz ) );
+			this.y = Math.atan2( 2 * ( q.y * q.w - q.x * q.z ), ( sqw + sqx - sqy - sqz ) );
+			this.z = Math.asin(  clamp( 2 * ( q.x * q.y + q.z * q.w ) ) );
+
+		} else if ( order === 'XZY' ) {
+
+			this.x = Math.atan2( 2 * ( q.x * q.w + q.y * q.z ), ( sqw - sqx + sqy - sqz ) );
+			this.y = Math.atan2( 2 * ( q.x * q.z + q.y * q.w ), ( sqw + sqx - sqy - sqz ) );
+			this.z = Math.asin(  clamp( 2 * ( q.z * q.w - q.x * q.y ) ) );
+
+		}
+
+		return this;
+
+	},
+
+	getPositionFromMatrix: function ( m ) {
+
+		this.x = m.elements[12];
+		this.y = m.elements[13];
+		this.z = m.elements[14];
+
+		return this;
+
+	},
+
+	getScaleFromMatrix: function ( m ) {
+
+		var sx = this.set( m.elements[0], m.elements[1], m.elements[2] ).length();
+		var sy = this.set( m.elements[4], m.elements[5], m.elements[6] ).length();
+		var sz = this.set( m.elements[8], m.elements[9], m.elements[10] ).length();
+
+		this.x = sx;
+		this.y = sy;
+		this.z = sz;
+
+		return this;
+	},
+
+	getColumnFromMatrix: function ( index, matrix ) {
+
+		var offset = index * 4;
+
+		var me = matrix.elements;
+
+		this.x = me[ offset ];
+		this.y = me[ offset + 1 ];
+		this.z = me[ offset + 2 ];
+
+		return this;
+
+	},
+
+	equals: function ( v ) {
+
+		return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );
+
+	},
+
+	fromArray: function ( array ) {
+
+		this.x = array[ 0 ];
+		this.y = array[ 1 ];
+		this.z = array[ 2 ];
+
+		return this;
+
+	},
+
+	toArray: function () {
+
+		return [ this.x, this.y, this.z ];
+
+	},
+
+	clone: function () {
+
+		return new THREE.Vector3( this.x, this.y, this.z );
+
+	}
+
+};
+
+THREE.extend( THREE.Vector3.prototype, {
+
+	applyEuler: function () {
+
+		var q1 = new THREE.Quaternion();
+
+		return function ( v, eulerOrder ) {
+
+			var quaternion = q1.setFromEuler( v, eulerOrder );
+
+			this.applyQuaternion( quaternion );
+
+			return this;
+
+		};
+
+	}(),
+
+	applyAxisAngle: function () {
+
+		var q1 = new THREE.Quaternion();
+
+		return function ( axis, angle ) {
+
+			var quaternion = q1.setFromAxisAngle( axis, angle );
+
+			this.applyQuaternion( quaternion );
+
+			return this;
+
+		};
+
+	}(),
+
+	projectOnVector: function () {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( vector ) {
+
+			v1.copy( vector ).normalize();
+			var d = this.dot( v1 );
+			return this.copy( v1 ).multiplyScalar( d );
+
+		};
+
+	}(),
+
+	projectOnPlane: function () {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( planeNormal ) {
+
+			v1.copy( this ).projectOnVector( planeNormal );
+
+			return this.sub( v1 );
+
+		}
+
+	}(),
+
+	reflect: function () {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( vector ) {
+
+		    v1.copy( this ).projectOnVector( vector ).multiplyScalar( 2 );
+
+		    return this.subVectors( v1, this );
+
+		}
+
+	}()
+
+} );
+/**
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author philogb / http://blog.thejit.org/
+ * @author mikael emtinger / http://gomo.se/
+ * @author egraether / http://egraether.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Vector4 = function ( x, y, z, w ) {
+
+	this.x = x || 0;
+	this.y = y || 0;
+	this.z = z || 0;
+	this.w = ( w !== undefined ) ? w : 1;
+
+};
+
+THREE.Vector4.prototype = {
+
+	constructor: THREE.Vector4,
+
+	set: function ( x, y, z, w ) {
+
+		this.x = x;
+		this.y = y;
+		this.z = z;
+		this.w = w;
+
+		return this;
+
+	},
+
+	setX: function ( x ) {
+
+		this.x = x;
+
+		return this;
+
+	},
+
+	setY: function ( y ) {
+
+		this.y = y;
+
+		return this;
+
+	},
+
+	setZ: function ( z ) {
+
+		this.z = z;
+
+		return this;
+
+	},
+
+	setW: function ( w ) {
+
+		this.w = w;
+
+		return this;
+
+	},
+
+	setComponent: function ( index, value ) {
+
+		switch ( index ) {
+
+			case 0: this.x = value; break;
+			case 1: this.y = value; break;
+			case 2: this.z = value; break;
+			case 3: this.w = value; break;
+			default: throw new Error( "index is out of range: " + index );
+
+		}
+
+	},
+
+	getComponent: function ( index ) {
+
+		switch ( index ) {
+
+			case 0: return this.x;
+			case 1: return this.y;
+			case 2: return this.z;
+			case 3: return this.w;
+			default: throw new Error( "index is out of range: " + index );
+
+		}
+
+	},
+
+	copy: function ( v ) {
+
+		this.x = v.x;
+		this.y = v.y;
+		this.z = v.z;
+		this.w = ( v.w !== undefined ) ? v.w : 1;
+
+		return this;
+
+	},
+
+	add: function ( v, w ) {
+
+		if ( w !== undefined ) {
+
+			console.warn( 'DEPRECATED: Vector4\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+			return this.addVectors( v, w );
+
+		}
+
+		this.x += v.x;
+		this.y += v.y;
+		this.z += v.z;
+		this.w += v.w;
+
+		return this;
+
+	},
+
+	addScalar: function ( s ) {
+
+		this.x += s;
+		this.y += s;
+		this.z += s;
+		this.w += s;
+
+		return this;
+
+	},
+
+	addVectors: function ( a, b ) {
+
+		this.x = a.x + b.x;
+		this.y = a.y + b.y;
+		this.z = a.z + b.z;
+		this.w = a.w + b.w;
+
+		return this;
+
+	},
+
+	sub: function ( v, w ) {
+
+		if ( w !== undefined ) {
+
+			console.warn( 'DEPRECATED: Vector4\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+			return this.subVectors( v, w );
+
+		}
+
+		this.x -= v.x;
+		this.y -= v.y;
+		this.z -= v.z;
+		this.w -= v.w;
+
+		return this;
+
+	},
+
+	subVectors: function ( a, b ) {
+
+		this.x = a.x - b.x;
+		this.y = a.y - b.y;
+		this.z = a.z - b.z;
+		this.w = a.w - b.w;
+
+		return this;
+
+	},
+
+	multiplyScalar: function ( s ) {
+
+		this.x *= s;
+		this.y *= s;
+		this.z *= s;
+		this.w *= s;
+
+		return this;
+
+	},
+
+	applyMatrix4: function ( m ) {
+
+		var x = this.x;
+		var y = this.y;
+		var z = this.z;
+		var w = this.w;
+
+		var e = m.elements;
+
+		this.x = e[0] * x + e[4] * y + e[8] * z + e[12] * w;
+		this.y = e[1] * x + e[5] * y + e[9] * z + e[13] * w;
+		this.z = e[2] * x + e[6] * y + e[10] * z + e[14] * w;
+		this.w = e[3] * x + e[7] * y + e[11] * z + e[15] * w;
+
+		return this;
+
+	},
+
+	divideScalar: function ( s ) {
+
+		if ( s !== 0 ) {
+
+			this.x /= s;
+			this.y /= s;
+			this.z /= s;
+			this.w /= s;
+
+		} else {
+
+			this.x = 0;
+			this.y = 0;
+			this.z = 0;
+			this.w = 1;
+
+		}
+
+		return this;
+
+	},
+
+	setAxisAngleFromQuaternion: function ( q ) {
+
+		// http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
+
+		// q is assumed to be normalized
+
+		this.w = 2 * Math.acos( q.w );
+
+		var s = Math.sqrt( 1 - q.w * q.w );
+
+		if ( s < 0.0001 ) {
+
+			 this.x = 1;
+			 this.y = 0;
+			 this.z = 0;
+
+		} else {
+
+			 this.x = q.x / s;
+			 this.y = q.y / s;
+			 this.z = q.z / s;
+
+		}
+
+		return this;
+
+	},
+
+	setAxisAngleFromRotationMatrix: function ( m ) {
+
+		// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm
+
+		// assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+		var angle, x, y, z,		// variables for result
+			epsilon = 0.01,		// margin to allow for rounding errors
+			epsilon2 = 0.1,		// margin to distinguish between 0 and 180 degrees
+
+			te = m.elements,
+
+			m11 = te[0], m12 = te[4], m13 = te[8],
+			m21 = te[1], m22 = te[5], m23 = te[9],
+			m31 = te[2], m32 = te[6], m33 = te[10];
+
+		if ( ( Math.abs( m12 - m21 ) < epsilon )
+		  && ( Math.abs( m13 - m31 ) < epsilon )
+		  && ( Math.abs( m23 - m32 ) < epsilon ) ) {
+
+			// singularity found
+			// first check for identity matrix which must have +1 for all terms
+			// in leading diagonal and zero in other terms
+
+			if ( ( Math.abs( m12 + m21 ) < epsilon2 )
+			  && ( Math.abs( m13 + m31 ) < epsilon2 )
+			  && ( Math.abs( m23 + m32 ) < epsilon2 )
+			  && ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {
+
+				// this singularity is identity matrix so angle = 0
+
+				this.set( 1, 0, 0, 0 );
+
+				return this; // zero angle, arbitrary axis
+
+			}
+
+			// otherwise this singularity is angle = 180
+
+			angle = Math.PI;
+
+			var xx = ( m11 + 1 ) / 2;
+			var yy = ( m22 + 1 ) / 2;
+			var zz = ( m33 + 1 ) / 2;
+			var xy = ( m12 + m21 ) / 4;
+			var xz = ( m13 + m31 ) / 4;
+			var yz = ( m23 + m32 ) / 4;
+
+			if ( ( xx > yy ) && ( xx > zz ) ) { // m11 is the largest diagonal term
+
+				if ( xx < epsilon ) {
+
+					x = 0;
+					y = 0.707106781;
+					z = 0.707106781;
+
+				} else {
+
+					x = Math.sqrt( xx );
+					y = xy / x;
+					z = xz / x;
+
+				}
+
+			} else if ( yy > zz ) { // m22 is the largest diagonal term
+
+				if ( yy < epsilon ) {
+
+					x = 0.707106781;
+					y = 0;
+					z = 0.707106781;
+
+				} else {
+
+					y = Math.sqrt( yy );
+					x = xy / y;
+					z = yz / y;
+
+				}
+
+			} else { // m33 is the largest diagonal term so base result on this
+
+				if ( zz < epsilon ) {
+
+					x = 0.707106781;
+					y = 0.707106781;
+					z = 0;
+
+				} else {
+
+					z = Math.sqrt( zz );
+					x = xz / z;
+					y = yz / z;
+
+				}
+
+			}
+
+			this.set( x, y, z, angle );
+
+			return this; // return 180 deg rotation
+
+		}
+
+		// as we have reached here there are no singularities so we can handle normally
+
+		var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 )
+						 + ( m13 - m31 ) * ( m13 - m31 )
+						 + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize
+
+		if ( Math.abs( s ) < 0.001 ) s = 1;
+
+		// prevent divide by zero, should not happen if matrix is orthogonal and should be
+		// caught by singularity test above, but I've left it in just in case
+
+		this.x = ( m32 - m23 ) / s;
+		this.y = ( m13 - m31 ) / s;
+		this.z = ( m21 - m12 ) / s;
+		this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );
+
+		return this;
+
+	},
+
+	min: function ( v ) {
+
+		if ( this.x > v.x ) {
+
+			this.x = v.x;
+
+		}
+
+		if ( this.y > v.y ) {
+
+			this.y = v.y;
+
+		}
+
+		if ( this.z > v.z ) {
+
+			this.z = v.z;
+
+		}
+
+		if ( this.w > v.w ) {
+
+			this.w = v.w;
+
+		}
+
+		return this;
+
+	},
+
+	max: function ( v ) {
+
+		if ( this.x < v.x ) {
+
+			this.x = v.x;
+
+		}
+
+		if ( this.y < v.y ) {
+
+			this.y = v.y;
+
+		}
+
+		if ( this.z < v.z ) {
+
+			this.z = v.z;
+
+		}
+
+		if ( this.w < v.w ) {
+
+			this.w = v.w;
+
+		}
+
+		return this;
+
+	},
+
+	clamp: function ( min, max ) {
+
+		// This function assumes min < max, if this assumption isn't true it will not operate correctly
+
+		if ( this.x < min.x ) {
+
+			this.x = min.x;
+
+		} else if ( this.x > max.x ) {
+
+			this.x = max.x;
+
+		}
+
+		if ( this.y < min.y ) {
+
+			this.y = min.y;
+
+		} else if ( this.y > max.y ) {
+
+			this.y = max.y;
+
+		}
+
+		if ( this.z < min.z ) {
+
+			this.z = min.z;
+
+		} else if ( this.z > max.z ) {
+
+			this.z = max.z;
+
+		}
+
+		if ( this.w < min.w ) {
+
+			this.w = min.w;
+
+		} else if ( this.w > max.w ) {
+
+			this.w = max.w;
+
+		}
+
+		return this;
+
+	},
+
+	negate: function() {
+
+		return this.multiplyScalar( -1 );
+
+	},
+
+	dot: function ( v ) {
+
+		return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
+
+	},
+
+	lengthSq: function () {
+
+		return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
+
+	},
+
+	length: function () {
+
+		return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
+
+	},
+
+	lengthManhattan: function () {
+
+		return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w );
+
+	},
+
+	normalize: function () {
+
+		return this.divideScalar( this.length() );
+
+	},
+
+	setLength: function ( l ) {
+
+		var oldLength = this.length();
+
+		if ( oldLength !== 0 && l !== oldLength ) {
+
+			this.multiplyScalar( l / oldLength );
+		}
+
+		return this;
+
+	},
+
+	lerp: function ( v, alpha ) {
+
+		this.x += ( v.x - this.x ) * alpha;
+		this.y += ( v.y - this.y ) * alpha;
+		this.z += ( v.z - this.z ) * alpha;
+		this.w += ( v.w - this.w ) * alpha;
+
+		return this;
+
+	},
+
+	equals: function ( v ) {
+
+		return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );
+
+	},
+
+	fromArray: function ( array ) {
+
+		this.x = array[ 0 ];
+		this.y = array[ 1 ];
+		this.z = array[ 2 ];
+		this.w = array[ 3 ];
+
+		return this;
+
+	},
+
+	toArray: function () {
+
+		return [ this.x, this.y, this.z, this.w ];
+
+	},
+
+	clone: function () {
+
+		return new THREE.Vector4( this.x, this.y, this.z, this.w );
+
+	}
+
+};
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Line3 = function ( start, end ) {
+
+	this.start = ( start !== undefined ) ? start : new THREE.Vector3();
+	this.end = ( end !== undefined ) ? end : new THREE.Vector3();
+
+};
+
+THREE.Line3.prototype = {
+
+	constructor: THREE.Line3,
+
+	set: function ( start, end ) {
+
+		this.start.copy( start );
+		this.end.copy( end );
+
+		return this;
+
+	},
+
+	copy: function ( line ) {
+
+		this.start.copy( line.start );
+		this.end.copy( line.end );
+
+		return this;
+
+	},
+
+	center: function ( optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+		return result.addVectors( this.start, this.end ).multiplyScalar( 0.5 );
+
+	},
+
+	delta: function ( optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+		return result.subVectors( this.end, this.start );
+
+	},
+
+	distanceSq: function () {
+
+		return this.start.distanceToSquared( this.end );
+
+	},
+
+	distance: function () {
+
+		return this.start.distanceTo( this.end );
+
+	},
+
+	at: function ( t, optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+
+		return this.delta( result ).multiplyScalar( t ).add( this.start );
+
+	},
+
+	closestPointToPointParameter: function() {
+
+		var startP = new THREE.Vector3();
+		var startEnd = new THREE.Vector3();
+
+		return function ( point, clampToLine ) {
+
+			startP.subVectors( point, this.start );
+			startEnd.subVectors( this.end, this.start );
+
+			var startEnd2 = startEnd.dot( startEnd );
+			var startEnd_startP = startEnd.dot( startP );
+
+			var t = startEnd_startP / startEnd2;
+
+			if ( clampToLine ) {
+
+				t = THREE.Math.clamp( t, 0, 1 );
+
+			}
+
+			return t;
+
+		};
+
+	}(),
+
+	closestPointToPoint: function ( point, clampToLine, optionalTarget ) {
+
+		var t = this.closestPointToPointParameter( point, clampToLine );
+
+		var result = optionalTarget || new THREE.Vector3();
+
+		return this.delta( result ).multiplyScalar( t ).add( this.start );
+
+	},
+
+	applyMatrix4: function ( matrix ) {
+
+		this.start.applyMatrix4( matrix );
+		this.end.applyMatrix4( matrix );
+
+		return this;
+
+	},
+
+	equals: function ( line ) {
+
+		return line.start.equals( this.start ) && line.end.equals( this.end );
+
+	},
+
+	clone: function () {
+
+		return new THREE.Line3().copy( this );
+
+	}
+
+};
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Box2 = function ( min, max ) {
+
+	this.min = ( min !== undefined ) ? min : new THREE.Vector2( Infinity, Infinity );
+	this.max = ( max !== undefined ) ? max : new THREE.Vector2( -Infinity, -Infinity );
+
+};
+
+THREE.Box2.prototype = {
+
+	constructor: THREE.Box2,
+
+	set: function ( min, max ) {
+
+		this.min.copy( min );
+		this.max.copy( max );
+
+		return this;
+
+	},
+
+	setFromPoints: function ( points ) {
+
+		if ( points.length > 0 ) {
+
+			var point = points[ 0 ];
+
+			this.min.copy( point );
+			this.max.copy( point );
+
+			for ( var i = 1, il = points.length; i < il; i ++ ) {
+
+				point = points[ i ];
+
+				if ( point.x < this.min.x ) {
+
+					this.min.x = point.x;
+
+				} else if ( point.x > this.max.x ) {
+
+					this.max.x = point.x;
+
+				}
+
+				if ( point.y < this.min.y ) {
+
+					this.min.y = point.y;
+
+				} else if ( point.y > this.max.y ) {
+
+					this.max.y = point.y;
+
+				}
+
+			}
+
+		} else {
+
+			this.makeEmpty();
+
+		}
+
+		return this;
+
+	},
+
+	setFromCenterAndSize: function () {
+
+		var v1 = new THREE.Vector2();
+
+		return function ( center, size ) {
+
+			var halfSize = v1.copy( size ).multiplyScalar( 0.5 );
+			this.min.copy( center ).sub( halfSize );
+			this.max.copy( center ).add( halfSize );
+
+			return this;
+
+		};
+
+	}(),
+
+	copy: function ( box ) {
+
+		this.min.copy( box.min );
+		this.max.copy( box.max );
+
+		return this;
+
+	},
+
+	makeEmpty: function () {
+
+		this.min.x = this.min.y = Infinity;
+		this.max.x = this.max.y = -Infinity;
+
+		return this;
+
+	},
+
+	empty: function () {
+
+		// this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+		return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y );
+
+	},
+
+	center: function ( optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector2();
+		return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+	},
+
+	size: function ( optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector2();
+		return result.subVectors( this.max, this.min );
+
+	},
+
+	expandByPoint: function ( point ) {
+
+		this.min.min( point );
+		this.max.max( point );
+
+		return this;
+	},
+
+	expandByVector: function ( vector ) {
+
+		this.min.sub( vector );
+		this.max.add( vector );
+
+		return this;
+	},
+
+	expandByScalar: function ( scalar ) {
+
+		this.min.addScalar( -scalar );
+		this.max.addScalar( scalar );
+
+		return this;
+	},
+
+	containsPoint: function ( point ) {
+
+		if ( point.x < this.min.x || point.x > this.max.x ||
+		     point.y < this.min.y || point.y > this.max.y ) {
+
+			return false;
+
+		}
+
+		return true;
+
+	},
+
+	containsBox: function ( box ) {
+
+		if ( ( this.min.x <= box.min.x ) && ( box.max.x <= this.max.x ) &&
+		     ( this.min.y <= box.min.y ) && ( box.max.y <= this.max.y ) ) {
+
+			return true;
+
+		}
+
+		return false;
+
+	},
+
+	getParameter: function ( point ) {
+
+		// This can potentially have a divide by zero if the box
+		// has a size dimension of 0.
+
+		return new THREE.Vector2(
+			( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+			( point.y - this.min.y ) / ( this.max.y - this.min.y )
+		);
+
+	},
+
+	isIntersectionBox: function ( box ) {
+
+		// using 6 splitting planes to rule out intersections.
+
+		if ( box.max.x < this.min.x || box.min.x > this.max.x ||
+		     box.max.y < this.min.y || box.min.y > this.max.y ) {
+
+			return false;
+
+		}
+
+		return true;
+
+	},
+
+	clampPoint: function ( point, optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector2();
+		return result.copy( point ).clamp( this.min, this.max );
+
+	},
+
+	distanceToPoint: function () {
+
+		var v1 = new THREE.Vector2();
+
+		return function ( point ) {
+
+			var clampedPoint = v1.copy( point ).clamp( this.min, this.max );
+			return clampedPoint.sub( point ).length();
+
+		};
+
+	}(),
+
+	intersect: function ( box ) {
+
+		this.min.max( box.min );
+		this.max.min( box.max );
+
+		return this;
+
+	},
+
+	union: function ( box ) {
+
+		this.min.min( box.min );
+		this.max.max( box.max );
+
+		return this;
+
+	},
+
+	translate: function ( offset ) {
+
+		this.min.add( offset );
+		this.max.add( offset );
+
+		return this;
+
+	},
+
+	equals: function ( box ) {
+
+		return box.min.equals( this.min ) && box.max.equals( this.max );
+
+	},
+
+	clone: function () {
+
+		return new THREE.Box2().copy( this );
+
+	}
+
+};
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Box3 = function ( min, max ) {
+
+	this.min = ( min !== undefined ) ? min : new THREE.Vector3( Infinity, Infinity, Infinity );
+	this.max = ( max !== undefined ) ? max : new THREE.Vector3( -Infinity, -Infinity, -Infinity );
+
+};
+
+THREE.Box3.prototype = {
+
+	constructor: THREE.Box3,
+
+	set: function ( min, max ) {
+
+		this.min.copy( min );
+		this.max.copy( max );
+
+		return this;
+
+	},
+
+	setFromPoints: function ( points ) {
+
+		if ( points.length > 0 ) {
+
+			var point = points[ 0 ];
+
+			this.min.copy( point );
+			this.max.copy( point );
+
+			for ( var i = 1, il = points.length; i < il; i ++ ) {
+
+				point = points[ i ];
+
+				if ( point.x < this.min.x ) {
+
+					this.min.x = point.x;
+
+				} else if ( point.x > this.max.x ) {
+
+					this.max.x = point.x;
+
+				}
+
+				if ( point.y < this.min.y ) {
+
+					this.min.y = point.y;
+
+				} else if ( point.y > this.max.y ) {
+
+					this.max.y = point.y;
+
+				}
+
+				if ( point.z < this.min.z ) {
+
+					this.min.z = point.z;
+
+				} else if ( point.z > this.max.z ) {
+
+					this.max.z = point.z;
+
+				}
+
+			}
+
+		} else {
+
+			this.makeEmpty();
+
+		}
+
+		return this;
+
+	},
+
+	setFromCenterAndSize: function() {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( center, size ) {
+
+			var halfSize = v1.copy( size ).multiplyScalar( 0.5 );
+
+			this.min.copy( center ).sub( halfSize );
+			this.max.copy( center ).add( halfSize );
+
+			return this;
+
+		};
+
+	}(),
+
+	copy: function ( box ) {
+
+		this.min.copy( box.min );
+		this.max.copy( box.max );
+
+		return this;
+
+	},
+
+	makeEmpty: function () {
+
+		this.min.x = this.min.y = this.min.z = Infinity;
+		this.max.x = this.max.y = this.max.z = -Infinity;
+
+		return this;
+
+	},
+
+	empty: function () {
+
+		// this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+		return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z );
+
+	},
+
+	center: function ( optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+		return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+	},
+
+	size: function ( optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+		return result.subVectors( this.max, this.min );
+
+	},
+
+	expandByPoint: function ( point ) {
+
+		this.min.min( point );
+		this.max.max( point );
+
+		return this;
+
+	},
+
+	expandByVector: function ( vector ) {
+
+		this.min.sub( vector );
+		this.max.add( vector );
+
+		return this;
+
+	},
+
+	expandByScalar: function ( scalar ) {
+
+		this.min.addScalar( -scalar );
+		this.max.addScalar( scalar );
+
+		return this;
+
+	},
+
+	containsPoint: function ( point ) {
+
+		if ( point.x < this.min.x || point.x > this.max.x ||
+		     point.y < this.min.y || point.y > this.max.y ||
+		     point.z < this.min.z || point.z > this.max.z ) {
+
+			return false;
+
+		}
+
+		return true;
+
+	},
+
+	containsBox: function ( box ) {
+
+		if ( ( this.min.x <= box.min.x ) && ( box.max.x <= this.max.x ) &&
+			 ( this.min.y <= box.min.y ) && ( box.max.y <= this.max.y ) &&
+			 ( this.min.z <= box.min.z ) && ( box.max.z <= this.max.z ) ) {
+
+			return true;
+
+		}
+
+		return false;
+
+	},
+
+	getParameter: function ( point ) {
+
+		// This can potentially have a divide by zero if the box
+		// has a size dimension of 0.
+
+		return new THREE.Vector3(
+			( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+			( point.y - this.min.y ) / ( this.max.y - this.min.y ),
+			( point.z - this.min.z ) / ( this.max.z - this.min.z )
+		);
+
+	},
+
+	isIntersectionBox: function ( box ) {
+
+		// using 6 splitting planes to rule out intersections.
+
+		if ( box.max.x < this.min.x || box.min.x > this.max.x ||
+		     box.max.y < this.min.y || box.min.y > this.max.y ||
+		     box.max.z < this.min.z || box.min.z > this.max.z ) {
+
+			return false;
+
+		}
+
+		return true;
+
+	},
+
+	clampPoint: function ( point, optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+		return result.copy( point ).clamp( this.min, this.max );
+
+	},
+
+	distanceToPoint: function() {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( point ) {
+
+			var clampedPoint = v1.copy( point ).clamp( this.min, this.max );
+			return clampedPoint.sub( point ).length();
+
+		};
+
+	}(),
+
+	getBoundingSphere: function() {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( optionalTarget ) {
+
+			var result = optionalTarget || new THREE.Sphere();
+
+			result.center = this.center();
+			result.radius = this.size( v1 ).length() * 0.5;
+
+			return result;
+
+		};
+
+	}(),
+
+	intersect: function ( box ) {
+
+		this.min.max( box.min );
+		this.max.min( box.max );
+
+		return this;
+
+	},
+
+	union: function ( box ) {
+
+		this.min.min( box.min );
+		this.max.max( box.max );
+
+		return this;
+
+	},
+
+	applyMatrix4: function() {
+
+		var points = [
+			new THREE.Vector3(),
+			new THREE.Vector3(),
+			new THREE.Vector3(),
+			new THREE.Vector3(),
+			new THREE.Vector3(),
+			new THREE.Vector3(),
+			new THREE.Vector3(),
+			new THREE.Vector3()
+		];
+
+		return function ( matrix ) {
+
+			// NOTE: I am using a binary pattern to specify all 2^3 combinations below
+			points[0].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000
+			points[1].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001
+			points[2].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010
+			points[3].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011
+			points[4].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100
+			points[5].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101
+			points[6].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110
+			points[7].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix );  // 111
+
+			this.makeEmpty();
+			this.setFromPoints( points );
+
+			return this;
+
+		};
+
+	}(),
+
+	translate: function ( offset ) {
+
+		this.min.add( offset );
+		this.max.add( offset );
+
+		return this;
+
+	},
+
+	equals: function ( box ) {
+
+		return box.min.equals( this.min ) && box.max.equals( this.max );
+
+	},
+
+	clone: function () {
+
+		return new THREE.Box3().copy( this );
+
+	}
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Matrix3 = function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+	this.elements = new Float32Array(9);
+
+	this.set(
+
+		( n11 !== undefined ) ? n11 : 1, n12 || 0, n13 || 0,
+		n21 || 0, ( n22 !== undefined ) ? n22 : 1, n23 || 0,
+		n31 || 0, n32 || 0, ( n33 !== undefined ) ? n33 : 1
+
+	);
+};
+
+THREE.Matrix3.prototype = {
+
+	constructor: THREE.Matrix3,
+
+	set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+		var te = this.elements;
+
+		te[0] = n11; te[3] = n12; te[6] = n13;
+		te[1] = n21; te[4] = n22; te[7] = n23;
+		te[2] = n31; te[5] = n32; te[8] = n33;
+
+		return this;
+
+	},
+
+	identity: function () {
+
+		this.set(
+
+			1, 0, 0,
+			0, 1, 0,
+			0, 0, 1
+
+		);
+
+		return this;
+
+	},
+
+	copy: function ( m ) {
+
+		var me = m.elements;
+
+		this.set(
+
+			me[0], me[3], me[6],
+			me[1], me[4], me[7],
+			me[2], me[5], me[8]
+
+		);
+
+		return this;
+
+	},
+
+	multiplyVector3: function ( vector ) {
+
+		console.warn( 'DEPRECATED: Matrix3\'s .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );
+		return vector.applyMatrix3( this );
+
+	},
+
+	multiplyVector3Array: function() {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( a ) {
+
+			for ( var i = 0, il = a.length; i < il; i += 3 ) {
+
+				v1.x = a[ i ];
+				v1.y = a[ i + 1 ];
+				v1.z = a[ i + 2 ];
+
+				v1.applyMatrix3(this);
+
+				a[ i ]     = v1.x;
+				a[ i + 1 ] = v1.y;
+				a[ i + 2 ] = v1.z;
+
+			}
+
+			return a;
+
+		};
+
+	}(),
+
+	multiplyScalar: function ( s ) {
+
+		var te = this.elements;
+
+		te[0] *= s; te[3] *= s; te[6] *= s;
+		te[1] *= s; te[4] *= s; te[7] *= s;
+		te[2] *= s; te[5] *= s; te[8] *= s;
+
+		return this;
+
+	},
+
+	determinant: function () {
+
+		var te = this.elements;
+
+		var a = te[0], b = te[1], c = te[2],
+			d = te[3], e = te[4], f = te[5],
+			g = te[6], h = te[7], i = te[8];
+
+		return a*e*i - a*f*h - b*d*i + b*f*g + c*d*h - c*e*g;
+
+	},
+
+	getInverse: function ( matrix, throwOnInvertible ) {
+
+		// input: THREE.Matrix4
+		// ( based on http://code.google.com/p/webgl-mjs/ )
+
+		var me = matrix.elements;
+		var te = this.elements;
+
+		te[ 0 ] =   me[10] * me[5] - me[6] * me[9];
+		te[ 1 ] = - me[10] * me[1] + me[2] * me[9];
+		te[ 2 ] =   me[6] * me[1] - me[2] * me[5];
+		te[ 3 ] = - me[10] * me[4] + me[6] * me[8];
+		te[ 4 ] =   me[10] * me[0] - me[2] * me[8];
+		te[ 5 ] = - me[6] * me[0] + me[2] * me[4];
+		te[ 6 ] =   me[9] * me[4] - me[5] * me[8];
+		te[ 7 ] = - me[9] * me[0] + me[1] * me[8];
+		te[ 8 ] =   me[5] * me[0] - me[1] * me[4];
+
+		var det = me[ 0 ] * te[ 0 ] + me[ 1 ] * te[ 3 ] + me[ 2 ] * te[ 6 ];
+
+		// no inverse
+
+		if ( det === 0 ) {
+
+			var msg = "Matrix3.getInverse(): can't invert matrix, determinant is 0";
+
+			if ( throwOnInvertible || false ) {
+
+				throw new Error( msg ); 
+
+			} else {
+
+				console.warn( msg );
+
+			}
+
+			this.identity();
+
+			return this;
+
+		}
+
+		this.multiplyScalar( 1.0 / det );
+
+		return this;
+
+	},
+
+	transpose: function () {
+
+		var tmp, m = this.elements;
+
+		tmp = m[1]; m[1] = m[3]; m[3] = tmp;
+		tmp = m[2]; m[2] = m[6]; m[6] = tmp;
+		tmp = m[5]; m[5] = m[7]; m[7] = tmp;
+
+		return this;
+
+	},
+
+	getNormalMatrix: function ( m ) {
+
+		// input: THREE.Matrix4
+
+		this.getInverse( m ).transpose();
+
+		return this;
+
+	},
+
+	transposeIntoArray: function ( r ) {
+
+		var m = this.elements;
+
+		r[ 0 ] = m[ 0 ];
+		r[ 1 ] = m[ 3 ];
+		r[ 2 ] = m[ 6 ];
+		r[ 3 ] = m[ 1 ];
+		r[ 4 ] = m[ 4 ];
+		r[ 5 ] = m[ 7 ];
+		r[ 6 ] = m[ 2 ];
+		r[ 7 ] = m[ 5 ];
+		r[ 8 ] = m[ 8 ];
+
+		return this;
+
+	},
+
+	clone: function () {
+
+		var te = this.elements;
+
+		return new THREE.Matrix3(
+
+			te[0], te[3], te[6],
+			te[1], te[4], te[7],
+			te[2], te[5], te[8]
+
+		);
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author philogb / http://blog.thejit.org/
+ * @author jordi_ros / http://plattsoft.com
+ * @author D1plo1d / http://github.com/D1plo1d
+ * @author alteredq / http://alteredqualia.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author timknip / http://www.floorplanner.com/
+ * @author bhouston / http://exocortex.com
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+
+THREE.Matrix4 = function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
+
+	var te = this.elements = new Float32Array( 16 );
+
+	// TODO: if n11 is undefined, then just set to identity, otherwise copy all other values into matrix
+	//   we should not support semi specification of Matrix4, it is just weird.
+
+	te[0] = ( n11 !== undefined ) ? n11 : 1; te[4] = n12 || 0; te[8] = n13 || 0; te[12] = n14 || 0;
+	te[1] = n21 || 0; te[5] = ( n22 !== undefined ) ? n22 : 1; te[9] = n23 || 0; te[13] = n24 || 0;
+	te[2] = n31 || 0; te[6] = n32 || 0; te[10] = ( n33 !== undefined ) ? n33 : 1; te[14] = n34 || 0;
+	te[3] = n41 || 0; te[7] = n42 || 0; te[11] = n43 || 0; te[15] = ( n44 !== undefined ) ? n44 : 1;
+
+};
+
+THREE.Matrix4.prototype = {
+
+	constructor: THREE.Matrix4,
+
+	set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
+
+		var te = this.elements;
+
+		te[0] = n11; te[4] = n12; te[8] = n13; te[12] = n14;
+		te[1] = n21; te[5] = n22; te[9] = n23; te[13] = n24;
+		te[2] = n31; te[6] = n32; te[10] = n33; te[14] = n34;
+		te[3] = n41; te[7] = n42; te[11] = n43; te[15] = n44;
+
+		return this;
+
+	},
+
+	identity: function () {
+
+		this.set(
+
+			1, 0, 0, 0,
+			0, 1, 0, 0,
+			0, 0, 1, 0,
+			0, 0, 0, 1
+
+		);
+
+		return this;
+
+	},
+
+	copy: function ( m ) {
+
+		var me = m.elements;
+
+		this.set(
+
+			me[0], me[4], me[8], me[12],
+			me[1], me[5], me[9], me[13],
+			me[2], me[6], me[10], me[14],
+			me[3], me[7], me[11], me[15]
+
+		);
+
+		return this;
+
+	},
+
+	extractPosition: function ( m ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .extractPosition() has been renamed to .copyPosition().' );
+		return this.copyPosition( m );
+
+	},
+
+	copyPosition: function ( m ) {
+
+		var te = this.elements;
+		var me = m.elements;
+
+		te[12] = me[12];
+		te[13] = me[13];
+		te[14] = me[14];
+
+		return this;
+
+	},
+
+	extractRotation: function () {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( m ) {
+
+			var te = this.elements;
+			var me = m.elements;
+
+			var scaleX = 1 / v1.set( me[0], me[1], me[2] ).length();
+			var scaleY = 1 / v1.set( me[4], me[5], me[6] ).length();
+			var scaleZ = 1 / v1.set( me[8], me[9], me[10] ).length();
+
+			te[0] = me[0] * scaleX;
+			te[1] = me[1] * scaleX;
+			te[2] = me[2] * scaleX;
+
+			te[4] = me[4] * scaleY;
+			te[5] = me[5] * scaleY;
+			te[6] = me[6] * scaleY;
+
+			te[8] = me[8] * scaleZ;
+			te[9] = me[9] * scaleZ;
+			te[10] = me[10] * scaleZ;
+
+			return this;
+
+		};
+
+	}(),
+
+	setRotationFromEuler: function ( v, order ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .setRotationFromEuler() has been deprecated in favor of makeRotationFromEuler.  Please update your code.' );
+
+		return this.makeRotationFromEuler( v, order );
+
+	},
+
+	makeRotationFromEuler: function ( v, order ) {
+
+		var te = this.elements;
+
+		var x = v.x, y = v.y, z = v.z;
+		var a = Math.cos( x ), b = Math.sin( x );
+		var c = Math.cos( y ), d = Math.sin( y );
+		var e = Math.cos( z ), f = Math.sin( z );
+
+		if ( order === undefined || order === 'XYZ' ) {
+
+			var ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+			te[0] = c * e;
+			te[4] = - c * f;
+			te[8] = d;
+
+			te[1] = af + be * d;
+			te[5] = ae - bf * d;
+			te[9] = - b * c;
+
+			te[2] = bf - ae * d;
+			te[6] = be + af * d;
+			te[10] = a * c;
+
+		} else if ( order === 'YXZ' ) {
+
+			var ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+			te[0] = ce + df * b;
+			te[4] = de * b - cf;
+			te[8] = a * d;
+
+			te[1] = a * f;
+			te[5] = a * e;
+			te[9] = - b;
+
+			te[2] = cf * b - de;
+			te[6] = df + ce * b;
+			te[10] = a * c;
+
+		} else if ( order === 'ZXY' ) {
+
+			var ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+			te[0] = ce - df * b;
+			te[4] = - a * f;
+			te[8] = de + cf * b;
+
+			te[1] = cf + de * b;
+			te[5] = a * e;
+			te[9] = df - ce * b;
+
+			te[2] = - a * d;
+			te[6] = b;
+			te[10] = a * c;
+
+		} else if ( order === 'ZYX' ) {
+
+			var ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+			te[0] = c * e;
+			te[4] = be * d - af;
+			te[8] = ae * d + bf;
+
+			te[1] = c * f;
+			te[5] = bf * d + ae;
+			te[9] = af * d - be;
+
+			te[2] = - d;
+			te[6] = b * c;
+			te[10] = a * c;
+
+		} else if ( order === 'YZX' ) {
+
+			var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+			te[0] = c * e;
+			te[4] = bd - ac * f;
+			te[8] = bc * f + ad;
+
+			te[1] = f;
+			te[5] = a * e;
+			te[9] = - b * e;
+
+			te[2] = - d * e;
+			te[6] = ad * f + bc;
+			te[10] = ac - bd * f;
+
+		} else if ( order === 'XZY' ) {
+
+			var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+			te[0] = c * e;
+			te[4] = - f;
+			te[8] = d * e;
+
+			te[1] = ac * f + bd;
+			te[5] = a * e;
+			te[9] = ad * f - bc;
+
+			te[2] = bc * f - ad;
+			te[6] = b * e;
+			te[10] = bd * f + ac;
+
+		}
+
+		// last column
+		te[3] = 0;
+		te[7] = 0;
+		te[11] = 0;
+
+		// bottom row
+		te[12] = 0;
+		te[13] = 0;
+		te[14] = 0;
+		te[15] = 1;
+
+		return this;
+
+	},
+
+	setRotationFromQuaternion: function ( q ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .setRotationFromQuaternion() has been deprecated in favor of makeRotationFromQuaternion.  Please update your code.' );
+
+		return this.makeRotationFromQuaternion( q );
+
+	},
+
+	makeRotationFromQuaternion: function ( q ) {
+
+		var te = this.elements;
+
+		var x = q.x, y = q.y, z = q.z, w = q.w;
+		var x2 = x + x, y2 = y + y, z2 = z + z;
+		var xx = x * x2, xy = x * y2, xz = x * z2;
+		var yy = y * y2, yz = y * z2, zz = z * z2;
+		var wx = w * x2, wy = w * y2, wz = w * z2;
+
+		te[0] = 1 - ( yy + zz );
+		te[4] = xy - wz;
+		te[8] = xz + wy;
+
+		te[1] = xy + wz;
+		te[5] = 1 - ( xx + zz );
+		te[9] = yz - wx;
+
+		te[2] = xz - wy;
+		te[6] = yz + wx;
+		te[10] = 1 - ( xx + yy );
+
+		// last column
+		te[3] = 0;
+		te[7] = 0;
+		te[11] = 0;
+
+		// bottom row
+		te[12] = 0;
+		te[13] = 0;
+		te[14] = 0;
+		te[15] = 1;
+
+		return this;
+
+	},
+
+	lookAt: function() {
+
+		var x = new THREE.Vector3();
+		var y = new THREE.Vector3();
+		var z = new THREE.Vector3();
+
+		return function ( eye, target, up ) {
+
+			var te = this.elements;
+
+			z.subVectors( eye, target ).normalize();
+
+			if ( z.length() === 0 ) {
+
+				z.z = 1;
+
+			}
+
+			x.crossVectors( up, z ).normalize();
+
+			if ( x.length() === 0 ) {
+
+				z.x += 0.0001;
+				x.crossVectors( up, z ).normalize();
+
+			}
+
+			y.crossVectors( z, x );
+
+
+			te[0] = x.x; te[4] = y.x; te[8] = z.x;
+			te[1] = x.y; te[5] = y.y; te[9] = z.y;
+			te[2] = x.z; te[6] = y.z; te[10] = z.z;
+
+			return this;
+
+		};
+
+	}(),
+
+	multiply: function ( m, n ) {
+
+		if ( n !== undefined ) {
+
+			console.warn( 'DEPRECATED: Matrix4\'s .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );
+			return this.multiplyMatrices( m, n );
+
+		}
+
+		return this.multiplyMatrices( this, m );
+
+	},
+
+	multiplyMatrices: function ( a, b ) {
+
+		var ae = a.elements;
+		var be = b.elements;
+		var te = this.elements;
+
+		var a11 = ae[0], a12 = ae[4], a13 = ae[8], a14 = ae[12];
+		var a21 = ae[1], a22 = ae[5], a23 = ae[9], a24 = ae[13];
+		var a31 = ae[2], a32 = ae[6], a33 = ae[10], a34 = ae[14];
+		var a41 = ae[3], a42 = ae[7], a43 = ae[11], a44 = ae[15];
+
+		var b11 = be[0], b12 = be[4], b13 = be[8], b14 = be[12];
+		var b21 = be[1], b22 = be[5], b23 = be[9], b24 = be[13];
+		var b31 = be[2], b32 = be[6], b33 = be[10], b34 = be[14];
+		var b41 = be[3], b42 = be[7], b43 = be[11], b44 = be[15];
+
+		te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
+		te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
+		te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
+		te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
+
+		te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
+		te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
+		te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
+		te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
+
+		te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
+		te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
+		te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
+		te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
+
+		te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
+		te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
+		te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
+		te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
+
+		return this;
+
+	},
+
+	multiplyToArray: function ( a, b, r ) {
+
+		var te = this.elements;
+
+		this.multiplyMatrices( a, b );
+
+		r[ 0 ] = te[0]; r[ 1 ] = te[1]; r[ 2 ] = te[2]; r[ 3 ] = te[3];
+		r[ 4 ] = te[4]; r[ 5 ] = te[5]; r[ 6 ] = te[6]; r[ 7 ] = te[7];
+		r[ 8 ]  = te[8]; r[ 9 ]  = te[9]; r[ 10 ] = te[10]; r[ 11 ] = te[11];
+		r[ 12 ] = te[12]; r[ 13 ] = te[13]; r[ 14 ] = te[14]; r[ 15 ] = te[15];
+
+		return this;
+
+	},
+
+	multiplyScalar: function ( s ) {
+
+		var te = this.elements;
+
+		te[0] *= s; te[4] *= s; te[8] *= s; te[12] *= s;
+		te[1] *= s; te[5] *= s; te[9] *= s; te[13] *= s;
+		te[2] *= s; te[6] *= s; te[10] *= s; te[14] *= s;
+		te[3] *= s; te[7] *= s; te[11] *= s; te[15] *= s;
+
+		return this;
+
+	},
+
+	multiplyVector3: function ( vector ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) or vector.applyProjection( matrix ) instead.' );
+		return vector.applyProjection( this );
+
+	},
+
+	multiplyVector4: function ( vector ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+		return vector.applyMatrix4( this );
+
+	},
+
+	multiplyVector3Array: function() {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( a ) {
+
+			for ( var i = 0, il = a.length; i < il; i += 3 ) {
+
+				v1.x = a[ i ];
+				v1.y = a[ i + 1 ];
+				v1.z = a[ i + 2 ];
+
+				v1.applyProjection( this );
+
+				a[ i ]     = v1.x;
+				a[ i + 1 ] = v1.y;
+				a[ i + 2 ] = v1.z;
+
+			}
+
+			return a;
+
+		};
+
+	}(),
+
+	rotateAxis: function ( v ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' );
+
+		v.transformDirection( this );
+
+	},
+
+	crossVector: function ( vector ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+		return vector.applyMatrix4( this );
+
+	},
+
+	determinant: function () {
+
+		var te = this.elements;
+
+		var n11 = te[0], n12 = te[4], n13 = te[8], n14 = te[12];
+		var n21 = te[1], n22 = te[5], n23 = te[9], n24 = te[13];
+		var n31 = te[2], n32 = te[6], n33 = te[10], n34 = te[14];
+		var n41 = te[3], n42 = te[7], n43 = te[11], n44 = te[15];
+
+		//TODO: make this more efficient
+		//( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
+
+		return (
+			n41 * (
+				+n14 * n23 * n32
+				-n13 * n24 * n32
+				-n14 * n22 * n33
+				+n12 * n24 * n33
+				+n13 * n22 * n34
+				-n12 * n23 * n34
+			) +
+			n42 * (
+				+n11 * n23 * n34
+				-n11 * n24 * n33
+				+n14 * n21 * n33
+				-n13 * n21 * n34
+				+n13 * n24 * n31
+				-n14 * n23 * n31
+			) +
+			n43 * (
+				+n11 * n24 * n32
+				-n11 * n22 * n34
+				-n14 * n21 * n32
+				+n12 * n21 * n34
+				+n14 * n22 * n31
+				-n12 * n24 * n31
+			) +
+			n44 * (
+				-n13 * n22 * n31
+				-n11 * n23 * n32
+				+n11 * n22 * n33
+				+n13 * n21 * n32
+				-n12 * n21 * n33
+				+n12 * n23 * n31
+			)
+
+		);
+
+	},
+
+	transpose: function () {
+
+		var te = this.elements;
+		var tmp;
+
+		tmp = te[1]; te[1] = te[4]; te[4] = tmp;
+		tmp = te[2]; te[2] = te[8]; te[8] = tmp;
+		tmp = te[6]; te[6] = te[9]; te[9] = tmp;
+
+		tmp = te[3]; te[3] = te[12]; te[12] = tmp;
+		tmp = te[7]; te[7] = te[13]; te[13] = tmp;
+		tmp = te[11]; te[11] = te[14]; te[14] = tmp;
+
+		return this;
+
+	},
+
+	flattenToArray: function ( flat ) {
+
+		var te = this.elements;
+		flat[ 0 ] = te[0]; flat[ 1 ] = te[1]; flat[ 2 ] = te[2]; flat[ 3 ] = te[3];
+		flat[ 4 ] = te[4]; flat[ 5 ] = te[5]; flat[ 6 ] = te[6]; flat[ 7 ] = te[7];
+		flat[ 8 ] = te[8]; flat[ 9 ] = te[9]; flat[ 10 ] = te[10]; flat[ 11 ] = te[11];
+		flat[ 12 ] = te[12]; flat[ 13 ] = te[13]; flat[ 14 ] = te[14]; flat[ 15 ] = te[15];
+
+		return flat;
+
+	},
+
+	flattenToArrayOffset: function( flat, offset ) {
+
+		var te = this.elements;
+		flat[ offset ] = te[0];
+		flat[ offset + 1 ] = te[1];
+		flat[ offset + 2 ] = te[2];
+		flat[ offset + 3 ] = te[3];
+
+		flat[ offset + 4 ] = te[4];
+		flat[ offset + 5 ] = te[5];
+		flat[ offset + 6 ] = te[6];
+		flat[ offset + 7 ] = te[7];
+
+		flat[ offset + 8 ]  = te[8];
+		flat[ offset + 9 ]  = te[9];
+		flat[ offset + 10 ] = te[10];
+		flat[ offset + 11 ] = te[11];
+
+		flat[ offset + 12 ] = te[12];
+		flat[ offset + 13 ] = te[13];
+		flat[ offset + 14 ] = te[14];
+		flat[ offset + 15 ] = te[15];
+
+		return flat;
+
+	},
+
+	getPosition: function() {
+
+		var v1 = new THREE.Vector3();
+
+		return function () {
+
+			console.warn( 'DEPRECATED: Matrix4\'s .getPosition() has been removed. Use Vector3.getPositionFromMatrix( matrix ) instead.' );
+
+			var te = this.elements;
+			return v1.set( te[12], te[13], te[14] );
+
+		};
+
+	}(),
+
+	setPosition: function ( v ) {
+
+		var te = this.elements;
+
+		te[12] = v.x;
+		te[13] = v.y;
+		te[14] = v.z;
+
+		return this;
+
+	},
+
+	getInverse: function ( m, throwOnInvertible ) {
+
+		// based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
+		var te = this.elements;
+		var me = m.elements;
+
+		var n11 = me[0], n12 = me[4], n13 = me[8], n14 = me[12];
+		var n21 = me[1], n22 = me[5], n23 = me[9], n24 = me[13];
+		var n31 = me[2], n32 = me[6], n33 = me[10], n34 = me[14];
+		var n41 = me[3], n42 = me[7], n43 = me[11], n44 = me[15];
+
+		te[0] = n23*n34*n42 - n24*n33*n42 + n24*n32*n43 - n22*n34*n43 - n23*n32*n44 + n22*n33*n44;
+		te[4] = n14*n33*n42 - n13*n34*n42 - n14*n32*n43 + n12*n34*n43 + n13*n32*n44 - n12*n33*n44;
+		te[8] = n13*n24*n42 - n14*n23*n42 + n14*n22*n43 - n12*n24*n43 - n13*n22*n44 + n12*n23*n44;
+		te[12] = n14*n23*n32 - n13*n24*n32 - n14*n22*n33 + n12*n24*n33 + n13*n22*n34 - n12*n23*n34;
+		te[1] = n24*n33*n41 - n23*n34*n41 - n24*n31*n43 + n21*n34*n43 + n23*n31*n44 - n21*n33*n44;
+		te[5] = n13*n34*n41 - n14*n33*n41 + n14*n31*n43 - n11*n34*n43 - n13*n31*n44 + n11*n33*n44;
+		te[9] = n14*n23*n41 - n13*n24*n41 - n14*n21*n43 + n11*n24*n43 + n13*n21*n44 - n11*n23*n44;
+		te[13] = n13*n24*n31 - n14*n23*n31 + n14*n21*n33 - n11*n24*n33 - n13*n21*n34 + n11*n23*n34;
+		te[2] = n22*n34*n41 - n24*n32*n41 + n24*n31*n42 - n21*n34*n42 - n22*n31*n44 + n21*n32*n44;
+		te[6] = n14*n32*n41 - n12*n34*n41 - n14*n31*n42 + n11*n34*n42 + n12*n31*n44 - n11*n32*n44;
+		te[10] = n12*n24*n41 - n14*n22*n41 + n14*n21*n42 - n11*n24*n42 - n12*n21*n44 + n11*n22*n44;
+		te[14] = n14*n22*n31 - n12*n24*n31 - n14*n21*n32 + n11*n24*n32 + n12*n21*n34 - n11*n22*n34;
+		te[3] = n23*n32*n41 - n22*n33*n41 - n23*n31*n42 + n21*n33*n42 + n22*n31*n43 - n21*n32*n43;
+		te[7] = n12*n33*n41 - n13*n32*n41 + n13*n31*n42 - n11*n33*n42 - n12*n31*n43 + n11*n32*n43;
+		te[11] = n13*n22*n41 - n12*n23*n41 - n13*n21*n42 + n11*n23*n42 + n12*n21*n43 - n11*n22*n43;
+		te[15] = n12*n23*n31 - n13*n22*n31 + n13*n21*n32 - n11*n23*n32 - n12*n21*n33 + n11*n22*n33;
+
+		var det = me[ 0 ] * te[ 0 ] + me[ 1 ] * te[ 4 ] + me[ 2 ] * te[ 8 ] + me[ 3 ] * te[ 12 ];
+
+		if ( det == 0 ) {
+
+			var msg = "Matrix4.getInverse(): can't invert matrix, determinant is 0";
+
+			if ( throwOnInvertible || false ) {
+
+				throw new Error( msg ); 
+
+			} else {
+
+				console.warn( msg );
+
+			}
+
+			this.identity();
+
+			return this;
+		}
+
+		this.multiplyScalar( 1 / det );
+
+		return this;
+
+	},
+
+	translate: function ( v ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .translate() has been removed.');
+
+	},
+
+	rotateX: function ( angle ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .rotateX() has been removed.');
+
+	},
+
+	rotateY: function ( angle ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .rotateY() has been removed.');
+
+	},
+
+	rotateZ: function ( angle ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .rotateZ() has been removed.');
+
+	},
+
+	rotateByAxis: function ( axis, angle ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .rotateByAxis() has been removed.');
+
+	},
+
+	scale: function ( v ) {
+
+		var te = this.elements;
+		var x = v.x, y = v.y, z = v.z;
+
+		te[0] *= x; te[4] *= y; te[8] *= z;
+		te[1] *= x; te[5] *= y; te[9] *= z;
+		te[2] *= x; te[6] *= y; te[10] *= z;
+		te[3] *= x; te[7] *= y; te[11] *= z;
+
+		return this;
+
+	},
+
+	getMaxScaleOnAxis: function () {
+
+		var te = this.elements;
+
+		var scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2];
+		var scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6];
+		var scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10];
+
+		return Math.sqrt( Math.max( scaleXSq, Math.max( scaleYSq, scaleZSq ) ) );
+
+	},
+
+	makeTranslation: function ( x, y, z ) {
+
+		this.set(
+
+			1, 0, 0, x,
+			0, 1, 0, y,
+			0, 0, 1, z,
+			0, 0, 0, 1
+
+		);
+
+		return this;
+
+	},
+
+	makeRotationX: function ( theta ) {
+
+		var c = Math.cos( theta ), s = Math.sin( theta );
+
+		this.set(
+
+			1, 0,  0, 0,
+			0, c, -s, 0,
+			0, s,  c, 0,
+			0, 0,  0, 1
+
+		);
+
+		return this;
+
+	},
+
+	makeRotationY: function ( theta ) {
+
+		var c = Math.cos( theta ), s = Math.sin( theta );
+
+		this.set(
+
+			 c, 0, s, 0,
+			 0, 1, 0, 0,
+			-s, 0, c, 0,
+			 0, 0, 0, 1
+
+		);
+
+		return this;
+
+	},
+
+	makeRotationZ: function ( theta ) {
+
+		var c = Math.cos( theta ), s = Math.sin( theta );
+
+		this.set(
+
+			c, -s, 0, 0,
+			s,  c, 0, 0,
+			0,  0, 1, 0,
+			0,  0, 0, 1
+
+		);
+
+		return this;
+
+	},
+
+	makeRotationAxis: function ( axis, angle ) {
+
+		// Based on http://www.gamedev.net/reference/articles/article1199.asp
+
+		var c = Math.cos( angle );
+		var s = Math.sin( angle );
+		var t = 1 - c;
+		var x = axis.x, y = axis.y, z = axis.z;
+		var tx = t * x, ty = t * y;
+
+		this.set(
+
+			tx * x + c, tx * y - s * z, tx * z + s * y, 0,
+			tx * y + s * z, ty * y + c, ty * z - s * x, 0,
+			tx * z - s * y, ty * z + s * x, t * z * z + c, 0,
+			0, 0, 0, 1
+
+		);
+
+		 return this;
+
+	},
+
+	makeScale: function ( x, y, z ) {
+
+		this.set(
+
+			x, 0, 0, 0,
+			0, y, 0, 0,
+			0, 0, z, 0,
+			0, 0, 0, 1
+
+		);
+
+		return this;
+
+	},
+
+	compose: function ( position, quaternion, scale ) {
+
+		console.warn( 'DEPRECATED: Matrix4\'s .compose() has been deprecated in favor of makeFromPositionQuaternionScale. Please update your code.' );
+
+		return this.makeFromPositionQuaternionScale( position, quaternion, scale );
+
+	},
+
+	makeFromPositionQuaternionScale: function ( position, quaternion, scale ) {
+
+		this.makeRotationFromQuaternion( quaternion );
+		this.scale( scale );
+		this.setPosition( position );
+
+		return this;
+
+	},
+
+	makeFromPositionEulerScale: function ( position, rotation, eulerOrder, scale ) {
+
+		this.makeRotationFromEuler( rotation, eulerOrder );
+		this.scale( scale );
+		this.setPosition( position );
+
+		return this;
+
+	},
+
+	makeFrustum: function ( left, right, bottom, top, near, far ) {
+
+		var te = this.elements;
+		var x = 2 * near / ( right - left );
+		var y = 2 * near / ( top - bottom );
+
+		var a = ( right + left ) / ( right - left );
+		var b = ( top + bottom ) / ( top - bottom );
+		var c = - ( far + near ) / ( far - near );
+		var d = - 2 * far * near / ( far - near );
+
+		te[0] = x;	te[4] = 0;	te[8] = a;	te[12] = 0;
+		te[1] = 0;	te[5] = y;	te[9] = b;	te[13] = 0;
+		te[2] = 0;	te[6] = 0;	te[10] = c;	te[14] = d;
+		te[3] = 0;	te[7] = 0;	te[11] = - 1;	te[15] = 0;
+
+		return this;
+
+	},
+
+	makePerspective: function ( fov, aspect, near, far ) {
+
+		var ymax = near * Math.tan( THREE.Math.degToRad( fov * 0.5 ) );
+		var ymin = - ymax;
+		var xmin = ymin * aspect;
+		var xmax = ymax * aspect;
+
+		return this.makeFrustum( xmin, xmax, ymin, ymax, near, far );
+
+	},
+
+	makeOrthographic: function ( left, right, top, bottom, near, far ) {
+
+		var te = this.elements;
+		var w = right - left;
+		var h = top - bottom;
+		var p = far - near;
+
+		var x = ( right + left ) / w;
+		var y = ( top + bottom ) / h;
+		var z = ( far + near ) / p;
+
+		te[0] = 2 / w;	te[4] = 0;	te[8] = 0;	te[12] = -x;
+		te[1] = 0;	te[5] = 2 / h;	te[9] = 0;	te[13] = -y;
+		te[2] = 0;	te[6] = 0;	te[10] = -2/p;	te[14] = -z;
+		te[3] = 0;	te[7] = 0;	te[11] = 0;	te[15] = 1;
+
+		return this;
+
+	},
+
+	clone: function () {
+
+		var te = this.elements;
+
+		return new THREE.Matrix4(
+
+			te[0], te[4], te[8], te[12],
+			te[1], te[5], te[9], te[13],
+			te[2], te[6], te[10], te[14],
+			te[3], te[7], te[11], te[15]
+
+		);
+
+	}
+
+};
+
+THREE.extend( THREE.Matrix4.prototype, {
+
+	decompose: function() {
+
+		var x = new THREE.Vector3();
+		var y = new THREE.Vector3();
+		var z = new THREE.Vector3();
+		var matrix = new THREE.Matrix4();
+
+		return function ( position, quaternion, scale ) {
+
+			var te = this.elements;
+
+			// grab the axis vectors
+			x.set( te[0], te[1], te[2] );
+			y.set( te[4], te[5], te[6] );
+			z.set( te[8], te[9], te[10] );
+
+			position = ( position instanceof THREE.Vector3 ) ? position : new THREE.Vector3();
+			quaternion = ( quaternion instanceof THREE.Quaternion ) ? quaternion : new THREE.Quaternion();
+			scale = ( scale instanceof THREE.Vector3 ) ? scale : new THREE.Vector3();
+
+			scale.x = x.length();
+			scale.y = y.length();
+			scale.z = z.length();
+
+			position.x = te[12];
+			position.y = te[13];
+			position.z = te[14];
+
+			// scale the rotation part
+
+			matrix.copy( this );
+
+			matrix.elements[0] /= scale.x;
+			matrix.elements[1] /= scale.x;
+			matrix.elements[2] /= scale.x;
+
+			matrix.elements[4] /= scale.y;
+			matrix.elements[5] /= scale.y;
+			matrix.elements[6] /= scale.y;
+
+			matrix.elements[8] /= scale.z;
+			matrix.elements[9] /= scale.z;
+			matrix.elements[10] /= scale.z;
+
+			quaternion.setFromRotationMatrix( matrix );
+
+			return [ position, quaternion, scale ];
+
+		};
+
+	}()
+
+} );
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Ray = function ( origin, direction ) {
+
+	this.origin = ( origin !== undefined ) ? origin : new THREE.Vector3();
+	this.direction = ( direction !== undefined ) ? direction : new THREE.Vector3();
+
+};
+
+THREE.Ray.prototype = {
+
+	constructor: THREE.Ray,
+
+	set: function ( origin, direction ) {
+
+		this.origin.copy( origin );
+		this.direction.copy( direction );
+
+		return this;
+
+	},
+
+	copy: function ( ray ) {
+
+		this.origin.copy( ray.origin );
+		this.direction.copy( ray.direction );
+
+		return this;
+
+	},
+
+	at: function( t, optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+
+		return result.copy( this.direction ).multiplyScalar( t ).add( this.origin );
+
+	},
+
+	recast: function() {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( t ) {
+
+			this.origin.copy( this.at( t, v1 ) );
+
+			return this;
+
+		};
+
+	}(),
+
+	closestPointToPoint: function ( point, optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+		result.subVectors( point, this.origin );
+		var directionDistance = result.dot( this.direction );
+
+		return result.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+
+	},
+
+	distanceToPoint: function() {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( point ) {
+
+			var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction );
+			v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+
+			return v1.distanceTo( point );
+
+		};
+
+	}(),
+
+	isIntersectionSphere: function( sphere ) {
+
+		return ( this.distanceToPoint( sphere.center ) <= sphere.radius );
+
+	},
+
+	isIntersectionPlane: function ( plane ) {
+
+		// check if the line and plane are non-perpendicular, if they
+		// eventually they will intersect.
+		var denominator = plane.normal.dot( this.direction );
+		if ( denominator != 0 ) {
+
+			return true;
+
+		}
+
+		// line is coplanar, return origin
+		if( plane.distanceToPoint( this.origin ) == 0 ) {
+
+			return true;
+
+		}
+
+		return false;
+
+	},
+
+	distanceToPlane: function ( plane ) {
+
+		var denominator = plane.normal.dot( this.direction );
+		if ( denominator == 0 ) {
+
+			// line is coplanar, return origin
+			if( plane.distanceToPoint( this.origin ) == 0 ) {
+
+				return 0;
+
+			}
+
+			// Unsure if this is the correct method to handle this case.
+			return undefined;
+
+		}
+
+		var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
+
+		return t;
+
+	},
+
+	intersectPlane: function ( plane, optionalTarget ) {
+
+		var t = this.distanceToPlane( plane );
+
+		if ( t === undefined ) {
+
+			return undefined;
+		}
+
+		return this.at( t, optionalTarget );
+
+	},
+
+	applyMatrix4: function ( matrix4 ) {
+
+		this.direction.add( this.origin ).applyMatrix4( matrix4 );
+		this.origin.applyMatrix4( matrix4 );
+		this.direction.sub( this.origin );
+
+		return this;
+	},
+
+	equals: function ( ray ) {
+
+		return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );
+
+	},
+
+	clone: function () {
+
+		return new THREE.Ray().copy( this );
+
+	}
+
+};
+/**
+ * @author bhouston / http://exocortex.com
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Sphere = function ( center, radius ) {
+
+	this.center = ( center !== undefined ) ? center : new THREE.Vector3();
+	this.radius = ( radius !== undefined ) ? radius : 0;
+
+};
+
+THREE.Sphere.prototype = {
+
+	constructor: THREE.Sphere,
+
+	set: function ( center, radius ) {
+
+		this.center.copy( center );
+		this.radius = radius;
+
+		return this;
+	},
+
+	setFromCenterAndPoints: function ( center, points ) {
+
+		var maxRadiusSq = 0;
+
+		for ( var i = 0, il = points.length; i < il; i ++ ) {
+
+			var radiusSq = center.distanceToSquared( points[ i ] );
+			maxRadiusSq = Math.max( maxRadiusSq, radiusSq );
+
+		}
+
+		this.center = center;
+		this.radius = Math.sqrt( maxRadiusSq );
+
+		return this;
+
+	},
+
+	copy: function ( sphere ) {
+
+		this.center.copy( sphere.center );
+		this.radius = sphere.radius;
+
+		return this;
+
+	},
+
+	empty: function () {
+
+		return ( this.radius <= 0 );
+
+	},
+
+	containsPoint: function ( point ) {
+
+		return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) );
+
+	},
+
+	distanceToPoint: function ( point ) {
+
+		return ( point.distanceTo( this.center ) - this.radius );
+
+	},
+
+	intersectsSphere: function ( sphere ) {
+
+		var radiusSum = this.radius + sphere.radius;
+
+		return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum );
+
+	},
+
+	clampPoint: function ( point, optionalTarget ) {
+
+		var deltaLengthSq = this.center.distanceToSquared( point );
+
+		var result = optionalTarget || new THREE.Vector3();
+		result.copy( point );
+
+		if ( deltaLengthSq > ( this.radius * this.radius ) ) {
+
+			result.sub( this.center ).normalize();
+			result.multiplyScalar( this.radius ).add( this.center );
+
+		}
+
+		return result;
+
+	},
+
+	getBoundingBox: function ( optionalTarget ) {
+
+		var box = optionalTarget || new THREE.Box3();
+
+		box.set( this.center, this.center );
+		box.expandByScalar( this.radius );
+
+		return box;
+
+	},
+
+	applyMatrix4: function ( matrix ) {
+
+		this.center.applyMatrix4( matrix );
+		this.radius = this.radius * matrix.getMaxScaleOnAxis();
+
+		return this;
+
+	},
+
+	translate: function ( offset ) {
+
+		this.center.add( offset );
+
+		return this;
+
+	},
+
+	equals: function ( sphere ) {
+
+		return sphere.center.equals( this.center ) && ( sphere.radius === this.radius );
+
+	},
+
+	clone: function () {
+
+		return new THREE.Sphere().copy( this );
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Frustum = function ( p0, p1, p2, p3, p4, p5 ) {
+
+	this.planes = [
+
+		( p0 !== undefined ) ? p0 : new THREE.Plane(),
+		( p1 !== undefined ) ? p1 : new THREE.Plane(),
+		( p2 !== undefined ) ? p2 : new THREE.Plane(),
+		( p3 !== undefined ) ? p3 : new THREE.Plane(),
+		( p4 !== undefined ) ? p4 : new THREE.Plane(),
+		( p5 !== undefined ) ? p5 : new THREE.Plane()
+
+	];
+
+};
+
+THREE.Frustum.prototype = {
+
+	constructor: THREE.Frustum,
+
+	set: function ( p0, p1, p2, p3, p4, p5 ) {
+
+		var planes = this.planes;
+
+		planes[0].copy( p0 );
+		planes[1].copy( p1 );
+		planes[2].copy( p2 );
+		planes[3].copy( p3 );
+		planes[4].copy( p4 );
+		planes[5].copy( p5 );
+
+		return this;
+
+	},
+
+	copy: function ( frustum ) {
+
+		var planes = this.planes;
+
+		for( var i = 0; i < 6; i ++ ) {
+
+			planes[i].copy( frustum.planes[i] );
+
+		}
+
+		return this;
+
+	},
+
+	setFromMatrix: function ( m ) {
+
+		var planes = this.planes;
+		var me = m.elements;
+		var me0 = me[0], me1 = me[1], me2 = me[2], me3 = me[3];
+		var me4 = me[4], me5 = me[5], me6 = me[6], me7 = me[7];
+		var me8 = me[8], me9 = me[9], me10 = me[10], me11 = me[11];
+		var me12 = me[12], me13 = me[13], me14 = me[14], me15 = me[15];
+
+		planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();
+		planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();
+		planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();
+		planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();
+		planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();
+		planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();
+
+		return this;
+
+	},
+
+	intersectsObject: function () {
+
+		var center = new THREE.Vector3();
+
+		return function ( object ) {
+
+			// this method is expanded inlined for performance reasons.
+
+			var matrix = object.matrixWorld;
+			var planes = this.planes;
+			var negRadius = - object.geometry.boundingSphere.radius * matrix.getMaxScaleOnAxis();
+
+			center.getPositionFromMatrix( matrix );
+
+			for ( var i = 0; i < 6; i ++ ) {
+
+				var distance = planes[ i ].distanceToPoint( center );
+
+				if ( distance < negRadius ) {
+
+					return false;
+
+				}
+
+			}
+
+			return true;
+
+		};
+
+	}(),
+
+	intersectsSphere: function ( sphere ) {
+
+		var planes = this.planes;
+		var center = sphere.center;
+		var negRadius = -sphere.radius;
+
+		for ( var i = 0; i < 6; i ++ ) {
+
+			var distance = planes[ i ].distanceToPoint( center );
+
+			if ( distance < negRadius ) {
+
+				return false;
+
+			}
+
+		}
+
+		return true;
+
+	},
+
+	containsPoint: function ( point ) {
+
+		var planes = this.planes;
+
+		for ( var i = 0; i < 6; i ++ ) {
+
+			if ( planes[ i ].distanceToPoint( point ) < 0 ) {
+
+				return false;
+
+			}
+
+		}
+
+		return true;
+
+	},
+
+	clone: function () {
+
+		return new THREE.Frustum().copy( this );
+
+	}
+
+};
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Plane = function ( normal, constant ) {
+
+	this.normal = ( normal !== undefined ) ? normal : new THREE.Vector3( 1, 0, 0 );
+	this.constant = ( constant !== undefined ) ? constant : 0;
+
+};
+
+THREE.Plane.prototype = {
+
+	constructor: THREE.Plane,
+
+	set: function ( normal, constant ) {
+
+		this.normal.copy( normal );
+		this.constant = constant;
+
+		return this;
+
+	},
+
+	setComponents: function ( x, y, z, w ) {
+
+		this.normal.set( x, y, z );
+		this.constant = w;
+
+		return this;
+
+	},
+
+	setFromNormalAndCoplanarPoint: function ( normal, point ) {
+
+		this.normal.copy( normal );
+		this.constant = - point.dot( this.normal );	// must be this.normal, not normal, as this.normal is normalized
+
+		return this;
+
+	},
+
+	setFromCoplanarPoints: function() {
+
+		var v1 = new THREE.Vector3();
+		var v2 = new THREE.Vector3();
+
+		return function ( a, b, c ) {
+
+			var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize();
+
+			// Q: should an error be thrown if normal is zero (e.g. degenerate plane)?
+
+			this.setFromNormalAndCoplanarPoint( normal, a );
+
+			return this;
+
+		};
+
+	}(),
+
+
+	copy: function ( plane ) {
+
+		this.normal.copy( plane.normal );
+		this.constant = plane.constant;
+
+		return this;
+
+	},
+
+	normalize: function () {
+
+		// Note: will lead to a divide by zero if the plane is invalid.
+
+		var inverseNormalLength = 1.0 / this.normal.length();
+		this.normal.multiplyScalar( inverseNormalLength );
+		this.constant *= inverseNormalLength;
+
+		return this;
+
+	},
+
+	negate: function () {
+
+		this.constant *= -1;
+		this.normal.negate();
+
+		return this;
+
+	},
+
+	distanceToPoint: function ( point ) {
+
+		return this.normal.dot( point ) + this.constant;
+
+	},
+
+	distanceToSphere: function ( sphere ) {
+
+		return this.distanceToPoint( sphere.center ) - sphere.radius;
+
+	},
+
+	projectPoint: function ( point, optionalTarget ) {
+
+		return this.orthoPoint( point, optionalTarget ).sub( point ).negate();
+
+	},
+
+	orthoPoint: function ( point, optionalTarget ) {
+
+		var perpendicularMagnitude = this.distanceToPoint( point );
+
+		var result = optionalTarget || new THREE.Vector3();
+		return result.copy( this.normal ).multiplyScalar( perpendicularMagnitude );
+
+	},
+
+	isIntersectionLine: function ( line ) {
+
+		// Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.
+
+		var startSign = this.distanceToPoint( line.start );
+		var endSign = this.distanceToPoint( line.end );
+
+		return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 );
+
+	},
+
+	intersectLine: function() {
+
+		var v1 = new THREE.Vector3();
+
+		return function ( line, optionalTarget ) {
+
+			var result = optionalTarget || new THREE.Vector3();
+
+			var direction = line.delta( v1 );
+
+			var denominator = this.normal.dot( direction );
+
+			if ( denominator == 0 ) {
+
+				// line is coplanar, return origin
+				if( this.distanceToPoint( line.start ) == 0 ) {
+
+					return result.copy( line.start );
+
+				}
+
+				// Unsure if this is the correct method to handle this case.
+				return undefined;
+
+			}
+
+			var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator;
+
+			if( t < 0 || t > 1 ) {
+
+				return undefined;
+
+			}
+
+			return result.copy( direction ).multiplyScalar( t ).add( line.start );
+
+		};
+
+	}(),
+
+
+	coplanarPoint: function ( optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+		return result.copy( this.normal ).multiplyScalar( - this.constant );
+
+	},
+
+	applyMatrix4: function() {
+
+		var v1 = new THREE.Vector3();
+		var v2 = new THREE.Vector3();
+
+		return function ( matrix, optionalNormalMatrix ) {
+
+			// compute new normal based on theory here:
+			// http://www.songho.ca/opengl/gl_normaltransform.html
+			optionalNormalMatrix = optionalNormalMatrix || new THREE.Matrix3().getNormalMatrix( matrix );
+			var newNormal = v1.copy( this.normal ).applyMatrix3( optionalNormalMatrix );
+
+			var newCoplanarPoint = this.coplanarPoint( v2 );
+			newCoplanarPoint.applyMatrix4( matrix );
+
+			this.setFromNormalAndCoplanarPoint( newNormal, newCoplanarPoint );
+
+			return this;
+
+		};
+
+	}(),
+
+	translate: function ( offset ) {
+
+		this.constant = this.constant - offset.dot( this.normal );
+
+		return this;
+
+	},
+
+	equals: function ( plane ) {
+
+		return plane.normal.equals( this.normal ) && ( plane.constant == this.constant );
+
+	},
+
+	clone: function () {
+
+		return new THREE.Plane().copy( this );
+
+	}
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Math = {
+
+	// Clamp value to range <a, b>
+
+	clamp: function ( x, a, b ) {
+
+		return ( x < a ) ? a : ( ( x > b ) ? b : x );
+
+	},
+
+	// Clamp value to range <a, inf)
+
+	clampBottom: function ( x, a ) {
+
+		return x < a ? a : x;
+
+	},
+
+	// Linear mapping from range <a1, a2> to range <b1, b2>
+
+	mapLinear: function ( x, a1, a2, b1, b2 ) {
+
+		return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+
+	},
+
+	// http://en.wikipedia.org/wiki/Smoothstep
+
+	smoothstep: function ( x, min, max ) {
+
+		if ( x <= min ) return 0;
+		if ( x >= max ) return 1;
+
+		x = ( x - min )/( max - min );
+
+		return x*x*(3 - 2*x);
+
+	},
+
+	smootherstep: function ( x, min, max ) {
+
+		if ( x <= min ) return 0;
+		if ( x >= max ) return 1;
+
+		x = ( x - min )/( max - min );
+
+		return x*x*x*(x*(x*6 - 15) + 10);
+
+	},
+
+	// Random float from <0, 1> with 16 bits of randomness
+	// (standard Math.random() creates repetitive patterns when applied over larger space)
+
+	random16: function () {
+
+		return ( 65280 * Math.random() + 255 * Math.random() ) / 65535;
+
+	},
+
+	// Random integer from <low, high> interval
+
+	randInt: function ( low, high ) {
+
+		return low + Math.floor( Math.random() * ( high - low + 1 ) );
+
+	},
+
+	// Random float from <low, high> interval
+
+	randFloat: function ( low, high ) {
+
+		return low + Math.random() * ( high - low );
+
+	},
+
+	// Random float from <-range/2, range/2> interval
+
+	randFloatSpread: function ( range ) {
+
+		return range * ( 0.5 - Math.random() );
+
+	},
+
+	sign: function ( x ) {
+
+		return ( x < 0 ) ? -1 : ( ( x > 0 ) ? 1 : 0 );
+
+	},
+
+	degToRad: function() {
+
+		var degreeToRadiansFactor = Math.PI / 180;
+
+		return function ( degrees ) {
+
+			return degrees * degreeToRadiansFactor;
+
+		};
+
+	}(),
+
+	radToDeg: function() {
+
+		var radianToDegreesFactor = 180 / Math.PI;
+
+		return function ( radians ) {
+
+			return radians * radianToDegreesFactor;
+
+		};
+
+	}()
+
+};
+/**
+ * Spline from Tween.js, slightly optimized (and trashed)
+ * http://sole.github.com/tween.js/examples/05_spline.html
+ *
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Spline = function ( points ) {
+
+	this.points = points;
+
+	var c = [], v3 = { x: 0, y: 0, z: 0 },
+	point, intPoint, weight, w2, w3,
+	pa, pb, pc, pd;
+
+	this.initFromArray = function( a ) {
+
+		this.points = [];
+
+		for ( var i = 0; i < a.length; i++ ) {
+
+			this.points[ i ] = { x: a[ i ][ 0 ], y: a[ i ][ 1 ], z: a[ i ][ 2 ] };
+
+		}
+
+	};
+
+	this.getPoint = function ( k ) {
+
+		point = ( this.points.length - 1 ) * k;
+		intPoint = Math.floor( point );
+		weight = point - intPoint;
+
+		c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1;
+		c[ 1 ] = intPoint;
+		c[ 2 ] = intPoint  > this.points.length - 2 ? this.points.length - 1 : intPoint + 1;
+		c[ 3 ] = intPoint  > this.points.length - 3 ? this.points.length - 1 : intPoint + 2;
+
+		pa = this.points[ c[ 0 ] ];
+		pb = this.points[ c[ 1 ] ];
+		pc = this.points[ c[ 2 ] ];
+		pd = this.points[ c[ 3 ] ];
+
+		w2 = weight * weight;
+		w3 = weight * w2;
+
+		v3.x = interpolate( pa.x, pb.x, pc.x, pd.x, weight, w2, w3 );
+		v3.y = interpolate( pa.y, pb.y, pc.y, pd.y, weight, w2, w3 );
+		v3.z = interpolate( pa.z, pb.z, pc.z, pd.z, weight, w2, w3 );
+
+		return v3;
+
+	};
+
+	this.getControlPointsArray = function () {
+
+		var i, p, l = this.points.length,
+			coords = [];
+
+		for ( i = 0; i < l; i ++ ) {
+
+			p = this.points[ i ];
+			coords[ i ] = [ p.x, p.y, p.z ];
+
+		}
+
+		return coords;
+
+	};
+
+	// approximate length by summing linear segments
+
+	this.getLength = function ( nSubDivisions ) {
+
+		var i, index, nSamples, position,
+			point = 0, intPoint = 0, oldIntPoint = 0,
+			oldPosition = new THREE.Vector3(),
+			tmpVec = new THREE.Vector3(),
+			chunkLengths = [],
+			totalLength = 0;
+
+		// first point has 0 length
+
+		chunkLengths[ 0 ] = 0;
+
+		if ( !nSubDivisions ) nSubDivisions = 100;
+
+		nSamples = this.points.length * nSubDivisions;
+
+		oldPosition.copy( this.points[ 0 ] );
+
+		for ( i = 1; i < nSamples; i ++ ) {
+
+			index = i / nSamples;
+
+			position = this.getPoint( index );
+			tmpVec.copy( position );
+
+			totalLength += tmpVec.distanceTo( oldPosition );
+
+			oldPosition.copy( position );
+
+			point = ( this.points.length - 1 ) * index;
+			intPoint = Math.floor( point );
+
+			if ( intPoint != oldIntPoint ) {
+
+				chunkLengths[ intPoint ] = totalLength;
+				oldIntPoint = intPoint;
+
+			}
+
+		}
+
+		// last point ends with total length
+
+		chunkLengths[ chunkLengths.length ] = totalLength;
+
+		return { chunks: chunkLengths, total: totalLength };
+
+	};
+
+	this.reparametrizeByArcLength = function ( samplingCoef ) {
+
+		var i, j,
+			index, indexCurrent, indexNext,
+			linearDistance, realDistance,
+			sampling, position,
+			newpoints = [],
+			tmpVec = new THREE.Vector3(),
+			sl = this.getLength();
+
+		newpoints.push( tmpVec.copy( this.points[ 0 ] ).clone() );
+
+		for ( i = 1; i < this.points.length; i++ ) {
+
+			//tmpVec.copy( this.points[ i - 1 ] );
+			//linearDistance = tmpVec.distanceTo( this.points[ i ] );
+
+			realDistance = sl.chunks[ i ] - sl.chunks[ i - 1 ];
+
+			sampling = Math.ceil( samplingCoef * realDistance / sl.total );
+
+			indexCurrent = ( i - 1 ) / ( this.points.length - 1 );
+			indexNext = i / ( this.points.length - 1 );
+
+			for ( j = 1; j < sampling - 1; j++ ) {
+
+				index = indexCurrent + j * ( 1 / sampling ) * ( indexNext - indexCurrent );
+
+				position = this.getPoint( index );
+				newpoints.push( tmpVec.copy( position ).clone() );
+
+			}
+
+			newpoints.push( tmpVec.copy( this.points[ i ] ).clone() );
+
+		}
+
+		this.points = newpoints;
+
+	};
+
+	// Catmull-Rom
+
+	function interpolate( p0, p1, p2, p3, t, t2, t3 ) {
+
+		var v0 = ( p2 - p0 ) * 0.5,
+			v1 = ( p3 - p1 ) * 0.5;
+
+		return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+	};
+
+};
+/**
+ * @author bhouston / http://exocortex.com
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Triangle = function ( a, b, c ) {
+
+	this.a = ( a !== undefined ) ? a : new THREE.Vector3();
+	this.b = ( b !== undefined ) ? b : new THREE.Vector3();
+	this.c = ( c !== undefined ) ? c : new THREE.Vector3();
+
+};
+
+THREE.Triangle.normal = function() {
+
+	var v0 = new THREE.Vector3();
+
+	return function ( a, b, c, optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+
+		result.subVectors( c, b );
+		v0.subVectors( a, b );
+		result.cross( v0 );
+
+		var resultLengthSq = result.lengthSq();
+		if( resultLengthSq > 0 ) {
+
+			return result.multiplyScalar( 1 / Math.sqrt( resultLengthSq ) );
+
+		}
+
+		return result.set( 0, 0, 0 );
+
+	};
+
+}();
+
+// static/instance method to calculate barycoordinates
+// based on: http://www.blackpawn.com/texts/pointinpoly/default.html
+THREE.Triangle.barycoordFromPoint = function() {
+
+	var v0 = new THREE.Vector3();
+	var v1 = new THREE.Vector3();
+	var v2 = new THREE.Vector3();
+
+	return function ( point, a, b, c, optionalTarget ) {
+
+		v0.subVectors( c, a );
+		v1.subVectors( b, a );
+		v2.subVectors( point, a );
+
+		var dot00 = v0.dot( v0 );
+		var dot01 = v0.dot( v1 );
+		var dot02 = v0.dot( v2 );
+		var dot11 = v1.dot( v1 );
+		var dot12 = v1.dot( v2 );
+
+		var denom = ( dot00 * dot11 - dot01 * dot01 );
+
+		var result = optionalTarget || new THREE.Vector3();
+
+		// colinear or singular triangle
+		if( denom == 0 ) {
+			// arbitrary location outside of triangle?
+			// not sure if this is the best idea, maybe should be returning undefined
+			return result.set( -2, -1, -1 );
+		}
+
+		var invDenom = 1 / denom;
+		var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
+		var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
+
+		// barycoordinates must always sum to 1
+		return result.set( 1 - u - v, v, u );
+
+	};
+
+}();
+
+THREE.Triangle.containsPoint = function() {
+
+	var v1 = new THREE.Vector3();
+
+	return function ( point, a, b, c ) {
+
+		var result = THREE.Triangle.barycoordFromPoint( point, a, b, c, v1 );
+
+		return ( result.x >= 0 ) && ( result.y >= 0 ) && ( ( result.x + result.y ) <= 1 );
+
+	};
+
+}();
+
+THREE.Triangle.prototype = {
+
+	constructor: THREE.Triangle,
+
+	set: function ( a, b, c ) {
+
+		this.a.copy( a );
+		this.b.copy( b );
+		this.c.copy( c );
+
+		return this;
+
+	},
+
+	setFromPointsAndIndices: function ( points, i0, i1, i2 ) {
+
+		this.a.copy( points[i0] );
+		this.b.copy( points[i1] );
+		this.c.copy( points[i2] );
+
+		return this;
+
+	},
+
+	copy: function ( triangle ) {
+
+		this.a.copy( triangle.a );
+		this.b.copy( triangle.b );
+		this.c.copy( triangle.c );
+
+		return this;
+
+	},
+
+	area: function() {
+
+		var v0 = new THREE.Vector3();
+		var v1 = new THREE.Vector3();
+
+		return function () {
+
+			v0.subVectors( this.c, this.b );
+			v1.subVectors( this.a, this.b );
+
+			return v0.cross( v1 ).length() * 0.5;
+
+		};
+
+	}(),
+
+	midpoint: function ( optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Vector3();
+		return result.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 );
+
+	},
+
+	normal: function ( optionalTarget ) {
+
+		return THREE.Triangle.normal( this.a, this.b, this.c, optionalTarget );
+
+	},
+
+	plane: function ( optionalTarget ) {
+
+		var result = optionalTarget || new THREE.Plane();
+
+		return result.setFromCoplanarPoints( this.a, this.b, this.c );
+
+	},
+
+	barycoordFromPoint: function ( point, optionalTarget ) {
+
+		return THREE.Triangle.barycoordFromPoint( point, this.a, this.b, this.c, optionalTarget );
+
+	},
+
+	containsPoint: function ( point ) {
+
+		return THREE.Triangle.containsPoint( point, this.a, this.b, this.c );
+
+	},
+
+	equals: function ( triangle ) {
+
+		return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c );
+
+	},
+
+	clone: function () {
+
+		return new THREE.Triangle().copy( this );
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Vertex = function ( v ) {
+
+	console.warn( 'THREE.Vertex has been DEPRECATED. Use THREE.Vector3 instead.')
+	return v;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.UV = function ( u, v ) {
+
+	console.warn( 'THREE.UV has been DEPRECATED. Use THREE.Vector2 instead.')
+	return new THREE.Vector2( u, v );
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Clock = function ( autoStart ) {
+
+	this.autoStart = ( autoStart !== undefined ) ? autoStart : true;
+
+	this.startTime = 0;
+	this.oldTime = 0;
+	this.elapsedTime = 0;
+
+	this.running = false;
+
+};
+
+THREE.Clock.prototype = {
+
+	constructor: THREE.Clock,
+
+	start: function () {
+
+		this.startTime = window.performance !== undefined && window.performance.now !== undefined
+					? window.performance.now()
+					: Date.now();
+
+		this.oldTime = this.startTime;
+		this.running = true;
+	},
+
+	stop: function () {
+
+		this.getElapsedTime();
+		this.running = false;
+
+	},
+
+	getElapsedTime: function () {
+
+		this.getDelta();
+		return this.elapsedTime;
+
+	},
+
+	getDelta: function () {
+
+		var diff = 0;
+
+		if ( this.autoStart && ! this.running ) {
+
+			this.start();
+
+		}
+
+		if ( this.running ) {
+
+			var newTime = window.performance !== undefined && window.performance.now !== undefined
+					? window.performance.now()
+					: Date.now();
+
+			diff = 0.001 * ( newTime - this.oldTime );
+			this.oldTime = newTime;
+
+			this.elapsedTime += diff;
+
+		}
+
+		return diff;
+
+	}
+
+};
+/**
+ * https://github.com/mrdoob/eventdispatcher.js/
+ */
+
+THREE.EventDispatcher = function () {}
+
+THREE.EventDispatcher.prototype = {
+
+	constructor: THREE.EventDispatcher,
+
+	addEventListener: function ( type, listener ) {
+
+		if ( this._listeners === undefined ) this._listeners = {};
+
+		var listeners = this._listeners;
+
+		if ( listeners[ type ] === undefined ) {
+
+			listeners[ type ] = [];
+
+		}
+
+		if ( listeners[ type ].indexOf( listener ) === - 1 ) {
+
+			listeners[ type ].push( listener );
+
+		}
+
+	},
+
+	hasEventListener: function ( type, listener ) {
+
+		if ( this._listeners === undefined ) return false;
+
+		var listeners = this._listeners;
+
+		if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) {
+
+			return true;
+
+		}
+
+		return false;
+
+	},
+
+	removeEventListener: function ( type, listener ) {
+
+		if ( this._listeners === undefined ) return;
+
+		var listeners = this._listeners;
+		var index = listeners[ type ].indexOf( listener );
+
+		if ( index !== - 1 ) {
+
+			listeners[ type ].splice( index, 1 );
+
+		}
+
+	},
+
+	dispatchEvent: function ( event ) {
+
+		if ( this._listeners === undefined ) return;
+
+		var listeners = this._listeners;
+		var listenerArray = listeners[ event.type ];
+
+		if ( listenerArray !== undefined ) {
+
+			event.target = this;
+
+			for ( var i = 0, l = listenerArray.length; i < l; i ++ ) {
+
+				listenerArray[ i ].call( this, event );
+
+			}
+
+		}
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author bhouston / http://exocortex.com/
+ */
+
+( function ( THREE ) {
+
+	THREE.Raycaster = function ( origin, direction, near, far ) {
+
+		this.ray = new THREE.Ray( origin, direction );
+
+		// normalized ray.direction required for accurate distance calculations
+		if ( this.ray.direction.lengthSq() > 0 ) {
+
+			this.ray.direction.normalize();
+
+		}
+
+		this.near = near || 0;
+		this.far = far || Infinity;
+
+	};
+
+	var sphere = new THREE.Sphere();
+	var localRay = new THREE.Ray();
+	var facePlane = new THREE.Plane();
+	var intersectPoint = new THREE.Vector3();
+	var matrixPosition = new THREE.Vector3();
+
+	var inverseMatrix = new THREE.Matrix4();
+
+	var descSort = function ( a, b ) {
+
+		return a.distance - b.distance;
+
+	};
+
+	var intersectObject = function ( object, raycaster, intersects ) {
+
+		if ( object instanceof THREE.Particle ) {
+
+			matrixPosition.getPositionFromMatrix( object.matrixWorld );
+			var distance = raycaster.ray.distanceToPoint( matrixPosition );
+
+			if ( distance > object.scale.x ) {
+
+				return intersects;
+
+			}
+
+			intersects.push( {
+
+				distance: distance,
+				point: object.position,
+				face: null,
+				object: object
+
+			} );
+
+		} else if ( object instanceof THREE.LOD ) {
+
+			matrixPosition.getPositionFromMatrix( object.matrixWorld );
+			var distance = raycaster.ray.origin.distanceTo( matrixPosition );
+
+			intersectObject( object.getObjectForDistance( distance ), raycaster, intersects );
+
+		} else if ( object instanceof THREE.Mesh ) {
+
+			// Checking boundingSphere distance to ray
+			matrixPosition.getPositionFromMatrix( object.matrixWorld );
+			sphere.set(
+				matrixPosition,
+				object.geometry.boundingSphere.radius * object.matrixWorld.getMaxScaleOnAxis() );
+
+			if ( ! raycaster.ray.isIntersectionSphere( sphere ) ) {
+
+				return intersects;
+
+			}
+
+			// Checking faces
+
+			var geometry = object.geometry;
+			var vertices = geometry.vertices;
+
+			var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
+			var objectMaterials = isFaceMaterial === true ? object.material.materials : null;
+
+			var side = object.material.side;
+
+			var a, b, c, d;
+			var precision = raycaster.precision;
+
+			inverseMatrix.getInverse( object.matrixWorld );
+
+			localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );
+
+			for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
+
+				var face = geometry.faces[ f ];
+
+				var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : object.material;
+
+				if ( material === undefined ) continue;
+
+				facePlane.setFromNormalAndCoplanarPoint( face.normal, vertices[face.a] );
+
+				var planeDistance = localRay.distanceToPlane( facePlane );
+
+				// bail if raycaster and plane are parallel
+				if ( Math.abs( planeDistance ) < precision ) continue;
+
+				// if negative distance, then plane is behind raycaster
+				if ( planeDistance < 0 ) continue;
+
+				// check if we hit the wrong side of a single sided face
+				side = material.side;
+				if ( side !== THREE.DoubleSide ) {
+
+					var planeSign = localRay.direction.dot( facePlane.normal );
+
+					if ( ! ( side === THREE.FrontSide ? planeSign < 0 : planeSign > 0 ) ) continue;
+
+				}
+
+				// this can be done using the planeDistance from localRay because localRay wasn't normalized, but ray was
+				if ( planeDistance < raycaster.near || planeDistance > raycaster.far ) continue;
+
+				intersectPoint = localRay.at( planeDistance, intersectPoint ); // passing in intersectPoint avoids a copy
+
+				if ( face instanceof THREE.Face3 ) {
+
+					a = vertices[ face.a ];
+					b = vertices[ face.b ];
+					c = vertices[ face.c ];
+
+					if ( ! THREE.Triangle.containsPoint( intersectPoint, a, b, c ) ) continue;
+
+				} else if ( face instanceof THREE.Face4 ) {
+
+					a = vertices[ face.a ];
+					b = vertices[ face.b ];
+					c = vertices[ face.c ];
+					d = vertices[ face.d ];
+
+					if ( ( ! THREE.Triangle.containsPoint( intersectPoint, a, b, d ) ) &&
+						 ( ! THREE.Triangle.containsPoint( intersectPoint, b, c, d ) ) ) continue;
+
+				} else {
+
+					// This is added because if we call out of this if/else group when none of the cases
+					//    match it will add a point to the intersection list erroneously.
+					throw Error( "face type not supported" );
+
+				}
+
+				intersects.push( {
+
+					distance: planeDistance,	// this works because the original ray was normalized, and the transformed localRay wasn't
+					point: raycaster.ray.at( planeDistance ),
+					face: face,
+					faceIndex: f,
+					object: object
+
+				} );
+
+			}
+
+		}
+
+	};
+
+	var intersectDescendants = function ( object, raycaster, intersects ) {
+
+		var descendants = object.getDescendants();
+
+		for ( var i = 0, l = descendants.length; i < l; i ++ ) {
+
+			intersectObject( descendants[ i ], raycaster, intersects );
+
+		}
+	};
+
+	//
+
+	THREE.Raycaster.prototype.precision = 0.0001;
+
+	THREE.Raycaster.prototype.set = function ( origin, direction ) {
+
+		this.ray.set( origin, direction );
+
+		// normalized ray.direction required for accurate distance calculations
+		if ( this.ray.direction.length() > 0 ) {
+
+			this.ray.direction.normalize();
+
+		}
+
+	};
+
+	THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) {
+
+		var intersects = [];
+
+		if ( recursive === true ) {
+
+			intersectDescendants( object, this, intersects );
+
+		}
+
+		intersectObject( object, this, intersects );
+
+		intersects.sort( descSort );
+
+		return intersects;
+
+	};
+
+	THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) {
+
+		var intersects = [];
+
+		for ( var i = 0, l = objects.length; i < l; i ++ ) {
+
+			intersectObject( objects[ i ], this, intersects );
+
+			if ( recursive === true ) {
+
+				intersectDescendants( objects[ i ], this, intersects );
+
+			}
+		}
+
+		intersects.sort( descSort );
+
+		return intersects;
+
+	};
+
+}( THREE ) );
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Object3D = function () {
+
+	this.id = THREE.Object3DIdCount ++;
+
+	this.name = '';
+
+	this.parent = undefined;
+	this.children = [];
+
+	this.up = new THREE.Vector3( 0, 1, 0 );
+
+	this.position = new THREE.Vector3();
+	this.rotation = new THREE.Vector3();
+	this.eulerOrder = THREE.Object3D.defaultEulerOrder;
+	this.scale = new THREE.Vector3( 1, 1, 1 );
+
+	this.renderDepth = null;
+
+	this.rotationAutoUpdate = true;
+
+	this.matrix = new THREE.Matrix4();
+	this.matrixWorld = new THREE.Matrix4();
+
+	this.matrixAutoUpdate = true;
+	this.matrixWorldNeedsUpdate = true;
+
+	this.quaternion = new THREE.Quaternion();
+	this.useQuaternion = false;
+
+	this.visible = true;
+
+	this.castShadow = false;
+	this.receiveShadow = false;
+
+	this.frustumCulled = true;
+
+	this.userData = {};
+
+};
+
+
+THREE.Object3D.prototype = {
+
+	constructor: THREE.Object3D,
+
+	applyMatrix: function () {
+
+		var m1 = new THREE.Matrix4();
+
+		return function ( matrix ) {
+
+			this.matrix.multiplyMatrices( matrix, this.matrix );
+
+			this.position.getPositionFromMatrix( this.matrix );
+
+			this.scale.getScaleFromMatrix( this.matrix );
+
+			m1.extractRotation( this.matrix );
+
+			if ( this.useQuaternion === true )  {
+
+				this.quaternion.setFromRotationMatrix( m1 );
+
+			} else {
+
+				this.rotation.setEulerFromRotationMatrix( m1, this.eulerOrder );
+
+			}
+
+		}
+
+	}(),
+
+	rotateOnAxis: function() {
+
+		// rotate object on axis in object space
+		// axis is assumed to be normalized
+
+		var q1 = new THREE.Quaternion();
+		var q2 = new THREE.Quaternion();
+
+		return function ( axis, angle ) {
+
+			q1.setFromAxisAngle( axis, angle );
+
+			if ( this.useQuaternion === true ) {
+
+				this.quaternion.multiply( q1 );
+
+			} else {
+
+				q2.setFromEuler( this.rotation, this.eulerOrder );
+				q2.multiply( q1 );
+
+				this.rotation.setEulerFromQuaternion( q2, this.eulerOrder );
+
+			}
+
+			return this;
+
+		}
+
+	}(),
+
+	translateOnAxis: function () {
+
+		// translate object by distance along axis in object space
+		// axis is assumed to be normalized
+
+		var v1 = new THREE.Vector3();
+
+		return function ( axis, distance ) {
+
+			v1.copy( axis );
+
+			if ( this.useQuaternion === true ) {
+
+				v1.applyQuaternion( this.quaternion );
+
+			} else {
+
+				v1.applyEuler( this.rotation, this.eulerOrder );
+
+			}
+
+			this.position.add( v1.multiplyScalar( distance ) );
+
+			return this;
+
+		}
+
+	}(),
+
+	translate: function ( distance, axis ) {
+
+		console.warn( 'DEPRECATED: Object3D\'s .translate() has been removed. Use .translateOnAxis( axis, distance ) instead. Note args have been changed.' );
+		return this.translateOnAxis( axis, distance );
+
+	},
+
+	translateX: function () {
+
+		var v1 = new THREE.Vector3( 1, 0, 0 );
+
+		return function ( distance ) {
+
+			return this.translateOnAxis( v1, distance );
+
+		};
+
+	}(),
+
+	translateY: function () {
+
+		var v1 = new THREE.Vector3( 0, 1, 0 );
+
+		return function ( distance ) {
+
+			return this.translateOnAxis( v1, distance );
+
+		};
+
+	}(),
+
+	translateZ: function () {
+
+		var v1 = new THREE.Vector3( 0, 0, 1 );
+
+		return function ( distance ) {
+
+			return this.translateOnAxis( v1, distance );
+
+		};
+
+	}(),
+
+	localToWorld: function ( vector ) {
+
+		return vector.applyMatrix4( this.matrixWorld );
+
+	},
+
+	worldToLocal: function () {
+
+		var m1 = new THREE.Matrix4();
+
+		return function ( vector ) {
+
+			return vector.applyMatrix4( m1.getInverse( this.matrixWorld ) );
+
+		};
+
+	}(),
+
+	lookAt: function () {
+
+		// This routine does not support objects with rotated and/or translated parent(s)
+
+		var m1 = new THREE.Matrix4();
+
+		return function ( vector ) {
+
+			m1.lookAt( vector, this.position, this.up );
+
+			if ( this.useQuaternion === true )  {
+
+				this.quaternion.setFromRotationMatrix( m1 );
+
+			} else {
+
+				this.rotation.setEulerFromRotationMatrix( m1, this.eulerOrder );
+
+			}
+
+		};
+
+	}(),
+
+	add: function ( object ) {
+
+		if ( object === this ) {
+
+			console.warn( 'THREE.Object3D.add: An object can\'t be added as a child of itself.' );
+			return;
+
+		}
+
+		if ( object instanceof THREE.Object3D ) {
+
+			if ( object.parent !== undefined ) {
+
+				object.parent.remove( object );
+
+			}
+
+			object.parent = this;
+			this.children.push( object );
+
+			// add to scene
+
+			var scene = this;
+
+			while ( scene.parent !== undefined ) {
+
+				scene = scene.parent;
+
+			}
+
+			if ( scene !== undefined && scene instanceof THREE.Scene )  {
+
+				scene.__addObject( object );
+
+			}
+
+		}
+
+	},
+
+	remove: function ( object ) {
+
+		var index = this.children.indexOf( object );
+
+		if ( index !== - 1 ) {
+
+			object.parent = undefined;
+			this.children.splice( index, 1 );
+
+			// remove from scene
+
+			var scene = this;
+
+			while ( scene.parent !== undefined ) {
+
+				scene = scene.parent;
+
+			}
+
+			if ( scene !== undefined && scene instanceof THREE.Scene ) {
+
+				scene.__removeObject( object );
+
+			}
+
+		}
+
+	},
+
+	traverse: function ( callback ) {
+
+		callback( this );
+
+		for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+			this.children[ i ].traverse( callback );
+
+		}
+
+	},
+
+	getObjectById: function ( id, recursive ) {
+
+		for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+			var child = this.children[ i ];
+
+			if ( child.id === id ) {
+
+				return child;
+
+			}
+
+			if ( recursive === true ) {
+
+				child = child.getObjectById( id, recursive );
+
+				if ( child !== undefined ) {
+
+					return child;
+
+				}
+
+			}
+
+		}
+
+		return undefined;
+
+	},
+
+	getObjectByName: function ( name, recursive ) {
+
+		for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+			var child = this.children[ i ];
+
+			if ( child.name === name ) {
+
+				return child;
+
+			}
+
+			if ( recursive === true ) {
+
+				child = child.getObjectByName( name, recursive );
+
+				if ( child !== undefined ) {
+
+					return child;
+
+				}
+
+			}
+
+		}
+
+		return undefined;
+
+	},
+
+	getChildByName: function ( name, recursive ) {
+
+		console.warn( 'DEPRECATED: Object3D\'s .getChildByName() has been renamed to .getObjectByName().' );
+		return this.getObjectByName( name, recursive );
+
+	},
+
+	getDescendants: function ( array ) {
+
+		if ( array === undefined ) array = [];
+
+		Array.prototype.push.apply( array, this.children );
+
+		for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+			this.children[ i ].getDescendants( array );
+
+		}
+
+		return array;
+
+	},
+
+	updateMatrix: function () {
+
+		// if we are not using a quaternion directly, convert Euler rotation to this.quaternion.
+
+		if ( this.useQuaternion === false )  {
+
+			this.matrix.makeFromPositionEulerScale( this.position, this.rotation, this.eulerOrder, this.scale );
+
+		} else {
+
+			this.matrix.makeFromPositionQuaternionScale( this.position, this.quaternion, this.scale );
+
+		}
+
+		this.matrixWorldNeedsUpdate = true;
+
+	},
+
+	updateMatrixWorld: function ( force ) {
+
+		if ( this.matrixAutoUpdate === true ) this.updateMatrix();
+
+		if ( this.matrixWorldNeedsUpdate === true || force === true ) {
+
+			if ( this.parent === undefined ) {
+
+				this.matrixWorld.copy( this.matrix );
+
+			} else {
+
+				this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+			}
+
+			this.matrixWorldNeedsUpdate = false;
+
+			force = true;
+
+		}
+
+		// update children
+
+		for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+			this.children[ i ].updateMatrixWorld( force );
+
+		}
+
+	},
+
+	clone: function ( object ) {
+
+		if ( object === undefined ) object = new THREE.Object3D();
+
+		object.name = this.name;
+
+		object.up.copy( this.up );
+
+		object.position.copy( this.position );
+		if ( object.rotation instanceof THREE.Vector3 ) object.rotation.copy( this.rotation ); // because of Sprite madness
+		object.eulerOrder = this.eulerOrder;
+		object.scale.copy( this.scale );
+
+		object.renderDepth = this.renderDepth;
+
+		object.rotationAutoUpdate = this.rotationAutoUpdate;
+
+		object.matrix.copy( this.matrix );
+		object.matrixWorld.copy( this.matrixWorld );
+
+		object.matrixAutoUpdate = this.matrixAutoUpdate;
+		object.matrixWorldNeedsUpdate = this.matrixWorldNeedsUpdate;
+
+		object.quaternion.copy( this.quaternion );
+		object.useQuaternion = this.useQuaternion;
+
+		object.visible = this.visible;
+
+		object.castShadow = this.castShadow;
+		object.receiveShadow = this.receiveShadow;
+
+		object.frustumCulled = this.frustumCulled;
+
+		object.userData = JSON.parse( JSON.stringify( this.userData ) );
+
+		for ( var i = 0; i < this.children.length; i ++ ) {
+
+			var child = this.children[ i ];
+			object.add( child.clone() );
+
+		}
+
+		return object;
+
+	}
+
+};
+
+THREE.Object3D.defaultEulerOrder = 'XYZ',
+
+THREE.Object3DIdCount = 0;
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author julianwa / https://github.com/julianwa
+ */
+
+THREE.Projector = function () {
+
+	var _object, _objectCount, _objectPool = [], _objectPoolLength = 0,
+	_vertex, _vertexCount, _vertexPool = [], _vertexPoolLength = 0,
+	_face, _face3Count, _face3Pool = [], _face3PoolLength = 0,
+	_face4Count, _face4Pool = [], _face4PoolLength = 0,
+	_line, _lineCount, _linePool = [], _linePoolLength = 0,
+	_particle, _particleCount, _particlePool = [], _particlePoolLength = 0,
+
+	_renderData = { objects: [], sprites: [], lights: [], elements: [] },
+
+	_vector3 = new THREE.Vector3(),
+	_vector4 = new THREE.Vector4(),
+
+	_clipBox = new THREE.Box3( new THREE.Vector3( -1, -1, -1 ), new THREE.Vector3( 1, 1, 1 ) ),
+	_boundingBox = new THREE.Box3(),
+	_points3 = new Array( 3 ),
+	_points4 = new Array( 4 ),
+
+	_viewMatrix = new THREE.Matrix4(),
+	_viewProjectionMatrix = new THREE.Matrix4(),
+
+	_modelMatrix,
+	_modelViewProjectionMatrix = new THREE.Matrix4(),
+
+	_normalMatrix = new THREE.Matrix3(),
+	_normalViewMatrix = new THREE.Matrix3(),
+
+	_centroid = new THREE.Vector3(),
+
+	_frustum = new THREE.Frustum(),
+
+	_clippedVertex1PositionScreen = new THREE.Vector4(),
+	_clippedVertex2PositionScreen = new THREE.Vector4();
+
+	this.projectVector = function ( vector, camera ) {
+
+		camera.matrixWorldInverse.getInverse( camera.matrixWorld );
+
+		_viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+
+		return vector.applyProjection( _viewProjectionMatrix );
+
+	};
+
+	this.unprojectVector = function ( vector, camera ) {
+
+		camera.projectionMatrixInverse.getInverse( camera.projectionMatrix );
+
+		_viewProjectionMatrix.multiplyMatrices( camera.matrixWorld, camera.projectionMatrixInverse );
+
+		return vector.applyProjection( _viewProjectionMatrix );
+
+	};
+
+	this.pickingRay = function ( vector, camera ) {
+
+		// set two vectors with opposing z values
+		vector.z = -1.0;
+		var end = new THREE.Vector3( vector.x, vector.y, 1.0 );
+
+		this.unprojectVector( vector, camera );
+		this.unprojectVector( end, camera );
+
+		// find direction from vector to end
+		end.sub( vector ).normalize();
+
+		return new THREE.Raycaster( vector, end );
+
+	};
+
+	var projectGraph = function ( root, sortObjects ) {
+
+		_objectCount = 0;
+
+		_renderData.objects.length = 0;
+		_renderData.sprites.length = 0;
+		_renderData.lights.length = 0;
+
+		var projectObject = function ( parent ) {
+
+			for ( var c = 0, cl = parent.children.length; c < cl; c ++ ) {
+
+				var object = parent.children[ c ];
+
+				if ( object.visible === false ) continue;
+
+				if ( object instanceof THREE.Light ) {
+
+					_renderData.lights.push( object );
+
+				} else if ( object instanceof THREE.Mesh || object instanceof THREE.Line ) {
+
+					if ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) {
+
+						_object = getNextObjectInPool();
+						_object.object = object;
+
+						if ( object.renderDepth !== null ) {
+
+							_object.z = object.renderDepth;
+
+						} else {
+
+							_vector3.getPositionFromMatrix( object.matrixWorld );
+							_vector3.applyProjection( _viewProjectionMatrix );
+							_object.z = _vector3.z;
+
+						}
+
+						_renderData.objects.push( _object );
+
+					}
+
+				} else if ( object instanceof THREE.Sprite || object instanceof THREE.Particle ) {
+
+					_object = getNextObjectInPool();
+					_object.object = object;
+
+					// TODO: Find an elegant and performant solution and remove this dupe code.
+
+					if ( object.renderDepth !== null ) {
+
+						_object.z = object.renderDepth;
+
+					} else {
+
+						_vector3.getPositionFromMatrix( object.matrixWorld );
+						_vector3.applyProjection( _viewProjectionMatrix );
+						_object.z = _vector3.z;
+
+					}
+
+					_renderData.sprites.push( _object );
+
+				} else {
+
+					_object = getNextObjectInPool();
+					_object.object = object;
+
+					if ( object.renderDepth !== null ) {
+
+						_object.z = object.renderDepth;
+
+					} else {
+
+						_vector3.getPositionFromMatrix( object.matrixWorld );
+						_vector3.applyProjection( _viewProjectionMatrix );
+						_object.z = _vector3.z;
+
+					}
+
+					_renderData.objects.push( _object );
+
+				}
+
+				projectObject( object );
+
+			}
+
+		};
+
+		projectObject( root );
+
+		if ( sortObjects === true ) _renderData.objects.sort( painterSort );
+
+		return _renderData;
+
+	};
+
+	this.projectScene = function ( scene, camera, sortObjects, sortElements ) {
+
+		var visible = false,
+		o, ol, v, vl, f, fl, n, nl, c, cl, u, ul, object,
+		geometry, vertices, faces, face, faceVertexNormals, faceVertexUvs, uvs,
+		v1, v2, v3, v4, isFaceMaterial, objectMaterials;
+
+		_face3Count = 0;
+		_face4Count = 0;
+		_lineCount = 0;
+		_particleCount = 0;
+
+		_renderData.elements.length = 0;
+
+		if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+		if ( camera.parent === undefined ) camera.updateMatrixWorld();
+
+		_viewMatrix.copy( camera.matrixWorldInverse.getInverse( camera.matrixWorld ) );
+		_viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
+
+		_normalViewMatrix.getNormalMatrix( _viewMatrix );
+
+		_frustum.setFromMatrix( _viewProjectionMatrix );
+
+		_renderData = projectGraph( scene, sortObjects );
+
+		for ( o = 0, ol = _renderData.objects.length; o < ol; o ++ ) {
+
+			object = _renderData.objects[ o ].object;
+
+			_modelMatrix = object.matrixWorld;
+
+			_vertexCount = 0;
+
+			if ( object instanceof THREE.Mesh ) {
+
+				geometry = object.geometry;
+
+				vertices = geometry.vertices;
+				faces = geometry.faces;
+				faceVertexUvs = geometry.faceVertexUvs;
+
+				_normalMatrix.getNormalMatrix( _modelMatrix );
+
+				isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
+				objectMaterials = isFaceMaterial === true ? object.material : null;
+
+				for ( v = 0, vl = vertices.length; v < vl; v ++ ) {
+
+					_vertex = getNextVertexInPool();
+
+					_vertex.positionWorld.copy( vertices[ v ] ).applyMatrix4( _modelMatrix );
+					_vertex.positionScreen.copy( _vertex.positionWorld ).applyMatrix4( _viewProjectionMatrix );
+
+					_vertex.positionScreen.x /= _vertex.positionScreen.w;
+					_vertex.positionScreen.y /= _vertex.positionScreen.w;
+					_vertex.positionScreen.z /= _vertex.positionScreen.w;
+
+					_vertex.visible = ! ( _vertex.positionScreen.x < -1 || _vertex.positionScreen.x > 1 ||
+							      _vertex.positionScreen.y < -1 || _vertex.positionScreen.y > 1 ||
+							      _vertex.positionScreen.z < -1 || _vertex.positionScreen.z > 1 );
+
+				}
+
+				for ( f = 0, fl = faces.length; f < fl; f ++ ) {
+
+					face = faces[ f ];
+
+					var material = isFaceMaterial === true
+						? objectMaterials.materials[ face.materialIndex ]
+						: object.material;
+
+					if ( material === undefined ) continue;
+
+					var side = material.side;
+
+					if ( face instanceof THREE.Face3 ) {
+
+						v1 = _vertexPool[ face.a ];
+						v2 = _vertexPool[ face.b ];
+						v3 = _vertexPool[ face.c ];
+
+						_points3[ 0 ] = v1.positionScreen;
+						_points3[ 1 ] = v2.positionScreen;
+						_points3[ 2 ] = v3.positionScreen;
+
+						if ( v1.visible === true || v2.visible === true || v3.visible === true ||
+							_clipBox.isIntersectionBox( _boundingBox.setFromPoints( _points3 ) ) ) {
+
+							visible = ( ( v3.positionScreen.x - v1.positionScreen.x ) * ( v2.positionScreen.y - v1.positionScreen.y ) -
+								( v3.positionScreen.y - v1.positionScreen.y ) * ( v2.positionScreen.x - v1.positionScreen.x ) ) < 0;
+
+							if ( side === THREE.DoubleSide || visible === ( side === THREE.FrontSide ) ) {
+
+								_face = getNextFace3InPool();
+
+								_face.v1.copy( v1 );
+								_face.v2.copy( v2 );
+								_face.v3.copy( v3 );
+
+							} else {
+
+								continue;
+
+							}
+
+						} else {
+
+							continue;
+
+						}
+
+					} else if ( face instanceof THREE.Face4 ) {
+
+						v1 = _vertexPool[ face.a ];
+						v2 = _vertexPool[ face.b ];
+						v3 = _vertexPool[ face.c ];
+						v4 = _vertexPool[ face.d ];
+
+						_points4[ 0 ] = v1.positionScreen;
+						_points4[ 1 ] = v2.positionScreen;
+						_points4[ 2 ] = v3.positionScreen;
+						_points4[ 3 ] = v4.positionScreen;
+
+						if ( v1.visible === true || v2.visible === true || v3.visible === true || v4.visible === true ||
+							_clipBox.isIntersectionBox( _boundingBox.setFromPoints( _points4 ) ) ) {
+
+							visible = ( v4.positionScreen.x - v1.positionScreen.x ) * ( v2.positionScreen.y - v1.positionScreen.y ) -
+								( v4.positionScreen.y - v1.positionScreen.y ) * ( v2.positionScreen.x - v1.positionScreen.x ) < 0 ||
+								( v2.positionScreen.x - v3.positionScreen.x ) * ( v4.positionScreen.y - v3.positionScreen.y ) -
+								( v2.positionScreen.y - v3.positionScreen.y ) * ( v4.positionScreen.x - v3.positionScreen.x ) < 0;
+
+
+							if ( side === THREE.DoubleSide || visible === ( side === THREE.FrontSide ) ) {
+
+								_face = getNextFace4InPool();
+
+								_face.v1.copy( v1 );
+								_face.v2.copy( v2 );
+								_face.v3.copy( v3 );
+								_face.v4.copy( v4 );
+
+							} else {
+
+								continue;
+
+							}
+
+						} else {
+
+							continue;
+
+						}
+
+					}
+
+					_face.normalModel.copy( face.normal );
+
+					if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) {
+
+						_face.normalModel.negate();
+
+					}
+
+					_face.normalModel.applyMatrix3( _normalMatrix ).normalize();
+
+					_face.normalModelView.copy( _face.normalModel ).applyMatrix3( _normalViewMatrix );
+
+					_face.centroidModel.copy( face.centroid ).applyMatrix4( _modelMatrix );
+
+					faceVertexNormals = face.vertexNormals;
+
+					for ( n = 0, nl = faceVertexNormals.length; n < nl; n ++ ) {
+
+						var normalModel = _face.vertexNormalsModel[ n ];
+						normalModel.copy( faceVertexNormals[ n ] );
+
+						if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) {
+
+							normalModel.negate();
+
+						}
+
+						normalModel.applyMatrix3( _normalMatrix ).normalize();
+
+						var normalModelView = _face.vertexNormalsModelView[ n ];
+						normalModelView.copy( normalModel ).applyMatrix3( _normalViewMatrix );
+
+					}
+
+					_face.vertexNormalsLength = faceVertexNormals.length;
+
+					for ( c = 0, cl = faceVertexUvs.length; c < cl; c ++ ) {
+
+						uvs = faceVertexUvs[ c ][ f ];
+
+						if ( uvs === undefined ) continue;
+
+						for ( u = 0, ul = uvs.length; u < ul; u ++ ) {
+
+							_face.uvs[ c ][ u ] = uvs[ u ];
+
+						}
+
+					}
+
+					_face.color = face.color;
+					_face.material = material;
+
+					_centroid.copy( _face.centroidModel ).applyProjection( _viewProjectionMatrix );
+
+					_face.z = _centroid.z;
+
+					_renderData.elements.push( _face );
+
+				}
+
+			} else if ( object instanceof THREE.Line ) {
+
+				_modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix );
+
+				vertices = object.geometry.vertices;
+
+				v1 = getNextVertexInPool();
+				v1.positionScreen.copy( vertices[ 0 ] ).applyMatrix4( _modelViewProjectionMatrix );
+
+				// Handle LineStrip and LinePieces
+				var step = object.type === THREE.LinePieces ? 2 : 1;
+
+				for ( v = 1, vl = vertices.length; v < vl; v ++ ) {
+
+					v1 = getNextVertexInPool();
+					v1.positionScreen.copy( vertices[ v ] ).applyMatrix4( _modelViewProjectionMatrix );
+
+					if ( ( v + 1 ) % step > 0 ) continue;
+
+					v2 = _vertexPool[ _vertexCount - 2 ];
+
+					_clippedVertex1PositionScreen.copy( v1.positionScreen );
+					_clippedVertex2PositionScreen.copy( v2.positionScreen );
+
+					if ( clipLine( _clippedVertex1PositionScreen, _clippedVertex2PositionScreen ) === true ) {
+
+						// Perform the perspective divide
+						_clippedVertex1PositionScreen.multiplyScalar( 1 / _clippedVertex1PositionScreen.w );
+						_clippedVertex2PositionScreen.multiplyScalar( 1 / _clippedVertex2PositionScreen.w );
+
+						_line = getNextLineInPool();
+						_line.v1.positionScreen.copy( _clippedVertex1PositionScreen );
+						_line.v2.positionScreen.copy( _clippedVertex2PositionScreen );
+
+						_line.z = Math.max( _clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z );
+
+						_line.material = object.material;
+
+						if ( object.material.vertexColors === THREE.VertexColors ) {
+
+							_line.vertexColors[ 0 ].copy( object.geometry.colors[ v ] );
+							_line.vertexColors[ 1 ].copy( object.geometry.colors[ v - 1 ] );
+
+						}
+
+						_renderData.elements.push( _line );
+
+					}
+
+				}
+
+			}
+
+		}
+
+		for ( o = 0, ol = _renderData.sprites.length; o < ol; o++ ) {
+
+			object = _renderData.sprites[ o ].object;
+
+			_modelMatrix = object.matrixWorld;
+
+			if ( object instanceof THREE.Particle ) {
+
+				_vector4.set( _modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1 );
+				_vector4.applyMatrix4( _viewProjectionMatrix );
+
+				_vector4.z /= _vector4.w;
+
+				if ( _vector4.z > 0 && _vector4.z < 1 ) {
+
+					_particle = getNextParticleInPool();
+					_particle.object = object;
+					_particle.x = _vector4.x / _vector4.w;
+					_particle.y = _vector4.y / _vector4.w;
+					_particle.z = _vector4.z;
+
+					_particle.rotation = object.rotation.z;
+
+					_particle.scale.x = object.scale.x * Math.abs( _particle.x - ( _vector4.x + camera.projectionMatrix.elements[0] ) / ( _vector4.w + camera.projectionMatrix.elements[12] ) );
+					_particle.scale.y = object.scale.y * Math.abs( _particle.y - ( _vector4.y + camera.projectionMatrix.elements[5] ) / ( _vector4.w + camera.projectionMatrix.elements[13] ) );
+
+					_particle.material = object.material;
+
+					_renderData.elements.push( _particle );
+
+				}
+
+			}
+
+		}
+
+		if ( sortElements === true ) _renderData.elements.sort( painterSort );
+
+		return _renderData;
+
+	};
+
+	// Pools
+
+	function getNextObjectInPool() {
+
+		if ( _objectCount === _objectPoolLength ) {
+
+			var object = new THREE.RenderableObject();
+			_objectPool.push( object );
+			_objectPoolLength ++;
+			_objectCount ++;
+			return object;
+
+		}
+
+		return _objectPool[ _objectCount ++ ];
+
+	}
+
+	function getNextVertexInPool() {
+
+		if ( _vertexCount === _vertexPoolLength ) {
+
+			var vertex = new THREE.RenderableVertex();
+			_vertexPool.push( vertex );
+			_vertexPoolLength ++;
+			_vertexCount ++;
+			return vertex;
+
+		}
+
+		return _vertexPool[ _vertexCount ++ ];
+
+	}
+
+	function getNextFace3InPool() {
+
+		if ( _face3Count === _face3PoolLength ) {
+
+			var face = new THREE.RenderableFace3();
+			_face3Pool.push( face );
+			_face3PoolLength ++;
+			_face3Count ++;
+			return face;
+
+		}
+
+		return _face3Pool[ _face3Count ++ ];
+
+
+	}
+
+	function getNextFace4InPool() {
+
+		if ( _face4Count === _face4PoolLength ) {
+
+			var face = new THREE.RenderableFace4();
+			_face4Pool.push( face );
+			_face4PoolLength ++;
+			_face4Count ++;
+			return face;
+
+		}
+
+		return _face4Pool[ _face4Count ++ ];
+
+	}
+
+	function getNextLineInPool() {
+
+		if ( _lineCount === _linePoolLength ) {
+
+			var line = new THREE.RenderableLine();
+			_linePool.push( line );
+			_linePoolLength ++;
+			_lineCount ++
+			return line;
+
+		}
+
+		return _linePool[ _lineCount ++ ];
+
+	}
+
+	function getNextParticleInPool() {
+
+		if ( _particleCount === _particlePoolLength ) {
+
+			var particle = new THREE.RenderableParticle();
+			_particlePool.push( particle );
+			_particlePoolLength ++;
+			_particleCount ++
+			return particle;
+
+		}
+
+		return _particlePool[ _particleCount ++ ];
+
+	}
+
+	//
+
+	function painterSort( a, b ) {
+
+		return b.z - a.z;
+
+	}
+
+	function clipLine( s1, s2 ) {
+
+		var alpha1 = 0, alpha2 = 1,
+
+		// Calculate the boundary coordinate of each vertex for the near and far clip planes,
+		// Z = -1 and Z = +1, respectively.
+		bc1near =  s1.z + s1.w,
+		bc2near =  s2.z + s2.w,
+		bc1far =  - s1.z + s1.w,
+		bc2far =  - s2.z + s2.w;
+
+		if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) {
+
+			// Both vertices lie entirely within all clip planes.
+			return true;
+
+		} else if ( ( bc1near < 0 && bc2near < 0) || (bc1far < 0 && bc2far < 0 ) ) {
+
+			// Both vertices lie entirely outside one of the clip planes.
+			return false;
+
+		} else {
+
+			// The line segment spans at least one clip plane.
+
+			if ( bc1near < 0 ) {
+
+				// v1 lies outside the near plane, v2 inside
+				alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) );
+
+			} else if ( bc2near < 0 ) {
+
+				// v2 lies outside the near plane, v1 inside
+				alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) );
+
+			}
+
+			if ( bc1far < 0 ) {
+
+				// v1 lies outside the far plane, v2 inside
+				alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) );
+
+			} else if ( bc2far < 0 ) {
+
+				// v2 lies outside the far plane, v2 inside
+				alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) );
+
+			}
+
+			if ( alpha2 < alpha1 ) {
+
+				// The line segment spans two boundaries, but is outside both of them.
+				// (This can't happen when we're only clipping against just near/far but good
+				//  to leave the check here for future usage if other clip planes are added.)
+				return false;
+
+			} else {
+
+				// Update the s1 and s2 vertices to match the clipped line segment.
+				s1.lerp( s2, alpha1 );
+				s2.lerp( s1, 1 - alpha2 );
+
+				return true;
+
+			}
+
+		}
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Face3 = function ( a, b, c, normal, color, materialIndex ) {
+
+	this.a = a;
+	this.b = b;
+	this.c = c;
+
+	this.normal = normal instanceof THREE.Vector3 ? normal : new THREE.Vector3();
+	this.vertexNormals = normal instanceof Array ? normal : [ ];
+
+	this.color = color instanceof THREE.Color ? color : new THREE.Color();
+	this.vertexColors = color instanceof Array ? color : [];
+
+	this.vertexTangents = [];
+
+	this.materialIndex = materialIndex !== undefined ? materialIndex : 0;
+
+	this.centroid = new THREE.Vector3();
+
+};
+
+THREE.Face3.prototype = {
+
+	constructor: THREE.Face3,
+
+	clone: function () {
+
+		var face = new THREE.Face3( this.a, this.b, this.c );
+
+		face.normal.copy( this.normal );
+		face.color.copy( this.color );
+		face.centroid.copy( this.centroid );
+
+		face.materialIndex = this.materialIndex;
+
+		var i, il;
+		for ( i = 0, il = this.vertexNormals.length; i < il; i ++ ) face.vertexNormals[ i ] = this.vertexNormals[ i ].clone();
+		for ( i = 0, il = this.vertexColors.length; i < il; i ++ ) face.vertexColors[ i ] = this.vertexColors[ i ].clone();
+		for ( i = 0, il = this.vertexTangents.length; i < il; i ++ ) face.vertexTangents[ i ] = this.vertexTangents[ i ].clone();
+
+		return face;
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Face4 = function ( a, b, c, d, normal, color, materialIndex ) {
+
+	this.a = a;
+	this.b = b;
+	this.c = c;
+	this.d = d;
+
+	this.normal = normal instanceof THREE.Vector3 ? normal : new THREE.Vector3();
+	this.vertexNormals = normal instanceof Array ? normal : [ ];
+
+	this.color = color instanceof THREE.Color ? color : new THREE.Color();
+	this.vertexColors = color instanceof Array ? color : [];
+
+	this.vertexTangents = [];
+
+	this.materialIndex = materialIndex !== undefined ? materialIndex : 0;
+
+	this.centroid = new THREE.Vector3();
+
+};
+
+THREE.Face4.prototype = {
+
+	constructor: THREE.Face4,
+
+	clone: function () {
+
+		var face = new THREE.Face4( this.a, this.b, this.c, this.d );
+
+		face.normal.copy( this.normal );
+		face.color.copy( this.color );
+		face.centroid.copy( this.centroid );
+
+		face.materialIndex = this.materialIndex;
+
+		var i, il;
+		for ( i = 0, il = this.vertexNormals.length; i < il; i ++ ) face.vertexNormals[ i ] = this.vertexNormals[ i ].clone();
+		for ( i = 0, il = this.vertexColors.length; i < il; i ++ ) face.vertexColors[ i ] = this.vertexColors[ i ].clone();
+		for ( i = 0, il = this.vertexTangents.length; i < il; i ++ ) face.vertexTangents[ i ] = this.vertexTangents[ i ].clone();
+
+		return face;
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author kile / http://kile.stravaganza.org/
+ * @author alteredq / http://alteredqualia.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Geometry = function () {
+
+	this.id = THREE.GeometryIdCount ++;
+
+	this.name = '';
+
+	this.vertices = [];
+	this.colors = [];  // one-to-one vertex colors, used in ParticleSystem, Line and Ribbon
+	this.normals = []; // one-to-one vertex normals, used in Ribbon
+
+	this.faces = [];
+
+	this.faceUvs = [[]];
+	this.faceVertexUvs = [[]];
+
+	this.morphTargets = [];
+	this.morphColors = [];
+	this.morphNormals = [];
+
+	this.skinWeights = [];
+	this.skinIndices = [];
+
+	this.lineDistances = [];
+
+	this.boundingBox = null;
+	this.boundingSphere = null;
+
+	this.hasTangents = false;
+
+	this.dynamic = true; // the intermediate typed arrays will be deleted when set to false
+
+	// update flags
+
+	this.verticesNeedUpdate = false;
+	this.elementsNeedUpdate = false;
+	this.uvsNeedUpdate = false;
+	this.normalsNeedUpdate = false;
+	this.tangentsNeedUpdate = false;
+	this.colorsNeedUpdate = false;
+	this.lineDistancesNeedUpdate = false;
+
+	this.buffersNeedUpdate = false;
+
+};
+
+THREE.Geometry.prototype = {
+
+	constructor: THREE.Geometry,
+
+	addEventListener: THREE.EventDispatcher.prototype.addEventListener,
+	hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
+	removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
+	dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent,
+
+	applyMatrix: function ( matrix ) {
+
+		var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+		for ( var i = 0, il = this.vertices.length; i < il; i ++ ) {
+
+			var vertex = this.vertices[ i ];
+			vertex.applyMatrix4( matrix );
+
+		}
+
+		for ( var i = 0, il = this.faces.length; i < il; i ++ ) {
+
+			var face = this.faces[ i ];
+			face.normal.applyMatrix3( normalMatrix ).normalize();
+
+			for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {
+
+				face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize();
+
+			}
+
+			face.centroid.applyMatrix4( matrix );
+
+		}
+
+	},
+
+	computeCentroids: function () {
+
+		var f, fl, face;
+
+		for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+			face = this.faces[ f ];
+			face.centroid.set( 0, 0, 0 );
+
+			if ( face instanceof THREE.Face3 ) {
+
+				face.centroid.add( this.vertices[ face.a ] );
+				face.centroid.add( this.vertices[ face.b ] );
+				face.centroid.add( this.vertices[ face.c ] );
+				face.centroid.divideScalar( 3 );
+
+			} else if ( face instanceof THREE.Face4 ) {
+
+				face.centroid.add( this.vertices[ face.a ] );
+				face.centroid.add( this.vertices[ face.b ] );
+				face.centroid.add( this.vertices[ face.c ] );
+				face.centroid.add( this.vertices[ face.d ] );
+				face.centroid.divideScalar( 4 );
+
+			}
+
+		}
+
+	},
+
+	computeFaceNormals: function () {
+
+		var cb = new THREE.Vector3(), ab = new THREE.Vector3();
+
+		for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+			var face = this.faces[ f ];
+
+			var vA = this.vertices[ face.a ];
+			var vB = this.vertices[ face.b ];
+			var vC = this.vertices[ face.c ];
+
+			cb.subVectors( vC, vB );
+			ab.subVectors( vA, vB );
+			cb.cross( ab );
+
+			cb.normalize();
+
+			face.normal.copy( cb );
+
+		}
+
+	},
+
+	computeVertexNormals: function ( areaWeighted ) {
+
+		var v, vl, f, fl, face, vertices;
+
+		// create internal buffers for reuse when calling this method repeatedly
+		// (otherwise memory allocation / deallocation every frame is big resource hog)
+
+		if ( this.__tmpVertices === undefined ) {
+
+			this.__tmpVertices = new Array( this.vertices.length );
+			vertices = this.__tmpVertices;
+
+			for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+				vertices[ v ] = new THREE.Vector3();
+
+			}
+
+			for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+				face = this.faces[ f ];
+
+				if ( face instanceof THREE.Face3 ) {
+
+					face.vertexNormals = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+
+				} else if ( face instanceof THREE.Face4 ) {
+
+					face.vertexNormals = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+
+				}
+
+			}
+
+		} else {
+
+			vertices = this.__tmpVertices;
+
+			for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+				vertices[ v ].set( 0, 0, 0 );
+
+			}
+
+		}
+
+		if ( areaWeighted ) {
+
+			// vertex normals weighted by triangle areas
+			// http://www.iquilezles.org/www/articles/normals/normals.htm
+
+			var vA, vB, vC, vD;
+			var cb = new THREE.Vector3(), ab = new THREE.Vector3(),
+				db = new THREE.Vector3(), dc = new THREE.Vector3(), bc = new THREE.Vector3();
+
+			for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+				face = this.faces[ f ];
+
+				if ( face instanceof THREE.Face3 ) {
+
+					vA = this.vertices[ face.a ];
+					vB = this.vertices[ face.b ];
+					vC = this.vertices[ face.c ];
+
+					cb.subVectors( vC, vB );
+					ab.subVectors( vA, vB );
+					cb.cross( ab );
+
+					vertices[ face.a ].add( cb );
+					vertices[ face.b ].add( cb );
+					vertices[ face.c ].add( cb );
+
+				} else if ( face instanceof THREE.Face4 ) {
+
+					vA = this.vertices[ face.a ];
+					vB = this.vertices[ face.b ];
+					vC = this.vertices[ face.c ];
+					vD = this.vertices[ face.d ];
+
+					// abd
+
+					db.subVectors( vD, vB );
+					ab.subVectors( vA, vB );
+					db.cross( ab );
+
+					vertices[ face.a ].add( db );
+					vertices[ face.b ].add( db );
+					vertices[ face.d ].add( db );
+
+					// bcd
+
+					dc.subVectors( vD, vC );
+					bc.subVectors( vB, vC );
+					dc.cross( bc );
+
+					vertices[ face.b ].add( dc );
+					vertices[ face.c ].add( dc );
+					vertices[ face.d ].add( dc );
+
+				}
+
+			}
+
+		} else {
+
+			for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+				face = this.faces[ f ];
+
+				if ( face instanceof THREE.Face3 ) {
+
+					vertices[ face.a ].add( face.normal );
+					vertices[ face.b ].add( face.normal );
+					vertices[ face.c ].add( face.normal );
+
+				} else if ( face instanceof THREE.Face4 ) {
+
+					vertices[ face.a ].add( face.normal );
+					vertices[ face.b ].add( face.normal );
+					vertices[ face.c ].add( face.normal );
+					vertices[ face.d ].add( face.normal );
+
+				}
+
+			}
+
+		}
+
+		for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+			vertices[ v ].normalize();
+
+		}
+
+		for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+			face = this.faces[ f ];
+
+			if ( face instanceof THREE.Face3 ) {
+
+				face.vertexNormals[ 0 ].copy( vertices[ face.a ] );
+				face.vertexNormals[ 1 ].copy( vertices[ face.b ] );
+				face.vertexNormals[ 2 ].copy( vertices[ face.c ] );
+
+			} else if ( face instanceof THREE.Face4 ) {
+
+				face.vertexNormals[ 0 ].copy( vertices[ face.a ] );
+				face.vertexNormals[ 1 ].copy( vertices[ face.b ] );
+				face.vertexNormals[ 2 ].copy( vertices[ face.c ] );
+				face.vertexNormals[ 3 ].copy( vertices[ face.d ] );
+
+			}
+
+		}
+
+	},
+
+	computeMorphNormals: function () {
+
+		var i, il, f, fl, face;
+
+		// save original normals
+		// - create temp variables on first access
+		//   otherwise just copy (for faster repeated calls)
+
+		for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+			face = this.faces[ f ];
+
+			if ( ! face.__originalFaceNormal ) {
+
+				face.__originalFaceNormal = face.normal.clone();
+
+			} else {
+
+				face.__originalFaceNormal.copy( face.normal );
+
+			}
+
+			if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = [];
+
+			for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) {
+
+				if ( ! face.__originalVertexNormals[ i ] ) {
+
+					face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone();
+
+				} else {
+
+					face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] );
+
+				}
+
+			}
+
+		}
+
+		// use temp geometry to compute face and vertex normals for each morph
+
+		var tmpGeo = new THREE.Geometry();
+		tmpGeo.faces = this.faces;
+
+		for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) {
+
+			// create on first access
+
+			if ( ! this.morphNormals[ i ] ) {
+
+				this.morphNormals[ i ] = {};
+				this.morphNormals[ i ].faceNormals = [];
+				this.morphNormals[ i ].vertexNormals = [];
+
+				var dstNormalsFace = this.morphNormals[ i ].faceNormals;
+				var dstNormalsVertex = this.morphNormals[ i ].vertexNormals;
+
+				var faceNormal, vertexNormals;
+
+				for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+					face = this.faces[ f ];
+
+					faceNormal = new THREE.Vector3();
+
+					if ( face instanceof THREE.Face3 ) {
+
+						vertexNormals = { a: new THREE.Vector3(), b: new THREE.Vector3(), c: new THREE.Vector3() };
+
+					} else {
+
+						vertexNormals = { a: new THREE.Vector3(), b: new THREE.Vector3(), c: new THREE.Vector3(), d: new THREE.Vector3() };
+
+					}
+
+					dstNormalsFace.push( faceNormal );
+					dstNormalsVertex.push( vertexNormals );
+
+				}
+
+			}
+
+			var morphNormals = this.morphNormals[ i ];
+
+			// set vertices to morph target
+
+			tmpGeo.vertices = this.morphTargets[ i ].vertices;
+
+			// compute morph normals
+
+			tmpGeo.computeFaceNormals();
+			tmpGeo.computeVertexNormals();
+
+			// store morph normals
+
+			var faceNormal, vertexNormals;
+
+			for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+				face = this.faces[ f ];
+
+				faceNormal = morphNormals.faceNormals[ f ];
+				vertexNormals = morphNormals.vertexNormals[ f ];
+
+				faceNormal.copy( face.normal );
+
+				if ( face instanceof THREE.Face3 ) {
+
+					vertexNormals.a.copy( face.vertexNormals[ 0 ] );
+					vertexNormals.b.copy( face.vertexNormals[ 1 ] );
+					vertexNormals.c.copy( face.vertexNormals[ 2 ] );
+
+				} else {
+
+					vertexNormals.a.copy( face.vertexNormals[ 0 ] );
+					vertexNormals.b.copy( face.vertexNormals[ 1 ] );
+					vertexNormals.c.copy( face.vertexNormals[ 2 ] );
+					vertexNormals.d.copy( face.vertexNormals[ 3 ] );
+
+				}
+
+			}
+
+		}
+
+		// restore original normals
+
+		for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+			face = this.faces[ f ];
+
+			face.normal = face.__originalFaceNormal;
+			face.vertexNormals = face.__originalVertexNormals;
+
+		}
+
+	},
+
+	computeTangents: function () {
+
+		// based on http://www.terathon.com/code/tangent.html
+		// tangents go to vertices
+
+		var f, fl, v, vl, i, il, vertexIndex,
+			face, uv, vA, vB, vC, uvA, uvB, uvC,
+			x1, x2, y1, y2, z1, z2,
+			s1, s2, t1, t2, r, t, test,
+			tan1 = [], tan2 = [],
+			sdir = new THREE.Vector3(), tdir = new THREE.Vector3(),
+			tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3(),
+			n = new THREE.Vector3(), w;
+
+		for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+			tan1[ v ] = new THREE.Vector3();
+			tan2[ v ] = new THREE.Vector3();
+
+		}
+
+		function handleTriangle( context, a, b, c, ua, ub, uc ) {
+
+			vA = context.vertices[ a ];
+			vB = context.vertices[ b ];
+			vC = context.vertices[ c ];
+
+			uvA = uv[ ua ];
+			uvB = uv[ ub ];
+			uvC = uv[ uc ];
+
+			x1 = vB.x - vA.x;
+			x2 = vC.x - vA.x;
+			y1 = vB.y - vA.y;
+			y2 = vC.y - vA.y;
+			z1 = vB.z - vA.z;
+			z2 = vC.z - vA.z;
+
+			s1 = uvB.x - uvA.x;
+			s2 = uvC.x - uvA.x;
+			t1 = uvB.y - uvA.y;
+			t2 = uvC.y - uvA.y;
+
+			r = 1.0 / ( s1 * t2 - s2 * t1 );
+			sdir.set( ( t2 * x1 - t1 * x2 ) * r,
+					  ( t2 * y1 - t1 * y2 ) * r,
+					  ( t2 * z1 - t1 * z2 ) * r );
+			tdir.set( ( s1 * x2 - s2 * x1 ) * r,
+					  ( s1 * y2 - s2 * y1 ) * r,
+					  ( s1 * z2 - s2 * z1 ) * r );
+
+			tan1[ a ].add( sdir );
+			tan1[ b ].add( sdir );
+			tan1[ c ].add( sdir );
+
+			tan2[ a ].add( tdir );
+			tan2[ b ].add( tdir );
+			tan2[ c ].add( tdir );
+
+		}
+
+		for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+			face = this.faces[ f ];
+			uv = this.faceVertexUvs[ 0 ][ f ]; // use UV layer 0 for tangents
+
+			if ( face instanceof THREE.Face3 ) {
+
+				handleTriangle( this, face.a, face.b, face.c, 0, 1, 2 );
+
+			} else if ( face instanceof THREE.Face4 ) {
+
+				handleTriangle( this, face.a, face.b, face.d, 0, 1, 3 );
+				handleTriangle( this, face.b, face.c, face.d, 1, 2, 3 );
+
+			}
+
+		}
+
+		var faceIndex = [ 'a', 'b', 'c', 'd' ];
+
+		for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+			face = this.faces[ f ];
+
+			for ( i = 0; i < face.vertexNormals.length; i++ ) {
+
+				n.copy( face.vertexNormals[ i ] );
+
+				vertexIndex = face[ faceIndex[ i ] ];
+
+				t = tan1[ vertexIndex ];
+
+				// Gram-Schmidt orthogonalize
+
+				tmp.copy( t );
+				tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();
+
+				// Calculate handedness
+
+				tmp2.crossVectors( face.vertexNormals[ i ], t );
+				test = tmp2.dot( tan2[ vertexIndex ] );
+				w = (test < 0.0) ? -1.0 : 1.0;
+
+				face.vertexTangents[ i ] = new THREE.Vector4( tmp.x, tmp.y, tmp.z, w );
+
+			}
+
+		}
+
+		this.hasTangents = true;
+
+	},
+
+	computeLineDistances: function ( ) {
+
+		var d = 0;
+		var vertices = this.vertices;
+
+		for ( var i = 0, il = vertices.length; i < il; i ++ ) {
+
+			if ( i > 0 ) {
+
+				d += vertices[ i ].distanceTo( vertices[ i - 1 ] );
+
+			}
+
+			this.lineDistances[ i ] = d;
+
+		}
+
+	},
+
+	computeBoundingBox: function () {
+
+		if ( this.boundingBox === null ) {
+
+			this.boundingBox = new THREE.Box3();
+
+		}
+
+		this.boundingBox.setFromPoints( this.vertices );
+
+	},
+
+	computeBoundingSphere: function () {
+
+		if ( this.boundingSphere === null ) {
+
+			this.boundingSphere = new THREE.Sphere();
+
+		}
+
+		this.boundingSphere.setFromCenterAndPoints( this.boundingSphere.center, this.vertices );
+
+	},
+
+	/*
+	 * Checks for duplicate vertices with hashmap.
+	 * Duplicated vertices are removed
+	 * and faces' vertices are updated.
+	 */
+
+	mergeVertices: function () {
+
+		var verticesMap = {}; // Hashmap for looking up vertice by position coordinates (and making sure they are unique)
+		var unique = [], changes = [];
+
+		var v, key;
+		var precisionPoints = 4; // number of decimal points, eg. 4 for epsilon of 0.0001
+		var precision = Math.pow( 10, precisionPoints );
+		var i,il, face;
+		var indices, k, j, jl, u;
+
+		// reset cache of vertices as it now will be changing.
+		this.__tmpVertices = undefined;
+
+		for ( i = 0, il = this.vertices.length; i < il; i ++ ) {
+
+			v = this.vertices[ i ];
+			key = [ Math.round( v.x * precision ), Math.round( v.y * precision ), Math.round( v.z * precision ) ].join( '_' );
+
+			if ( verticesMap[ key ] === undefined ) {
+
+				verticesMap[ key ] = i;
+				unique.push( this.vertices[ i ] );
+				changes[ i ] = unique.length - 1;
+
+			} else {
+
+				//console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]);
+				changes[ i ] = changes[ verticesMap[ key ] ];
+
+			}
+
+		};
+
+
+		// if faces are completely degenerate after merging vertices, we
+		// have to remove them from the geometry.
+		var faceIndicesToRemove = [];
+
+		for( i = 0, il = this.faces.length; i < il; i ++ ) {
+
+			face = this.faces[ i ];
+
+			if ( face instanceof THREE.Face3 ) {
+
+				face.a = changes[ face.a ];
+				face.b = changes[ face.b ];
+				face.c = changes[ face.c ];
+
+				indices = [ face.a, face.b, face.c ];
+
+				var dupIndex = -1;
+
+				// if any duplicate vertices are found in a Face3
+				// we have to remove the face as nothing can be saved
+				for ( var n = 0; n < 3; n ++ ) {
+					if ( indices[ n ] == indices[ ( n + 1 ) % 3 ] ) {
+
+						dupIndex = n;
+						faceIndicesToRemove.push( i );
+						break;
+
+					}
+				}
+
+			} else if ( face instanceof THREE.Face4 ) {
+
+				face.a = changes[ face.a ];
+				face.b = changes[ face.b ];
+				face.c = changes[ face.c ];
+				face.d = changes[ face.d ];
+
+				// check dups in (a, b, c, d) and convert to -> face3
+
+				indices = [ face.a, face.b, face.c, face.d ];
+
+				var dupIndex = -1;
+
+				for ( var n = 0; n < 4; n ++ ) {
+
+					if ( indices[ n ] == indices[ ( n + 1 ) % 4 ] ) {
+
+						// if more than one duplicated vertex is found
+						// we can't generate any valid Face3's, thus
+						// we need to remove this face complete.
+						if ( dupIndex >= 0 ) {
+
+							faceIndicesToRemove.push( i );
+
+						}
+
+						dupIndex = n;
+
+					}
+				}
+
+				if ( dupIndex >= 0 ) {
+
+					indices.splice( dupIndex, 1 );
+
+					var newFace = new THREE.Face3( indices[0], indices[1], indices[2], face.normal, face.color, face.materialIndex );
+
+					for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {
+
+						u = this.faceVertexUvs[ j ][ i ];
+
+						if ( u ) {
+							u.splice( dupIndex, 1 );
+						}
+
+					}
+
+					if( face.vertexNormals && face.vertexNormals.length > 0) {
+
+						newFace.vertexNormals = face.vertexNormals;
+						newFace.vertexNormals.splice( dupIndex, 1 );
+
+					}
+
+					if( face.vertexColors && face.vertexColors.length > 0 ) {
+
+						newFace.vertexColors = face.vertexColors;
+						newFace.vertexColors.splice( dupIndex, 1 );
+					}
+
+					this.faces[ i ] = newFace;
+				}
+
+			}
+
+		}
+
+		for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) {
+
+			this.faces.splice( i, 1 );
+
+			for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {
+
+				this.faceVertexUvs[ j ].splice( i, 1 );
+
+			}
+
+		}
+
+		// Use unique set of vertices
+
+		var diff = this.vertices.length - unique.length;
+		this.vertices = unique;
+		return diff;
+
+	},
+
+	clone: function () {
+
+		var geometry = new THREE.Geometry();
+
+		var vertices = this.vertices;
+
+		for ( var i = 0, il = vertices.length; i < il; i ++ ) {
+
+			geometry.vertices.push( vertices[ i ].clone() );
+
+		}
+
+		var faces = this.faces;
+
+		for ( var i = 0, il = faces.length; i < il; i ++ ) {
+
+			geometry.faces.push( faces[ i ].clone() );
+
+		}
+
+		var uvs = this.faceVertexUvs[ 0 ];
+
+		for ( var i = 0, il = uvs.length; i < il; i ++ ) {
+
+			var uv = uvs[ i ], uvCopy = [];
+
+			for ( var j = 0, jl = uv.length; j < jl; j ++ ) {
+
+				uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) );
+
+			}
+
+			geometry.faceVertexUvs[ 0 ].push( uvCopy );
+
+		}
+
+		return geometry;
+
+	},
+
+	dispose: function () {
+
+		this.dispatchEvent( { type: 'dispose' } );
+
+	}
+
+};
+
+THREE.GeometryIdCount = 0;
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.BufferGeometry = function () {
+
+	this.id = THREE.GeometryIdCount ++;
+
+	// attributes
+
+	this.attributes = {};
+
+	// attributes typed arrays are kept only if dynamic flag is set
+
+	this.dynamic = false;
+
+	// offsets for chunks when using indexed elements
+
+	this.offsets = [];
+
+	// boundings
+
+	this.boundingBox = null;
+	this.boundingSphere = null;
+
+	this.hasTangents = false;
+
+	// for compatibility
+
+	this.morphTargets = [];
+
+};
+
+THREE.BufferGeometry.prototype = {
+
+	constructor: THREE.BufferGeometry,
+
+	addEventListener: THREE.EventDispatcher.prototype.addEventListener,
+	hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
+	removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
+	dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent,
+
+	applyMatrix: function ( matrix ) {
+
+		var positionArray;
+		var normalArray;
+
+		if ( this.attributes[ "position" ] ) positionArray = this.attributes[ "position" ].array;
+		if ( this.attributes[ "normal" ] ) normalArray = this.attributes[ "normal" ].array;
+
+		if ( positionArray !== undefined ) {
+
+			matrix.multiplyVector3Array( positionArray );
+			this.verticesNeedUpdate = true;
+
+		}
+
+		if ( normalArray !== undefined ) {
+
+			var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+			normalMatrix.multiplyVector3Array( normalArray );
+
+			this.normalizeNormals();
+
+			this.normalsNeedUpdate = true;
+
+		}
+
+	},
+
+	computeBoundingBox: function () {
+
+		if ( this.boundingBox === null ) {
+
+			this.boundingBox = new THREE.Box3();
+
+		}
+
+		var positions = this.attributes[ "position" ].array;
+
+		if ( positions ) {
+
+			var bb = this.boundingBox;
+			var x, y, z;
+
+			if( positions.length >= 3 ) {
+				bb.min.x = bb.max.x = positions[ 0 ];
+				bb.min.y = bb.max.y = positions[ 1 ];
+				bb.min.z = bb.max.z = positions[ 2 ];
+			}
+
+			for ( var i = 3, il = positions.length; i < il; i += 3 ) {
+
+				x = positions[ i ];
+				y = positions[ i + 1 ];
+				z = positions[ i + 2 ];
+
+				// bounding box
+
+				if ( x < bb.min.x ) {
+
+					bb.min.x = x;
+
+				} else if ( x > bb.max.x ) {
+
+					bb.max.x = x;
+
+				}
+
+				if ( y < bb.min.y ) {
+
+					bb.min.y = y;
+
+				} else if ( y > bb.max.y ) {
+
+					bb.max.y = y;
+
+				}
+
+				if ( z < bb.min.z ) {
+
+					bb.min.z = z;
+
+				} else if ( z > bb.max.z ) {
+
+					bb.max.z = z;
+
+				}
+
+			}
+
+		}
+
+		if ( positions === undefined || positions.length === 0 ) {
+
+			this.boundingBox.min.set( 0, 0, 0 );
+			this.boundingBox.max.set( 0, 0, 0 );
+
+		}
+
+	},
+
+	computeBoundingSphere: function () {
+
+		if ( this.boundingSphere === null ) {
+
+			this.boundingSphere = new THREE.Sphere();
+
+		}
+
+		var positions = this.attributes[ "position" ].array;
+
+		if ( positions ) {
+
+			var radiusSq, maxRadiusSq = 0;
+			var x, y, z;
+
+			for ( var i = 0, il = positions.length; i < il; i += 3 ) {
+
+				x = positions[ i ];
+				y = positions[ i + 1 ];
+				z = positions[ i + 2 ];
+
+				radiusSq =  x * x + y * y + z * z;
+				if ( radiusSq > maxRadiusSq ) maxRadiusSq = radiusSq;
+
+			}
+
+			this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
+
+		}
+
+	},
+
+	computeVertexNormals: function () {
+
+		if ( this.attributes[ "position" ] ) {
+
+			var i, il;
+			var j, jl;
+
+			var nVertexElements = this.attributes[ "position" ].array.length;
+
+			if ( this.attributes[ "normal" ] === undefined ) {
+
+				this.attributes[ "normal" ] = {
+
+					itemSize: 3,
+					array: new Float32Array( nVertexElements ),
+					numItems: nVertexElements
+
+				};
+
+			} else {
+
+				// reset existing normals to zero
+
+				for ( i = 0, il = this.attributes[ "normal" ].array.length; i < il; i ++ ) {
+
+					this.attributes[ "normal" ].array[ i ] = 0;
+
+				}
+
+			}
+
+			var positions = this.attributes[ "position" ].array;
+			var normals = this.attributes[ "normal" ].array;
+
+			var vA, vB, vC, x, y, z,
+
+			pA = new THREE.Vector3(),
+			pB = new THREE.Vector3(),
+			pC = new THREE.Vector3(),
+
+			cb = new THREE.Vector3(),
+			ab = new THREE.Vector3();
+
+			// indexed elements
+
+			if ( this.attributes[ "index" ] ) {
+
+				var indices = this.attributes[ "index" ].array;
+
+				var offsets = this.offsets;
+
+				for ( j = 0, jl = offsets.length; j < jl; ++ j ) {
+
+					var start = offsets[ j ].start;
+					var count = offsets[ j ].count;
+					var index = offsets[ j ].index;
+
+					for ( i = start, il = start + count; i < il; i += 3 ) {
+
+						vA = index + indices[ i ];
+						vB = index + indices[ i + 1 ];
+						vC = index + indices[ i + 2 ];
+
+						x = positions[ vA * 3 ];
+						y = positions[ vA * 3 + 1 ];
+						z = positions[ vA * 3 + 2 ];
+						pA.set( x, y, z );
+
+						x = positions[ vB * 3 ];
+						y = positions[ vB * 3 + 1 ];
+						z = positions[ vB * 3 + 2 ];
+						pB.set( x, y, z );
+
+						x = positions[ vC * 3 ];
+						y = positions[ vC * 3 + 1 ];
+						z = positions[ vC * 3 + 2 ];
+						pC.set( x, y, z );
+
+						cb.subVectors( pC, pB );
+						ab.subVectors( pA, pB );
+						cb.cross( ab );
+
+						normals[ vA * 3 ]     += cb.x;
+						normals[ vA * 3 + 1 ] += cb.y;
+						normals[ vA * 3 + 2 ] += cb.z;
+
+						normals[ vB * 3 ]     += cb.x;
+						normals[ vB * 3 + 1 ] += cb.y;
+						normals[ vB * 3 + 2 ] += cb.z;
+
+						normals[ vC * 3 ]     += cb.x;
+						normals[ vC * 3 + 1 ] += cb.y;
+						normals[ vC * 3 + 2 ] += cb.z;
+
+					}
+
+				}
+
+			// non-indexed elements (unconnected triangle soup)
+
+			} else {
+
+				for ( i = 0, il = positions.length; i < il; i += 9 ) {
+
+					x = positions[ i ];
+					y = positions[ i + 1 ];
+					z = positions[ i + 2 ];
+					pA.set( x, y, z );
+
+					x = positions[ i + 3 ];
+					y = positions[ i + 4 ];
+					z = positions[ i + 5 ];
+					pB.set( x, y, z );
+
+					x = positions[ i + 6 ];
+					y = positions[ i + 7 ];
+					z = positions[ i + 8 ];
+					pC.set( x, y, z );
+
+					cb.subVectors( pC, pB );
+					ab.subVectors( pA, pB );
+					cb.cross( ab );
+
+					normals[ i ] 	 = cb.x;
+					normals[ i + 1 ] = cb.y;
+					normals[ i + 2 ] = cb.z;
+
+					normals[ i + 3 ] = cb.x;
+					normals[ i + 4 ] = cb.y;
+					normals[ i + 5 ] = cb.z;
+
+					normals[ i + 6 ] = cb.x;
+					normals[ i + 7 ] = cb.y;
+					normals[ i + 8 ] = cb.z;
+
+				}
+
+			}
+
+			this.normalizeNormals();
+
+			this.normalsNeedUpdate = true;
+
+		}
+
+	},
+
+	normalizeNormals: function () {
+
+		var normals = this.attributes[ "normal" ].array;
+
+		var x, y, z, n;
+
+		for ( var i = 0, il = normals.length; i < il; i += 3 ) {
+
+			x = normals[ i ];
+			y = normals[ i + 1 ];
+			z = normals[ i + 2 ];
+
+			n = 1.0 / Math.sqrt( x * x + y * y + z * z );
+
+			normals[ i ] 	 *= n;
+			normals[ i + 1 ] *= n;
+			normals[ i + 2 ] *= n;
+
+		}
+
+	},
+
+	computeTangents: function () {
+
+		// based on http://www.terathon.com/code/tangent.html
+		// (per vertex tangents)
+
+		if ( this.attributes[ "index" ] === undefined ||
+			 this.attributes[ "position" ] === undefined ||
+			 this.attributes[ "normal" ] === undefined ||
+			 this.attributes[ "uv" ] === undefined ) {
+
+			console.warn( "Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()" );
+			return;
+
+		}
+
+		var indices = this.attributes[ "index" ].array;
+		var positions = this.attributes[ "position" ].array;
+		var normals = this.attributes[ "normal" ].array;
+		var uvs = this.attributes[ "uv" ].array;
+
+		var nVertices = positions.length / 3;
+
+		if ( this.attributes[ "tangent" ] === undefined ) {
+
+			var nTangentElements = 4 * nVertices;
+
+			this.attributes[ "tangent" ] = {
+
+				itemSize: 4,
+				array: new Float32Array( nTangentElements ),
+				numItems: nTangentElements
+
+			};
+
+		}
+
+		var tangents = this.attributes[ "tangent" ].array;
+
+		var tan1 = [], tan2 = [];
+
+		for ( var k = 0; k < nVertices; k ++ ) {
+
+			tan1[ k ] = new THREE.Vector3();
+			tan2[ k ] = new THREE.Vector3();
+
+		}
+
+		var xA, yA, zA,
+			xB, yB, zB,
+			xC, yC, zC,
+
+			uA, vA,
+			uB, vB,
+			uC, vC,
+
+			x1, x2, y1, y2, z1, z2,
+			s1, s2, t1, t2, r;
+
+		var sdir = new THREE.Vector3(), tdir = new THREE.Vector3();
+
+		function handleTriangle( a, b, c ) {
+
+			xA = positions[ a * 3 ];
+			yA = positions[ a * 3 + 1 ];
+			zA = positions[ a * 3 + 2 ];
+
+			xB = positions[ b * 3 ];
+			yB = positions[ b * 3 + 1 ];
+			zB = positions[ b * 3 + 2 ];
+
+			xC = positions[ c * 3 ];
+			yC = positions[ c * 3 + 1 ];
+			zC = positions[ c * 3 + 2 ];
+
+			uA = uvs[ a * 2 ];
+			vA = uvs[ a * 2 + 1 ];
+
+			uB = uvs[ b * 2 ];
+			vB = uvs[ b * 2 + 1 ];
+
+			uC = uvs[ c * 2 ];
+			vC = uvs[ c * 2 + 1 ];
+
+			x1 = xB - xA;
+			x2 = xC - xA;
+
+			y1 = yB - yA;
+			y2 = yC - yA;
+
+			z1 = zB - zA;
+			z2 = zC - zA;
+
+			s1 = uB - uA;
+			s2 = uC - uA;
+
+			t1 = vB - vA;
+			t2 = vC - vA;
+
+			r = 1.0 / ( s1 * t2 - s2 * t1 );
+
+			sdir.set(
+				( t2 * x1 - t1 * x2 ) * r,
+				( t2 * y1 - t1 * y2 ) * r,
+				( t2 * z1 - t1 * z2 ) * r
+			);
+
+			tdir.set(
+				( s1 * x2 - s2 * x1 ) * r,
+				( s1 * y2 - s2 * y1 ) * r,
+				( s1 * z2 - s2 * z1 ) * r
+			);
+
+			tan1[ a ].add( sdir );
+			tan1[ b ].add( sdir );
+			tan1[ c ].add( sdir );
+
+			tan2[ a ].add( tdir );
+			tan2[ b ].add( tdir );
+			tan2[ c ].add( tdir );
+
+		}
+
+		var i, il;
+		var j, jl;
+		var iA, iB, iC;
+
+		var offsets = this.offsets;
+
+		for ( j = 0, jl = offsets.length; j < jl; ++ j ) {
+
+			var start = offsets[ j ].start;
+			var count = offsets[ j ].count;
+			var index = offsets[ j ].index;
+
+			for ( i = start, il = start + count; i < il; i += 3 ) {
+
+				iA = index + indices[ i ];
+				iB = index + indices[ i + 1 ];
+				iC = index + indices[ i + 2 ];
+
+				handleTriangle( iA, iB, iC );
+
+			}
+
+		}
+
+		var tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3();
+		var n = new THREE.Vector3(), n2 = new THREE.Vector3();
+		var w, t, test;
+
+		function handleVertex( v ) {
+
+			n.x = normals[ v * 3 ];
+			n.y = normals[ v * 3 + 1 ];
+			n.z = normals[ v * 3 + 2 ];
+
+			n2.copy( n );
+
+			t = tan1[ v ];
+
+			// Gram-Schmidt orthogonalize
+
+			tmp.copy( t );
+			tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();
+
+			// Calculate handedness
+
+			tmp2.crossVectors( n2, t );
+			test = tmp2.dot( tan2[ v ] );
+			w = ( test < 0.0 ) ? -1.0 : 1.0;
+
+			tangents[ v * 4 ]     = tmp.x;
+			tangents[ v * 4 + 1 ] = tmp.y;
+			tangents[ v * 4 + 2 ] = tmp.z;
+			tangents[ v * 4 + 3 ] = w;
+
+		}
+
+		for ( j = 0, jl = offsets.length; j < jl; ++ j ) {
+
+			var start = offsets[ j ].start;
+			var count = offsets[ j ].count;
+			var index = offsets[ j ].index;
+
+			for ( i = start, il = start + count; i < il; i += 3 ) {
+
+				iA = index + indices[ i ];
+				iB = index + indices[ i + 1 ];
+				iC = index + indices[ i + 2 ];
+
+				handleVertex( iA );
+				handleVertex( iB );
+				handleVertex( iC );
+
+			}
+
+		}
+
+		this.hasTangents = true;
+		this.tangentsNeedUpdate = true;
+
+	},
+
+	dispose: function () {
+
+		this.dispatchEvent( { type: 'dispose' } );
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.Camera = function () {
+
+	THREE.Object3D.call( this );
+
+	this.matrixWorldInverse = new THREE.Matrix4();
+
+	this.projectionMatrix = new THREE.Matrix4();
+	this.projectionMatrixInverse = new THREE.Matrix4();
+
+};
+
+THREE.Camera.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Camera.prototype.lookAt = function () {
+
+	// This routine does not support cameras with rotated and/or translated parent(s)
+
+	var m1 = new THREE.Matrix4();
+
+	return function ( vector ) {
+
+		m1.lookAt( this.position, vector, this.up );
+
+		if ( this.useQuaternion === true )  {
+
+			this.quaternion.setFromRotationMatrix( m1 );
+
+		} else {
+
+			this.rotation.setEulerFromRotationMatrix( m1, this.eulerOrder );
+
+		}
+
+	};
+
+}();
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.OrthographicCamera = function ( left, right, top, bottom, near, far ) {
+
+	THREE.Camera.call( this );
+
+	this.left = left;
+	this.right = right;
+	this.top = top;
+	this.bottom = bottom;
+
+	this.near = ( near !== undefined ) ? near : 0.1;
+	this.far = ( far !== undefined ) ? far : 2000;
+
+	this.updateProjectionMatrix();
+
+};
+
+THREE.OrthographicCamera.prototype = Object.create( THREE.Camera.prototype );
+
+THREE.OrthographicCamera.prototype.updateProjectionMatrix = function () {
+
+	this.projectionMatrix.makeOrthographic( this.left, this.right, this.top, this.bottom, this.near, this.far );
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author greggman / http://games.greggman.com/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ */
+
+THREE.PerspectiveCamera = function ( fov, aspect, near, far ) {
+
+	THREE.Camera.call( this );
+
+	this.fov = fov !== undefined ? fov : 50;
+	this.aspect = aspect !== undefined ? aspect : 1;
+	this.near = near !== undefined ? near : 0.1;
+	this.far = far !== undefined ? far : 2000;
+
+	this.updateProjectionMatrix();
+
+};
+
+THREE.PerspectiveCamera.prototype = Object.create( THREE.Camera.prototype );
+
+
+/**
+ * Uses Focal Length (in mm) to estimate and set FOV
+ * 35mm (fullframe) camera is used if frame size is not specified;
+ * Formula based on http://www.bobatkins.com/photography/technical/field_of_view.html
+ */
+
+THREE.PerspectiveCamera.prototype.setLens = function ( focalLength, frameHeight ) {
+
+	if ( frameHeight === undefined ) frameHeight = 24;
+
+	this.fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) );
+	this.updateProjectionMatrix();
+
+}
+
+
+/**
+ * Sets an offset in a larger frustum. This is useful for multi-window or
+ * multi-monitor/multi-machine setups.
+ *
+ * For example, if you have 3x2 monitors and each monitor is 1920x1080 and
+ * the monitors are in grid like this
+ *
+ *   +---+---+---+
+ *   | A | B | C |
+ *   +---+---+---+
+ *   | D | E | F |
+ *   +---+---+---+
+ *
+ * then for each monitor you would call it like this
+ *
+ *   var w = 1920;
+ *   var h = 1080;
+ *   var fullWidth = w * 3;
+ *   var fullHeight = h * 2;
+ *
+ *   --A--
+ *   camera.setOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );
+ *   --B--
+ *   camera.setOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );
+ *   --C--
+ *   camera.setOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );
+ *   --D--
+ *   camera.setOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );
+ *   --E--
+ *   camera.setOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );
+ *   --F--
+ *   camera.setOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );
+ *
+ *   Note there is no reason monitors have to be the same size or in a grid.
+ */
+
+THREE.PerspectiveCamera.prototype.setViewOffset = function ( fullWidth, fullHeight, x, y, width, height ) {
+
+	this.fullWidth = fullWidth;
+	this.fullHeight = fullHeight;
+	this.x = x;
+	this.y = y;
+	this.width = width;
+	this.height = height;
+
+	this.updateProjectionMatrix();
+
+};
+
+
+THREE.PerspectiveCamera.prototype.updateProjectionMatrix = function () {
+
+	if ( this.fullWidth ) {
+
+		var aspect = this.fullWidth / this.fullHeight;
+		var top = Math.tan( THREE.Math.degToRad( this.fov * 0.5 ) ) * this.near;
+		var bottom = -top;
+		var left = aspect * bottom;
+		var right = aspect * top;
+		var width = Math.abs( right - left );
+		var height = Math.abs( top - bottom );
+
+		this.projectionMatrix.makeFrustum(
+			left + this.x * width / this.fullWidth,
+			left + ( this.x + this.width ) * width / this.fullWidth,
+			top - ( this.y + this.height ) * height / this.fullHeight,
+			top - this.y * height / this.fullHeight,
+			this.near,
+			this.far
+		);
+
+	} else {
+
+		this.projectionMatrix.makePerspective( this.fov, this.aspect, this.near, this.far );
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+ 
+THREE.Light = function ( hex ) {
+
+	THREE.Object3D.call( this );
+
+	this.color = new THREE.Color( hex );
+
+};
+
+THREE.Light.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Light.prototype.clone = function ( light ) {
+
+	if ( light === undefined ) light = new THREE.Light();
+
+	THREE.Object3D.prototype.clone.call( this, light );
+
+	light.color.copy( this.color );
+
+	return light;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.AmbientLight = function ( hex ) {
+
+	THREE.Light.call( this, hex );
+
+};
+
+THREE.AmbientLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.AmbientLight.prototype.clone = function () {
+
+	var light = new THREE.AmbientLight();
+
+	THREE.Light.prototype.clone.call( this, light );
+
+	return light;
+
+};
+/**
+ * @author MPanknin / http://www.redplant.de/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.AreaLight = function ( hex, intensity ) {
+
+	THREE.Light.call( this, hex );
+
+	this.normal = new THREE.Vector3( 0, -1, 0 );
+	this.right = new THREE.Vector3( 1, 0, 0 );
+
+	this.intensity = ( intensity !== undefined ) ? intensity : 1;
+
+	this.width = 1.0;
+	this.height = 1.0;
+
+	this.constantAttenuation = 1.5;
+	this.linearAttenuation = 0.5;
+	this.quadraticAttenuation = 0.1;
+
+};
+
+THREE.AreaLight.prototype = Object.create( THREE.Light.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.DirectionalLight = function ( hex, intensity ) {
+
+	THREE.Light.call( this, hex );
+
+	this.position.set( 0, 1, 0 );
+	this.target = new THREE.Object3D();
+
+	this.intensity = ( intensity !== undefined ) ? intensity : 1;
+
+	this.castShadow = false;
+	this.onlyShadow = false;
+
+	//
+
+	this.shadowCameraNear = 50;
+	this.shadowCameraFar = 5000;
+
+	this.shadowCameraLeft = -500;
+	this.shadowCameraRight = 500;
+	this.shadowCameraTop = 500;
+	this.shadowCameraBottom = -500;
+
+	this.shadowCameraVisible = false;
+
+	this.shadowBias = 0;
+	this.shadowDarkness = 0.5;
+
+	this.shadowMapWidth = 512;
+	this.shadowMapHeight = 512;
+
+	//
+
+	this.shadowCascade = false;
+
+	this.shadowCascadeOffset = new THREE.Vector3( 0, 0, -1000 );
+	this.shadowCascadeCount = 2;
+
+	this.shadowCascadeBias = [ 0, 0, 0 ];
+	this.shadowCascadeWidth = [ 512, 512, 512 ];
+	this.shadowCascadeHeight = [ 512, 512, 512 ];
+
+	this.shadowCascadeNearZ = [ -1.000, 0.990, 0.998 ];
+	this.shadowCascadeFarZ  = [  0.990, 0.998, 1.000 ];
+
+	this.shadowCascadeArray = [];
+
+	//
+
+	this.shadowMap = null;
+	this.shadowMapSize = null;
+	this.shadowCamera = null;
+	this.shadowMatrix = null;
+
+};
+
+THREE.DirectionalLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.DirectionalLight.prototype.clone = function () {
+
+	var light = new THREE.DirectionalLight();
+
+	THREE.Light.prototype.clone.call( this, light );
+
+	light.target = this.target.clone();
+
+	light.intensity = this.intensity;
+
+	light.castShadow = this.castShadow;
+	light.onlyShadow = this.onlyShadow;
+
+	return light;
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.HemisphereLight = function ( skyColorHex, groundColorHex, intensity ) {
+
+	THREE.Light.call( this, skyColorHex );
+
+	this.position.set( 0, 100, 0 );
+
+	this.groundColor = new THREE.Color( groundColorHex );
+	this.intensity = ( intensity !== undefined ) ? intensity : 1;
+
+};
+
+THREE.HemisphereLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.HemisphereLight.prototype.clone = function () {
+
+	var light = new THREE.PointLight();
+
+	THREE.Light.prototype.clone.call( this, light );
+
+	light.groundColor.copy( this.groundColor );
+	light.intensity = this.intensity;
+
+	return light;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.PointLight = function ( hex, intensity, distance ) {
+
+	THREE.Light.call( this, hex );
+
+	this.intensity = ( intensity !== undefined ) ? intensity : 1;
+	this.distance = ( distance !== undefined ) ? distance : 0;
+
+};
+
+THREE.PointLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.PointLight.prototype.clone = function () {
+
+	var light = new THREE.PointLight();
+
+	THREE.Light.prototype.clone.call( this, light );
+
+	light.intensity = this.intensity;
+	light.distance = this.distance;
+
+	return light;
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SpotLight = function ( hex, intensity, distance, angle, exponent ) {
+
+	THREE.Light.call( this, hex );
+
+	this.position.set( 0, 1, 0 );
+	this.target = new THREE.Object3D();
+
+	this.intensity = ( intensity !== undefined ) ? intensity : 1;
+	this.distance = ( distance !== undefined ) ? distance : 0;
+	this.angle = ( angle !== undefined ) ? angle : Math.PI / 3;
+	this.exponent = ( exponent !== undefined ) ? exponent : 10;
+
+	this.castShadow = false;
+	this.onlyShadow = false;
+
+	//
+
+	this.shadowCameraNear = 50;
+	this.shadowCameraFar = 5000;
+	this.shadowCameraFov = 50;
+
+	this.shadowCameraVisible = false;
+
+	this.shadowBias = 0;
+	this.shadowDarkness = 0.5;
+
+	this.shadowMapWidth = 512;
+	this.shadowMapHeight = 512;
+
+	//
+
+	this.shadowMap = null;
+	this.shadowMapSize = null;
+	this.shadowCamera = null;
+	this.shadowMatrix = null;
+
+};
+
+THREE.SpotLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.SpotLight.prototype.clone = function () {
+
+	var light = new THREE.SpotLight();
+
+	THREE.Light.prototype.clone.call( this, light );
+
+	light.target = this.target.clone();
+
+	light.intensity = this.intensity;
+	light.distance = this.distance;
+	light.angle = this.angle;
+	light.exponent = this.exponent;
+
+	light.castShadow = this.castShadow;
+	light.onlyShadow = this.onlyShadow;
+
+	return light;
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Loader = function ( showStatus ) {
+
+	this.showStatus = showStatus;
+	this.statusDomElement = showStatus ? THREE.Loader.prototype.addStatusElement() : null;
+
+	this.onLoadStart = function () {};
+	this.onLoadProgress = function () {};
+	this.onLoadComplete = function () {};
+
+};
+
+THREE.Loader.prototype = {
+
+	constructor: THREE.Loader,
+
+	crossOrigin: 'anonymous',
+
+	addStatusElement: function () {
+
+		var e = document.createElement( "div" );
+
+		e.style.position = "absolute";
+		e.style.right = "0px";
+		e.style.top = "0px";
+		e.style.fontSize = "0.8em";
+		e.style.textAlign = "left";
+		e.style.background = "rgba(0,0,0,0.25)";
+		e.style.color = "#fff";
+		e.style.width = "120px";
+		e.style.padding = "0.5em 0.5em 0.5em 0.5em";
+		e.style.zIndex = 1000;
+
+		e.innerHTML = "Loading ...";
+
+		return e;
+
+	},
+
+	updateProgress: function ( progress ) {
+
+		var message = "Loaded ";
+
+		if ( progress.total ) {
+
+			message += ( 100 * progress.loaded / progress.total ).toFixed(0) + "%";
+
+
+		} else {
+
+			message += ( progress.loaded / 1000 ).toFixed(2) + " KB";
+
+		}
+
+		this.statusDomElement.innerHTML = message;
+
+	},
+
+	extractUrlBase: function ( url ) {
+
+		var parts = url.split( '/' );
+		parts.pop();
+		return ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/';
+
+	},
+
+	initMaterials: function ( materials, texturePath ) {
+
+		var array = [];
+
+		for ( var i = 0; i < materials.length; ++ i ) {
+
+			array[ i ] = THREE.Loader.prototype.createMaterial( materials[ i ], texturePath );
+
+		}
+
+		return array;
+
+	},
+
+	needsTangents: function ( materials ) {
+
+		for( var i = 0, il = materials.length; i < il; i ++ ) {
+
+			var m = materials[ i ];
+
+			if ( m instanceof THREE.ShaderMaterial ) return true;
+
+		}
+
+		return false;
+
+	},
+
+	createMaterial: function ( m, texturePath ) {
+
+		var _this = this;
+
+		function is_pow2( n ) {
+
+			var l = Math.log( n ) / Math.LN2;
+			return Math.floor( l ) == l;
+
+		}
+
+		function nearest_pow2( n ) {
+
+			var l = Math.log( n ) / Math.LN2;
+			return Math.pow( 2, Math.round(  l ) );
+
+		}
+
+		function load_image( where, url ) {
+
+			var image = new Image();
+
+			image.onload = function () {
+
+				if ( !is_pow2( this.width ) || !is_pow2( this.height ) ) {
+
+					var width = nearest_pow2( this.width );
+					var height = nearest_pow2( this.height );
+
+					where.image.width = width;
+					where.image.height = height;
+					where.image.getContext( '2d' ).drawImage( this, 0, 0, width, height );
+
+				} else {
+
+					where.image = this;
+
+				}
+
+				where.needsUpdate = true;
+
+			};
+
+			image.crossOrigin = _this.crossOrigin;
+			image.src = url;
+
+		}
+
+		function create_texture( where, name, sourceFile, repeat, offset, wrap, anisotropy ) {
+
+			var isCompressed = /\.dds$/i.test( sourceFile );
+			var fullPath = texturePath + "/" + sourceFile;
+
+			if ( isCompressed ) {
+
+				var texture = THREE.ImageUtils.loadCompressedTexture( fullPath );
+
+				where[ name ] = texture;
+
+			} else {
+
+				var texture = document.createElement( 'canvas' );
+
+				where[ name ] = new THREE.Texture( texture );
+
+			}
+
+			where[ name ].sourceFile = sourceFile;
+
+			if( repeat ) {
+
+				where[ name ].repeat.set( repeat[ 0 ], repeat[ 1 ] );
+
+				if ( repeat[ 0 ] !== 1 ) where[ name ].wrapS = THREE.RepeatWrapping;
+				if ( repeat[ 1 ] !== 1 ) where[ name ].wrapT = THREE.RepeatWrapping;
+
+			}
+
+			if ( offset ) {
+
+				where[ name ].offset.set( offset[ 0 ], offset[ 1 ] );
+
+			}
+
+			if ( wrap ) {
+
+				var wrapMap = {
+					"repeat": THREE.RepeatWrapping,
+					"mirror": THREE.MirroredRepeatWrapping
+				}
+
+				if ( wrapMap[ wrap[ 0 ] ] !== undefined ) where[ name ].wrapS = wrapMap[ wrap[ 0 ] ];
+				if ( wrapMap[ wrap[ 1 ] ] !== undefined ) where[ name ].wrapT = wrapMap[ wrap[ 1 ] ];
+
+			}
+
+			if ( anisotropy ) {
+
+				where[ name ].anisotropy = anisotropy;
+
+			}
+
+			if ( ! isCompressed ) {
+
+				load_image( where[ name ], fullPath );
+
+			}
+
+		}
+
+		function rgb2hex( rgb ) {
+
+			return ( rgb[ 0 ] * 255 << 16 ) + ( rgb[ 1 ] * 255 << 8 ) + rgb[ 2 ] * 255;
+
+		}
+
+		// defaults
+
+		var mtype = "MeshLambertMaterial";
+		var mpars = { color: 0xeeeeee, opacity: 1.0, map: null, lightMap: null, normalMap: null, bumpMap: null, wireframe: false };
+
+		// parameters from model file
+
+		if ( m.shading ) {
+
+			var shading = m.shading.toLowerCase();
+
+			if ( shading === "phong" ) mtype = "MeshPhongMaterial";
+			else if ( shading === "basic" ) mtype = "MeshBasicMaterial";
+
+		}
+
+		if ( m.blending !== undefined && THREE[ m.blending ] !== undefined ) {
+
+			mpars.blending = THREE[ m.blending ];
+
+		}
+
+		if ( m.transparent !== undefined || m.opacity < 1.0 ) {
+
+			mpars.transparent = m.transparent;
+
+		}
+
+		if ( m.depthTest !== undefined ) {
+
+			mpars.depthTest = m.depthTest;
+
+		}
+
+		if ( m.depthWrite !== undefined ) {
+
+			mpars.depthWrite = m.depthWrite;
+
+		}
+
+		if ( m.visible !== undefined ) {
+
+			mpars.visible = m.visible;
+
+		}
+
+		if ( m.flipSided !== undefined ) {
+
+			mpars.side = THREE.BackSide;
+
+		}
+
+		if ( m.doubleSided !== undefined ) {
+
+			mpars.side = THREE.DoubleSide;
+
+		}
+
+		if ( m.wireframe !== undefined ) {
+
+			mpars.wireframe = m.wireframe;
+
+		}
+
+		if ( m.vertexColors !== undefined ) {
+
+			if ( m.vertexColors === "face" ) {
+
+				mpars.vertexColors = THREE.FaceColors;
+
+			} else if ( m.vertexColors ) {
+
+				mpars.vertexColors = THREE.VertexColors;
+
+			}
+
+		}
+
+		// colors
+
+		if ( m.colorDiffuse ) {
+
+			mpars.color = rgb2hex( m.colorDiffuse );
+
+		} else if ( m.DbgColor ) {
+
+			mpars.color = m.DbgColor;
+
+		}
+
+		if ( m.colorSpecular ) {
+
+			mpars.specular = rgb2hex( m.colorSpecular );
+
+		}
+
+		if ( m.colorAmbient ) {
+
+			mpars.ambient = rgb2hex( m.colorAmbient );
+
+		}
+
+		// modifiers
+
+		if ( m.transparency ) {
+
+			mpars.opacity = m.transparency;
+
+		}
+
+		if ( m.specularCoef ) {
+
+			mpars.shininess = m.specularCoef;
+
+		}
+
+		// textures
+
+		if ( m.mapDiffuse && texturePath ) {
+
+			create_texture( mpars, "map", m.mapDiffuse, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy );
+
+		}
+
+		if ( m.mapLight && texturePath ) {
+
+			create_texture( mpars, "lightMap", m.mapLight, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy );
+
+		}
+
+		if ( m.mapBump && texturePath ) {
+
+			create_texture( mpars, "bumpMap", m.mapBump, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy );
+
+		}
+
+		if ( m.mapNormal && texturePath ) {
+
+			create_texture( mpars, "normalMap", m.mapNormal, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy );
+
+		}
+
+		if ( m.mapSpecular && texturePath ) {
+
+			create_texture( mpars, "specularMap", m.mapSpecular, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy );
+
+		}
+
+		//
+
+		if ( m.mapBumpScale ) {
+
+			mpars.bumpScale = m.mapBumpScale;
+
+		}
+
+		// special case for normal mapped material
+
+		if ( m.mapNormal ) {
+
+			var shader = THREE.ShaderLib[ "normalmap" ];
+			var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+			uniforms[ "tNormal" ].value = mpars.normalMap;
+
+			if ( m.mapNormalFactor ) {
+
+				uniforms[ "uNormalScale" ].value.set( m.mapNormalFactor, m.mapNormalFactor );
+
+			}
+
+			if ( mpars.map ) {
+
+				uniforms[ "tDiffuse" ].value = mpars.map;
+				uniforms[ "enableDiffuse" ].value = true;
+
+			}
+
+			if ( mpars.specularMap ) {
+
+				uniforms[ "tSpecular" ].value = mpars.specularMap;
+				uniforms[ "enableSpecular" ].value = true;
+
+			}
+
+			if ( mpars.lightMap ) {
+
+				uniforms[ "tAO" ].value = mpars.lightMap;
+				uniforms[ "enableAO" ].value = true;
+
+			}
+
+			// for the moment don't handle displacement texture
+
+			uniforms[ "uDiffuseColor" ].value.setHex( mpars.color );
+			uniforms[ "uSpecularColor" ].value.setHex( mpars.specular );
+			uniforms[ "uAmbientColor" ].value.setHex( mpars.ambient );
+
+			uniforms[ "uShininess" ].value = mpars.shininess;
+
+			if ( mpars.opacity !== undefined ) {
+
+				uniforms[ "uOpacity" ].value = mpars.opacity;
+
+			}
+
+			var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: true };
+			var material = new THREE.ShaderMaterial( parameters );
+
+			if ( mpars.transparent ) {
+
+				material.transparent = true;
+
+			}
+
+		} else {
+
+			var material = new THREE[ mtype ]( mpars );
+
+		}
+
+		if ( m.DbgName !== undefined ) material.name = m.DbgName;
+
+		return material;
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.ImageLoader = function () {
+
+	this.crossOrigin = null;
+
+};
+
+THREE.ImageLoader.prototype = {
+
+	constructor: THREE.ImageLoader,
+
+	addEventListener: THREE.EventDispatcher.prototype.addEventListener,
+	hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
+	removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
+	dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent,
+
+	load: function ( url, image ) {
+
+		var scope = this;
+
+		if ( image === undefined ) image = new Image();
+
+		image.addEventListener( 'load', function () {
+
+			scope.dispatchEvent( { type: 'load', content: image } );
+
+		}, false );
+
+		image.addEventListener( 'error', function () {
+
+			scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );
+
+		}, false );
+
+		if ( scope.crossOrigin ) image.crossOrigin = scope.crossOrigin;
+
+		image.src = url;
+
+	}
+
+}
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.JSONLoader = function ( showStatus ) {
+
+	THREE.Loader.call( this, showStatus );
+
+	this.withCredentials = false;
+
+};
+
+THREE.JSONLoader.prototype = Object.create( THREE.Loader.prototype );
+
+THREE.JSONLoader.prototype.load = function ( url, callback, texturePath ) {
+
+	var scope = this;
+
+	// todo: unify load API to for easier SceneLoader use
+
+	texturePath = texturePath && ( typeof texturePath === "string" ) ? texturePath : this.extractUrlBase( url );
+
+	this.onLoadStart();
+	this.loadAjaxJSON( this, url, callback, texturePath );
+
+};
+
+THREE.JSONLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, callbackProgress ) {
+
+	var xhr = new XMLHttpRequest();
+
+	var length = 0;
+
+	xhr.onreadystatechange = function () {
+
+		if ( xhr.readyState === xhr.DONE ) {
+
+			if ( xhr.status === 200 || xhr.status === 0 ) {
+
+				if ( xhr.responseText ) {
+
+					var json = JSON.parse( xhr.responseText );
+					var result = context.parse( json, texturePath );
+					callback( result.geometry, result.materials );
+
+				} else {
+
+					console.warn( "THREE.JSONLoader: [" + url + "] seems to be unreachable or file there is empty" );
+
+				}
+
+				// in context of more complex asset initialization
+				// do not block on single failed file
+				// maybe should go even one more level up
+
+				context.onLoadComplete();
+
+			} else {
+
+				console.error( "THREE.JSONLoader: Couldn't load [" + url + "] [" + xhr.status + "]" );
+
+			}
+
+		} else if ( xhr.readyState === xhr.LOADING ) {
+
+			if ( callbackProgress ) {
+
+				if ( length === 0 ) {
+
+					length = xhr.getResponseHeader( "Content-Length" );
+
+				}
+
+				callbackProgress( { total: length, loaded: xhr.responseText.length } );
+
+			}
+
+		} else if ( xhr.readyState === xhr.HEADERS_RECEIVED ) {
+
+			if ( callbackProgress !== undefined ) {
+
+				length = xhr.getResponseHeader( "Content-Length" );
+
+			}
+
+		}
+
+	};
+
+	xhr.open( "GET", url, true );
+	xhr.withCredentials = this.withCredentials;
+	xhr.send( null );
+
+};
+
+THREE.JSONLoader.prototype.parse = function ( json, texturePath ) {
+
+	var scope = this,
+	geometry = new THREE.Geometry(),
+	scale = ( json.scale !== undefined ) ? 1.0 / json.scale : 1.0;
+
+	parseModel( scale );
+
+	parseSkin();
+	parseMorphing( scale );
+
+	geometry.computeCentroids();
+	geometry.computeFaceNormals();
+
+	function parseModel( scale ) {
+
+		function isBitSet( value, position ) {
+
+			return value & ( 1 << position );
+
+		}
+
+		var i, j, fi,
+
+		offset, zLength, nVertices,
+
+		colorIndex, normalIndex, uvIndex, materialIndex,
+
+		type,
+		isQuad,
+		hasMaterial,
+		hasFaceUv, hasFaceVertexUv,
+		hasFaceNormal, hasFaceVertexNormal,
+		hasFaceColor, hasFaceVertexColor,
+
+		vertex, face, color, normal,
+
+		uvLayer, uvs, u, v,
+
+		faces = json.faces,
+		vertices = json.vertices,
+		normals = json.normals,
+		colors = json.colors,
+
+		nUvLayers = 0;
+
+		// disregard empty arrays
+
+		for ( i = 0; i < json.uvs.length; i++ ) {
+
+			if ( json.uvs[ i ].length ) nUvLayers ++;
+
+		}
+
+		for ( i = 0; i < nUvLayers; i++ ) {
+
+			geometry.faceUvs[ i ] = [];
+			geometry.faceVertexUvs[ i ] = [];
+
+		}
+
+		offset = 0;
+		zLength = vertices.length;
+
+		while ( offset < zLength ) {
+
+			vertex = new THREE.Vector3();
+
+			vertex.x = vertices[ offset ++ ] * scale;
+			vertex.y = vertices[ offset ++ ] * scale;
+			vertex.z = vertices[ offset ++ ] * scale;
+
+			geometry.vertices.push( vertex );
+
+		}
+
+		offset = 0;
+		zLength = faces.length;
+
+		while ( offset < zLength ) {
+
+			type = faces[ offset ++ ];
+
+
+			isQuad              = isBitSet( type, 0 );
+			hasMaterial         = isBitSet( type, 1 );
+			hasFaceUv           = isBitSet( type, 2 );
+			hasFaceVertexUv     = isBitSet( type, 3 );
+			hasFaceNormal       = isBitSet( type, 4 );
+			hasFaceVertexNormal = isBitSet( type, 5 );
+			hasFaceColor	    = isBitSet( type, 6 );
+			hasFaceVertexColor  = isBitSet( type, 7 );
+
+			//console.log("type", type, "bits", isQuad, hasMaterial, hasFaceUv, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor);
+
+			if ( isQuad ) {
+
+				face = new THREE.Face4();
+
+				face.a = faces[ offset ++ ];
+				face.b = faces[ offset ++ ];
+				face.c = faces[ offset ++ ];
+				face.d = faces[ offset ++ ];
+
+				nVertices = 4;
+
+			} else {
+
+				face = new THREE.Face3();
+
+				face.a = faces[ offset ++ ];
+				face.b = faces[ offset ++ ];
+				face.c = faces[ offset ++ ];
+
+				nVertices = 3;
+
+			}
+
+			if ( hasMaterial ) {
+
+				materialIndex = faces[ offset ++ ];
+				face.materialIndex = materialIndex;
+
+			}
+
+			// to get face <=> uv index correspondence
+
+			fi = geometry.faces.length;
+
+			if ( hasFaceUv ) {
+
+				for ( i = 0; i < nUvLayers; i++ ) {
+
+					uvLayer = json.uvs[ i ];
+
+					uvIndex = faces[ offset ++ ];
+
+					u = uvLayer[ uvIndex * 2 ];
+					v = uvLayer[ uvIndex * 2 + 1 ];
+
+					geometry.faceUvs[ i ][ fi ] = new THREE.Vector2( u, v );
+
+				}
+
+			}
+
+			if ( hasFaceVertexUv ) {
+
+				for ( i = 0; i < nUvLayers; i++ ) {
+
+					uvLayer = json.uvs[ i ];
+
+					uvs = [];
+
+					for ( j = 0; j < nVertices; j ++ ) {
+
+						uvIndex = faces[ offset ++ ];
+
+						u = uvLayer[ uvIndex * 2 ];
+						v = uvLayer[ uvIndex * 2 + 1 ];
+
+						uvs[ j ] = new THREE.Vector2( u, v );
+
+					}
+
+					geometry.faceVertexUvs[ i ][ fi ] = uvs;
+
+				}
+
+			}
+
+			if ( hasFaceNormal ) {
+
+				normalIndex = faces[ offset ++ ] * 3;
+
+				normal = new THREE.Vector3();
+
+				normal.x = normals[ normalIndex ++ ];
+				normal.y = normals[ normalIndex ++ ];
+				normal.z = normals[ normalIndex ];
+
+				face.normal = normal;
+
+			}
+
+			if ( hasFaceVertexNormal ) {
+
+				for ( i = 0; i < nVertices; i++ ) {
+
+					normalIndex = faces[ offset ++ ] * 3;
+
+					normal = new THREE.Vector3();
+
+					normal.x = normals[ normalIndex ++ ];
+					normal.y = normals[ normalIndex ++ ];
+					normal.z = normals[ normalIndex ];
+
+					face.vertexNormals.push( normal );
+
+				}
+
+			}
+
+
+			if ( hasFaceColor ) {
+
+				colorIndex = faces[ offset ++ ];
+
+				color = new THREE.Color( colors[ colorIndex ] );
+				face.color = color;
+
+			}
+
+
+			if ( hasFaceVertexColor ) {
+
+				for ( i = 0; i < nVertices; i++ ) {
+
+					colorIndex = faces[ offset ++ ];
+
+					color = new THREE.Color( colors[ colorIndex ] );
+					face.vertexColors.push( color );
+
+				}
+
+			}
+
+			geometry.faces.push( face );
+
+		}
+
+	};
+
+	function parseSkin() {
+
+		var i, l, x, y, z, w, a, b, c, d;
+
+		if ( json.skinWeights ) {
+
+			for ( i = 0, l = json.skinWeights.length; i < l; i += 2 ) {
+
+				x = json.skinWeights[ i     ];
+				y = json.skinWeights[ i + 1 ];
+				z = 0;
+				w = 0;
+
+				geometry.skinWeights.push( new THREE.Vector4( x, y, z, w ) );
+
+			}
+
+		}
+
+		if ( json.skinIndices ) {
+
+			for ( i = 0, l = json.skinIndices.length; i < l; i += 2 ) {
+
+				a = json.skinIndices[ i     ];
+				b = json.skinIndices[ i + 1 ];
+				c = 0;
+				d = 0;
+
+				geometry.skinIndices.push( new THREE.Vector4( a, b, c, d ) );
+
+			}
+
+		}
+
+		geometry.bones = json.bones;
+		geometry.animation = json.animation;
+
+	};
+
+	function parseMorphing( scale ) {
+
+		if ( json.morphTargets !== undefined ) {
+
+			var i, l, v, vl, dstVertices, srcVertices;
+
+			for ( i = 0, l = json.morphTargets.length; i < l; i ++ ) {
+
+				geometry.morphTargets[ i ] = {};
+				geometry.morphTargets[ i ].name = json.morphTargets[ i ].name;
+				geometry.morphTargets[ i ].vertices = [];
+
+				dstVertices = geometry.morphTargets[ i ].vertices;
+				srcVertices = json.morphTargets [ i ].vertices;
+
+				for( v = 0, vl = srcVertices.length; v < vl; v += 3 ) {
+
+					var vertex = new THREE.Vector3();
+					vertex.x = srcVertices[ v ] * scale;
+					vertex.y = srcVertices[ v + 1 ] * scale;
+					vertex.z = srcVertices[ v + 2 ] * scale;
+
+					dstVertices.push( vertex );
+
+				}
+
+			}
+
+		}
+
+		if ( json.morphColors !== undefined ) {
+
+			var i, l, c, cl, dstColors, srcColors, color;
+
+			for ( i = 0, l = json.morphColors.length; i < l; i++ ) {
+
+				geometry.morphColors[ i ] = {};
+				geometry.morphColors[ i ].name = json.morphColors[ i ].name;
+				geometry.morphColors[ i ].colors = [];
+
+				dstColors = geometry.morphColors[ i ].colors;
+				srcColors = json.morphColors [ i ].colors;
+
+				for ( c = 0, cl = srcColors.length; c < cl; c += 3 ) {
+
+					color = new THREE.Color( 0xffaa00 );
+					color.setRGB( srcColors[ c ], srcColors[ c + 1 ], srcColors[ c + 2 ] );
+					dstColors.push( color );
+
+				}
+
+			}
+
+		}
+
+	};
+
+	if ( json.materials === undefined ) {
+
+		return { geometry: geometry };
+
+	} else {
+
+		var materials = this.initMaterials( json.materials, texturePath );
+
+		if ( this.needsTangents( materials ) ) {
+
+			geometry.computeTangents();
+
+		}
+
+		return { geometry: geometry, materials: materials };
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.LoadingMonitor = function () {
+
+	var scope = this;
+
+	var loaded = 0;
+	var total = 0;
+
+	var onLoad = function ( event ) {
+
+		loaded ++;
+
+		scope.dispatchEvent( { type: 'progress', loaded: loaded, total: total } );
+
+		if ( loaded === total ) {
+
+			scope.dispatchEvent( { type: 'load' } );
+
+		}
+
+	};
+
+	this.add = function ( loader ) {
+
+		total ++;
+
+		loader.addEventListener( 'load', onLoad, false );
+
+	};
+
+};
+
+THREE.LoadingMonitor.prototype = {
+
+	constructor: THREE.LoadingMonitor,
+
+	addEventListener: THREE.EventDispatcher.prototype.addEventListener,
+	hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
+	removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
+	dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.GeometryLoader = function () {};
+THREE.GeometryLoader.prototype = {
+
+	constructor: THREE.GeometryLoader,
+
+	addEventListener: THREE.EventDispatcher.prototype.addEventListener,
+	hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
+	removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
+	dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent,
+
+	load: function ( url ) {
+
+		var scope = this;
+		var request = new XMLHttpRequest();
+
+		request.addEventListener( 'load', function ( event ) {
+
+			var response = scope.parse( JSON.parse( event.target.responseText ) );
+
+			scope.dispatchEvent( { type: 'load', content: response } );
+
+		}, false );
+
+		request.addEventListener( 'progress', function ( event ) {
+
+			scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );
+
+		}, false );
+
+		request.addEventListener( 'error', function () {
+
+			scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );
+
+		}, false );
+
+		request.open( 'GET', url, true );
+		request.send( null );
+
+	},
+
+	parse: function ( json ) {
+
+		
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.MaterialLoader = function () {};
+
+THREE.MaterialLoader.prototype = {
+
+	constructor: THREE.MaterialLoader,
+
+	addEventListener: THREE.EventDispatcher.prototype.addEventListener,
+	hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
+	removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
+	dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent,
+
+	load: function ( url ) {
+
+		var scope = this;
+		var request = new XMLHttpRequest();
+
+		request.addEventListener( 'load', function ( event ) {
+
+			var response = scope.parse( JSON.parse( event.target.responseText ) );
+
+			scope.dispatchEvent( { type: 'load', content: response } );
+
+		}, false );
+
+		request.addEventListener( 'progress', function ( event ) {
+
+			scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );
+
+		}, false );
+
+		request.addEventListener( 'error', function () {
+
+			scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );
+
+		}, false );
+
+		request.open( 'GET', url, true );
+		request.send( null );
+
+	},
+
+	parse: function ( json ) {
+
+		var material;
+
+		switch ( json.type ) {
+
+			case 'MeshBasicMaterial':
+
+				material = new THREE.MeshBasicMaterial( {
+
+					color: json.color,
+					opacity: json.opacity,
+					transparent: json.transparent,
+					wireframe: json.wireframe
+
+				} );
+
+				break;
+
+			case 'MeshLambertMaterial':
+
+				material = new THREE.MeshLambertMaterial( {
+
+					color: json.color,
+					ambient: json.ambient,
+					emissive: json.emissive,
+					opacity: json.opacity,
+					transparent: json.transparent,
+					wireframe: json.wireframe
+
+				} );
+
+				break;
+
+			case 'MeshPhongMaterial':
+
+				material = new THREE.MeshPhongMaterial( {
+
+					color: json.color,
+					ambient: json.ambient,
+					emissive: json.emissive,
+					specular: json.specular,
+					shininess: json.shininess,
+					opacity: json.opacity,
+					transparent: json.transparent,
+					wireframe: json.wireframe
+
+				} );
+
+				break;
+
+			case 'MeshNormalMaterial':
+
+				material = new THREE.MeshNormalMaterial( {
+
+					opacity: json.opacity,
+					transparent: json.transparent,
+					wireframe: json.wireframe
+
+				} );
+
+				break;
+
+			case 'MeshDepthMaterial':
+
+				material = new THREE.MeshDepthMaterial( {
+
+					opacity: json.opacity,
+					transparent: json.transparent,
+					wireframe: json.wireframe
+
+				} );
+
+				break;
+
+		}
+
+		return material;
+
+	}
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SceneLoader = function () {
+
+	this.onLoadStart = function () {};
+	this.onLoadProgress = function() {};
+	this.onLoadComplete = function () {};
+
+	this.callbackSync = function () {};
+	this.callbackProgress = function () {};
+
+	this.geometryHandlerMap = {};
+	this.hierarchyHandlerMap = {};
+
+	this.addGeometryHandler( "ascii", THREE.JSONLoader );
+
+};
+
+THREE.SceneLoader.prototype.constructor = THREE.SceneLoader;
+
+THREE.SceneLoader.prototype.load = function ( url, callbackFinished ) {
+
+	var scope = this;
+
+	var xhr = new XMLHttpRequest();
+
+	xhr.onreadystatechange = function () {
+
+		if ( xhr.readyState === 4 ) {
+
+			if ( xhr.status === 200 || xhr.status === 0 ) {
+
+				var json = JSON.parse( xhr.responseText );
+				scope.parse( json, callbackFinished, url );
+
+			} else {
+
+				console.error( "THREE.SceneLoader: Couldn't load [" + url + "] [" + xhr.status + "]" );
+
+			}
+
+		}
+
+	};
+
+	xhr.open( "GET", url, true );
+	xhr.send( null );
+
+};
+
+THREE.SceneLoader.prototype.addGeometryHandler = function ( typeID, loaderClass ) {
+
+	this.geometryHandlerMap[ typeID ] = { "loaderClass": loaderClass };
+
+};
+
+THREE.SceneLoader.prototype.addHierarchyHandler = function ( typeID, loaderClass ) {
+
+	this.hierarchyHandlerMap[ typeID ] = { "loaderClass": loaderClass };
+
+};
+
+THREE.SceneLoader.prototype.parse = function ( json, callbackFinished, url ) {
+
+	var scope = this;
+
+	var urlBase = THREE.Loader.prototype.extractUrlBase( url );
+
+	var geometry, material, camera, fog,
+		texture, images, color,
+		light, hex, intensity,
+		counter_models, counter_textures,
+		total_models, total_textures,
+		result;
+
+	var target_array = [];
+
+	var data = json;
+
+	// async geometry loaders
+
+	for ( var typeID in this.geometryHandlerMap ) {
+
+		var loaderClass = this.geometryHandlerMap[ typeID ][ "loaderClass" ];
+		this.geometryHandlerMap[ typeID ][ "loaderObject" ] = new loaderClass();
+
+	}
+
+	// async hierachy loaders
+
+	for ( var typeID in this.hierarchyHandlerMap ) {
+
+		var loaderClass = this.hierarchyHandlerMap[ typeID ][ "loaderClass" ];
+		this.hierarchyHandlerMap[ typeID ][ "loaderObject" ] = new loaderClass();
+
+	}
+
+	counter_models = 0;
+	counter_textures = 0;
+
+	result = {
+
+		scene: new THREE.Scene(),
+		geometries: {},
+		face_materials: {},
+		materials: {},
+		textures: {},
+		objects: {},
+		cameras: {},
+		lights: {},
+		fogs: {},
+		empties: {},
+		groups: {}
+
+	};
+
+	if ( data.transform ) {
+
+		var position = data.transform.position,
+			rotation = data.transform.rotation,
+			scale = data.transform.scale;
+
+		if ( position )
+			result.scene.position.set( position[ 0 ], position[ 1 ], position [ 2 ] );
+
+		if ( rotation )
+			result.scene.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation [ 2 ] );
+
+		if ( scale )
+			result.scene.scale.set( scale[ 0 ], scale[ 1 ], scale [ 2 ] );
+
+		if ( position || rotation || scale ) {
+
+			result.scene.updateMatrix();
+			result.scene.updateMatrixWorld();
+
+		}
+
+	}
+
+	function get_url( source_url, url_type ) {
+
+		if ( url_type == "relativeToHTML" ) {
+
+			return source_url;
+
+		} else {
+
+			return urlBase + "/" + source_url;
+
+		}
+
+	};
+
+	// toplevel loader function, delegates to handle_children
+
+	function handle_objects() {
+
+		handle_children( result.scene, data.objects );
+
+	}
+
+	// handle all the children from the loaded json and attach them to given parent
+
+	function handle_children( parent, children ) {
+
+		var mat, dst, pos, rot, scl, quat;
+
+		for ( var objID in children ) {
+
+			// check by id if child has already been handled,
+			// if not, create new object
+
+			if ( result.objects[ objID ] === undefined ) {
+
+				var objJSON = children[ objID ];
+
+				var object = null;
+
+				// meshes
+
+				if ( objJSON.type && ( objJSON.type in scope.hierarchyHandlerMap ) ) {
+
+					if ( objJSON.loading === undefined ) {
+
+						var reservedTypes = { "type": 1, "url": 1, "material": 1,
+											  "position": 1, "rotation": 1, "scale" : 1,
+											  "visible": 1, "children": 1, "userData": 1,
+											  "skin": 1, "morph": 1, "mirroredLoop": 1, "duration": 1 };
+
+						var loaderParameters = {};
+
+						for ( var parType in objJSON ) {
+
+							if ( ! ( parType in reservedTypes ) ) {
+
+								loaderParameters[ parType ] = objJSON[ parType ];
+
+							}
+
+						}
+
+						material = result.materials[ objJSON.material ];
+
+						objJSON.loading = true;
+
+						var loader = scope.hierarchyHandlerMap[ objJSON.type ][ "loaderObject" ];
+
+						// ColladaLoader
+
+						if ( loader.options ) {
+
+							loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ) );
+
+						// UTF8Loader
+						// OBJLoader
+
+						} else {
+
+							loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ), loaderParameters );
+
+						}
+
+					}
+
+				} else if ( objJSON.geometry !== undefined ) {
+
+					geometry = result.geometries[ objJSON.geometry ];
+
+					// geometry already loaded
+
+					if ( geometry ) {
+
+						var needsTangents = false;
+
+						material = result.materials[ objJSON.material ];
+						needsTangents = material instanceof THREE.ShaderMaterial;
+
+						pos = objJSON.position;
+						rot = objJSON.rotation;
+						scl = objJSON.scale;
+						mat = objJSON.matrix;
+						quat = objJSON.quaternion;
+
+						// use materials from the model file
+						// if there is no material specified in the object
+
+						if ( ! objJSON.material ) {
+
+							material = new THREE.MeshFaceMaterial( result.face_materials[ objJSON.geometry ] );
+
+						}
+
+						// use materials from the model file
+						// if there is just empty face material
+						// (must create new material as each model has its own face material)
+
+						if ( ( material instanceof THREE.MeshFaceMaterial ) && material.materials.length === 0 ) {
+
+							material = new THREE.MeshFaceMaterial( result.face_materials[ objJSON.geometry ] );
+
+						}
+
+						if ( material instanceof THREE.MeshFaceMaterial ) {
+
+							for ( var i = 0; i < material.materials.length; i ++ ) {
+
+								needsTangents = needsTangents || ( material.materials[ i ] instanceof THREE.ShaderMaterial );
+
+							}
+
+						}
+
+						if ( needsTangents ) {
+
+							geometry.computeTangents();
+
+						}
+
+						if ( objJSON.skin ) {
+
+							object = new THREE.SkinnedMesh( geometry, material );
+
+						} else if ( objJSON.morph ) {
+
+							object = new THREE.MorphAnimMesh( geometry, material );
+
+							if ( objJSON.duration !== undefined ) {
+
+								object.duration = objJSON.duration;
+
+							}
+
+							if ( objJSON.time !== undefined ) {
+
+								object.time = objJSON.time;
+
+							}
+
+							if ( objJSON.mirroredLoop !== undefined ) {
+
+								object.mirroredLoop = objJSON.mirroredLoop;
+
+							}
+
+							if ( material.morphNormals ) {
+
+								geometry.computeMorphNormals();
+
+							}
+
+						} else {
+
+							object = new THREE.Mesh( geometry, material );
+
+						}
+
+						object.name = objID;
+
+						if ( mat ) {
+
+							object.matrixAutoUpdate = false;
+							object.matrix.set(
+								mat[0],  mat[1],  mat[2],  mat[3],
+								mat[4],  mat[5],  mat[6],  mat[7],
+								mat[8],  mat[9],  mat[10], mat[11],
+								mat[12], mat[13], mat[14], mat[15]
+							);
+
+						} else {
+
+							object.position.set( pos[0], pos[1], pos[2] );
+
+							if ( quat ) {
+
+								object.quaternion.set( quat[0], quat[1], quat[2], quat[3] );
+								object.useQuaternion = true;
+
+							} else {
+
+								object.rotation.set( rot[0], rot[1], rot[2] );
+
+							}
+
+							object.scale.set( scl[0], scl[1], scl[2] );
+
+						}
+
+						object.visible = objJSON.visible;
+						object.castShadow = objJSON.castShadow;
+						object.receiveShadow = objJSON.receiveShadow;
+
+						parent.add( object );
+
+						result.objects[ objID ] = object;
+
+					}
+
+				// lights
+
+				} else if ( objJSON.type === "DirectionalLight" || objJSON.type === "PointLight" || objJSON.type === "AmbientLight" ) {
+
+					hex = ( objJSON.color !== undefined ) ? objJSON.color : 0xffffff;
+					intensity = ( objJSON.intensity !== undefined ) ? objJSON.intensity : 1;
+
+					if ( objJSON.type === "DirectionalLight" ) {
+
+						pos = objJSON.direction;
+
+						light = new THREE.DirectionalLight( hex, intensity );
+						light.position.set( pos[0], pos[1], pos[2] );
+
+						if ( objJSON.target ) {
+
+							target_array.push( { "object": light, "targetName" : objJSON.target } );
+
+							// kill existing default target
+							// otherwise it gets added to scene when parent gets added
+
+							light.target = null;
+
+						}
+
+					} else if ( objJSON.type === "PointLight" ) {
+
+						pos = objJSON.position;
+						dst = objJSON.distance;
+
+						light = new THREE.PointLight( hex, intensity, dst );
+						light.position.set( pos[0], pos[1], pos[2] );
+
+					} else if ( objJSON.type === "AmbientLight" ) {
+
+						light = new THREE.AmbientLight( hex );
+
+					}
+
+					parent.add( light );
+
+					light.name = objID;
+					result.lights[ objID ] = light;
+					result.objects[ objID ] = light;
+
+				// cameras
+
+				} else if ( objJSON.type === "PerspectiveCamera" || objJSON.type === "OrthographicCamera" ) {
+
+					pos = objJSON.position;
+					rot = objJSON.rotation;
+					quat = objJSON.quaternion;
+
+					if ( objJSON.type === "PerspectiveCamera" ) {
+
+						camera = new THREE.PerspectiveCamera( objJSON.fov, objJSON.aspect, objJSON.near, objJSON.far );
+
+					} else if ( objJSON.type === "OrthographicCamera" ) {
+
+						camera = new THREE.OrthographicCamera( objJSON.left, objJSON.right, objJSON.top, objJSON.bottom, objJSON.near, objJSON.far );
+
+					}
+
+					camera.name = objID;
+					camera.position.set( pos[0], pos[1], pos[2] );
+
+					if ( quat !== undefined ) {
+
+						camera.quaternion.set( quat[0], quat[1], quat[2], quat[3] );
+						camera.useQuaternion = true;
+
+					} else if ( rot !== undefined ) {
+
+						camera.rotation.set( rot[0], rot[1], rot[2] );
+
+					}
+
+					parent.add( camera );
+
+					result.cameras[ objID ] = camera;
+					result.objects[ objID ] = camera;
+
+				// pure Object3D
+
+				} else {
+
+					pos = objJSON.position;
+					rot = objJSON.rotation;
+					scl = objJSON.scale;
+					quat = objJSON.quaternion;
+
+					object = new THREE.Object3D();
+					object.name = objID;
+					object.position.set( pos[0], pos[1], pos[2] );
+
+					if ( quat ) {
+
+						object.quaternion.set( quat[0], quat[1], quat[2], quat[3] );
+						object.useQuaternion = true;
+
+					} else {
+
+						object.rotation.set( rot[0], rot[1], rot[2] );
+
+					}
+
+					object.scale.set( scl[0], scl[1], scl[2] );
+					object.visible = ( objJSON.visible !== undefined ) ? objJSON.visible : false;
+
+					parent.add( object );
+
+					result.objects[ objID ] = object;
+					result.empties[ objID ] = object;
+
+				}
+
+				if ( object ) {
+
+					if ( objJSON.userData !== undefined )  {
+
+						for ( var key in objJSON.userData ) {
+
+							var value = objJSON.userData[ key ];
+							object.userData[ key ] = value;
+
+						}
+
+					}
+
+					if ( objJSON.groups !== undefined ) {
+
+						for ( var i = 0; i < objJSON.groups.length; i ++ ) {
+
+							var groupID = objJSON.groups[ i ];
+
+							if ( result.groups[ groupID ] === undefined ) {
+
+								result.groups[ groupID ] = [];
+
+							}
+
+							result.groups[ groupID ].push( objID );
+
+						}
+
+					}
+
+					if ( objJSON.children !== undefined ) {
+
+						handle_children( object, objJSON.children );
+
+					}
+
+				}
+
+			}
+
+		}
+
+	};
+
+	function handle_mesh( geo, mat, id ) {
+
+		result.geometries[ id ] = geo;
+		result.face_materials[ id ] = mat;
+		handle_objects();
+
+	};
+
+	function handle_hierarchy( node, id, parent, material, obj ) {
+
+		var p = obj.position;
+		var r = obj.rotation;
+		var q = obj.quaternion;
+		var s = obj.scale;
+
+		node.position.set( p[0], p[1], p[2] );
+
+		if ( q ) {
+
+			node.quaternion.set( q[0], q[1], q[2], q[3] );
+			node.useQuaternion = true;
+
+		} else {
+
+			node.rotation.set( r[0], r[1], r[2] );
+
+		}
+
+		node.scale.set( s[0], s[1], s[2] );
+
+		// override children materials
+		// if object material was specified in JSON explicitly
+
+		if ( material ) {
+
+			node.traverse( function ( child )  {
+
+				child.material = material;
+
+			} );
+
+		}
+
+		// override children visibility
+		// with root node visibility as specified in JSON
+
+		var visible = ( obj.visible !== undefined ) ? obj.visible : true;
+
+		node.traverse( function ( child )  {
+
+			child.visible = visible;
+
+		} );
+
+		parent.add( node );
+
+		node.name = id;
+
+		result.objects[ id ] = node;
+		handle_objects();
+
+	};
+
+	function create_callback_geometry( id ) {
+
+		return function ( geo, mat ) {
+
+			geo.name = id;
+
+			handle_mesh( geo, mat, id );
+
+			counter_models -= 1;
+
+			scope.onLoadComplete();
+
+			async_callback_gate();
+
+		}
+
+	};
+
+	function create_callback_hierachy( id, parent, material, obj ) {
+
+		return function ( event ) {
+
+			var result;
+
+			// loaders which use EventDispatcher
+
+			if ( event.content ) {
+
+				result = event.content;
+
+			// ColladaLoader
+
+			} else if ( event.dae ) {
+
+				result = event.scene;
+
+
+			// UTF8Loader
+
+			} else {
+
+				result = event;
+
+			}
+
+			handle_hierarchy( result, id, parent, material, obj );
+
+			counter_models -= 1;
+
+			scope.onLoadComplete();
+
+			async_callback_gate();
+
+		}
+
+	};
+
+	function create_callback_embed( id ) {
+
+		return function ( geo, mat ) {
+
+			geo.name = id;
+
+			result.geometries[ id ] = geo;
+			result.face_materials[ id ] = mat;
+
+		}
+
+	};
+
+	function async_callback_gate() {
+
+		var progress = {
+
+			totalModels : total_models,
+			totalTextures : total_textures,
+			loadedModels : total_models - counter_models,
+			loadedTextures : total_textures - counter_textures
+
+		};
+
+		scope.callbackProgress( progress, result );
+
+		scope.onLoadProgress();
+
+		if ( counter_models === 0 && counter_textures === 0 ) {
+
+			finalize();
+			callbackFinished( result );
+
+		}
+
+	};
+
+	function finalize() {
+
+		// take care of targets which could be asynchronously loaded objects
+
+		for ( var i = 0; i < target_array.length; i ++ ) {
+
+			var ta = target_array[ i ];
+
+			var target = result.objects[ ta.targetName ];
+
+			if ( target ) {
+
+				ta.object.target = target;
+
+			} else {
+
+				// if there was error and target of specified name doesn't exist in the scene file
+				// create instead dummy target
+				// (target must be added to scene explicitly as parent is already added)
+
+				ta.object.target = new THREE.Object3D();
+				result.scene.add( ta.object.target );
+
+			}
+
+			ta.object.target.userData.targetInverse = ta.object;
+
+		}
+
+	};
+
+	var callbackTexture = function ( count ) {
+
+		counter_textures -= count;
+		async_callback_gate();
+
+		scope.onLoadComplete();
+
+	};
+
+	// must use this instead of just directly calling callbackTexture
+	// because of closure in the calling context loop
+
+	var generateTextureCallback = function ( count ) {
+
+		return function () {
+
+			callbackTexture( count );
+
+		};
+
+	};
+
+	// first go synchronous elements
+
+	// fogs
+
+	var fogID, fogJSON;
+
+	for ( fogID in data.fogs ) {
+
+		fogJSON = data.fogs[ fogID ];
+
+		if ( fogJSON.type === "linear" ) {
+
+			fog = new THREE.Fog( 0x000000, fogJSON.near, fogJSON.far );
+
+		} else if ( fogJSON.type === "exp2" ) {
+
+			fog = new THREE.FogExp2( 0x000000, fogJSON.density );
+
+		}
+
+		color = fogJSON.color;
+		fog.color.setRGB( color[0], color[1], color[2] );
+
+		result.fogs[ fogID ] = fog;
+
+	}
+
+	// now come potentially asynchronous elements
+
+	// geometries
+
+	// count how many geometries will be loaded asynchronously
+
+	var geoID, geoJSON;
+
+	for ( geoID in data.geometries ) {
+
+		geoJSON = data.geometries[ geoID ];
+
+		if ( geoJSON.type in this.geometryHandlerMap ) {
+
+			counter_models += 1;
+
+			scope.onLoadStart();
+
+		}
+
+	}
+
+	// count how many hierarchies will be loaded asynchronously
+
+	var objID, objJSON;
+
+	for ( objID in data.objects ) {
+
+		objJSON = data.objects[ objID ];
+
+		if ( objJSON.type && ( objJSON.type in this.hierarchyHandlerMap ) ) {
+
+			counter_models += 1;
+
+			scope.onLoadStart();
+
+		}
+
+	}
+
+	total_models = counter_models;
+
+	for ( geoID in data.geometries ) {
+
+		geoJSON = data.geometries[ geoID ];
+
+		if ( geoJSON.type === "cube" ) {
+
+			geometry = new THREE.CubeGeometry( geoJSON.width, geoJSON.height, geoJSON.depth, geoJSON.widthSegments, geoJSON.heightSegments, geoJSON.depthSegments );
+			geometry.name = geoID;
+			result.geometries[ geoID ] = geometry;
+
+		} else if ( geoJSON.type === "plane" ) {
+
+			geometry = new THREE.PlaneGeometry( geoJSON.width, geoJSON.height, geoJSON.widthSegments, geoJSON.heightSegments );
+			geometry.name = geoID;
+			result.geometries[ geoID ] = geometry;
+
+		} else if ( geoJSON.type === "sphere" ) {
+
+			geometry = new THREE.SphereGeometry( geoJSON.radius, geoJSON.widthSegments, geoJSON.heightSegments );
+			geometry.name = geoID;
+			result.geometries[ geoID ] = geometry;
+
+		} else if ( geoJSON.type === "cylinder" ) {
+
+			geometry = new THREE.CylinderGeometry( geoJSON.topRad, geoJSON.botRad, geoJSON.height, geoJSON.radSegs, geoJSON.heightSegs );
+			geometry.name = geoID;
+			result.geometries[ geoID ] = geometry;
+
+		} else if ( geoJSON.type === "torus" ) {
+
+			geometry = new THREE.TorusGeometry( geoJSON.radius, geoJSON.tube, geoJSON.segmentsR, geoJSON.segmentsT );
+			geometry.name = geoID;
+			result.geometries[ geoID ] = geometry;
+
+		} else if ( geoJSON.type === "icosahedron" ) {
+
+			geometry = new THREE.IcosahedronGeometry( geoJSON.radius, geoJSON.subdivisions );
+			geometry.name = geoID;
+			result.geometries[ geoID ] = geometry;
+
+		} else if ( geoJSON.type in this.geometryHandlerMap ) {
+
+			var loaderParameters = {};
+
+			for ( var parType in geoJSON ) {
+
+				if ( parType !== "type" && parType !== "url" ) {
+
+					loaderParameters[ parType ] = geoJSON[ parType ];
+
+				}
+
+			}
+
+			var loader = this.geometryHandlerMap[ geoJSON.type ][ "loaderObject" ];
+			loader.load( get_url( geoJSON.url, data.urlBaseType ), create_callback_geometry( geoID ), loaderParameters );
+
+		} else if ( geoJSON.type === "embedded" ) {
+
+			var modelJson = data.embeds[ geoJSON.id ],
+				texture_path = "";
+
+			// pass metadata along to jsonLoader so it knows the format version
+
+			modelJson.metadata = data.metadata;
+
+			if ( modelJson ) {
+
+				var jsonLoader = this.geometryHandlerMap[ "ascii" ][ "loaderObject" ];
+				var model = jsonLoader.parse( modelJson, texture_path );
+				create_callback_embed( geoID )( model.geometry, model.materials );
+
+			}
+
+		}
+
+	}
+
+	// textures
+
+	// count how many textures will be loaded asynchronously
+
+	var textureID, textureJSON;
+
+	for ( textureID in data.textures ) {
+
+		textureJSON = data.textures[ textureID ];
+
+		if ( textureJSON.url instanceof Array ) {
+
+			counter_textures += textureJSON.url.length;
+
+			for( var n = 0; n < textureJSON.url.length; n ++ ) {
+
+				scope.onLoadStart();
+
+			}
+
+		} else {
+
+			counter_textures += 1;
+
+			scope.onLoadStart();
+
+		}
+
+	}
+
+	total_textures = counter_textures;
+
+	for ( textureID in data.textures ) {
+
+		textureJSON = data.textures[ textureID ];
+
+		if ( textureJSON.mapping !== undefined && THREE[ textureJSON.mapping ] !== undefined  ) {
+
+			textureJSON.mapping = new THREE[ textureJSON.mapping ]();
+
+		}
+
+		if ( textureJSON.url instanceof Array ) {
+
+			var count = textureJSON.url.length;
+			var url_array = [];
+
+			for( var i = 0; i < count; i ++ ) {
+
+				url_array[ i ] = get_url( textureJSON.url[ i ], data.urlBaseType );
+
+			}
+
+			var isCompressed = /\.dds$/i.test( url_array[ 0 ] );
+
+			if ( isCompressed ) {
+
+				texture = THREE.ImageUtils.loadCompressedTextureCube( url_array, textureJSON.mapping, generateTextureCallback( count ) );
+
+			} else {
+
+				texture = THREE.ImageUtils.loadTextureCube( url_array, textureJSON.mapping, generateTextureCallback( count ) );
+
+			}
+
+		} else {
+
+			var isCompressed = /\.dds$/i.test( textureJSON.url );
+			var fullUrl = get_url( textureJSON.url, data.urlBaseType );
+			var textureCallback = generateTextureCallback( 1 );
+
+			if ( isCompressed ) {
+
+				texture = THREE.ImageUtils.loadCompressedTexture( fullUrl, textureJSON.mapping, textureCallback );
+
+			} else {
+
+				texture = THREE.ImageUtils.loadTexture( fullUrl, textureJSON.mapping, textureCallback );
+
+			}
+
+			if ( THREE[ textureJSON.minFilter ] !== undefined )
+				texture.minFilter = THREE[ textureJSON.minFilter ];
+
+			if ( THREE[ textureJSON.magFilter ] !== undefined )
+				texture.magFilter = THREE[ textureJSON.magFilter ];
+
+			if ( textureJSON.anisotropy ) texture.anisotropy = textureJSON.anisotropy;
+
+			if ( textureJSON.repeat ) {
+
+				texture.repeat.set( textureJSON.repeat[ 0 ], textureJSON.repeat[ 1 ] );
+
+				if ( textureJSON.repeat[ 0 ] !== 1 ) texture.wrapS = THREE.RepeatWrapping;
+				if ( textureJSON.repeat[ 1 ] !== 1 ) texture.wrapT = THREE.RepeatWrapping;
+
+			}
+
+			if ( textureJSON.offset ) {
+
+				texture.offset.set( textureJSON.offset[ 0 ], textureJSON.offset[ 1 ] );
+
+			}
+
+			// handle wrap after repeat so that default repeat can be overriden
+
+			if ( textureJSON.wrap ) {
+
+				var wrapMap = {
+				"repeat" 	: THREE.RepeatWrapping,
+				"mirror"	: THREE.MirroredRepeatWrapping
+				}
+
+				if ( wrapMap[ textureJSON.wrap[ 0 ] ] !== undefined ) texture.wrapS = wrapMap[ textureJSON.wrap[ 0 ] ];
+				if ( wrapMap[ textureJSON.wrap[ 1 ] ] !== undefined ) texture.wrapT = wrapMap[ textureJSON.wrap[ 1 ] ];
+
+			}
+
+		}
+
+		result.textures[ textureID ] = texture;
+
+	}
+
+	// materials
+
+	var matID, matJSON;
+	var parID;
+
+	for ( matID in data.materials ) {
+
+		matJSON = data.materials[ matID ];
+
+		for ( parID in matJSON.parameters ) {
+
+			if ( parID === "envMap" || parID === "map" || parID === "lightMap" || parID === "bumpMap" ) {
+
+				matJSON.parameters[ parID ] = result.textures[ matJSON.parameters[ parID ] ];
+
+			} else if ( parID === "shading" ) {
+
+				matJSON.parameters[ parID ] = ( matJSON.parameters[ parID ] === "flat" ) ? THREE.FlatShading : THREE.SmoothShading;
+
+			} else if ( parID === "side" ) {
+
+				if ( matJSON.parameters[ parID ] == "double" ) {
+
+					matJSON.parameters[ parID ] = THREE.DoubleSide;
+
+				} else if ( matJSON.parameters[ parID ] == "back" ) {
+
+					matJSON.parameters[ parID ] = THREE.BackSide;
+
+				} else {
+
+					matJSON.parameters[ parID ] = THREE.FrontSide;
+
+				}
+
+			} else if ( parID === "blending" ) {
+
+				matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.NormalBlending;
+
+			} else if ( parID === "combine" ) {
+
+				matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.MultiplyOperation;
+
+			} else if ( parID === "vertexColors" ) {
+
+				if ( matJSON.parameters[ parID ] == "face" ) {
+
+					matJSON.parameters[ parID ] = THREE.FaceColors;
+
+				// default to vertex colors if "vertexColors" is anything else face colors or 0 / null / false
+
+				} else if ( matJSON.parameters[ parID ] )   {
+
+					matJSON.parameters[ parID ] = THREE.VertexColors;
+
+				}
+
+			} else if ( parID === "wrapRGB" ) {
+
+				var v3 = matJSON.parameters[ parID ];
+				matJSON.parameters[ parID ] = new THREE.Vector3( v3[ 0 ], v3[ 1 ], v3[ 2 ] );
+
+			}
+
+		}
+
+		if ( matJSON.parameters.opacity !== undefined && matJSON.parameters.opacity < 1.0 ) {
+
+			matJSON.parameters.transparent = true;
+
+		}
+
+		if ( matJSON.parameters.normalMap ) {
+
+			var shader = THREE.ShaderLib[ "normalmap" ];
+			var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+			var diffuse = matJSON.parameters.color;
+			var specular = matJSON.parameters.specular;
+			var ambient = matJSON.parameters.ambient;
+			var shininess = matJSON.parameters.shininess;
+
+			uniforms[ "tNormal" ].value = result.textures[ matJSON.parameters.normalMap ];
+
+			if ( matJSON.parameters.normalScale ) {
+
+				uniforms[ "uNormalScale" ].value.set( matJSON.parameters.normalScale[ 0 ], matJSON.parameters.normalScale[ 1 ] );
+
+			}
+
+			if ( matJSON.parameters.map ) {
+
+				uniforms[ "tDiffuse" ].value = matJSON.parameters.map;
+				uniforms[ "enableDiffuse" ].value = true;
+
+			}
+
+			if ( matJSON.parameters.envMap ) {
+
+				uniforms[ "tCube" ].value = matJSON.parameters.envMap;
+				uniforms[ "enableReflection" ].value = true;
+				uniforms[ "uReflectivity" ].value = matJSON.parameters.reflectivity;
+
+			}
+
+			if ( matJSON.parameters.lightMap ) {
+
+				uniforms[ "tAO" ].value = matJSON.parameters.lightMap;
+				uniforms[ "enableAO" ].value = true;
+
+			}
+
+			if ( matJSON.parameters.specularMap ) {
+
+				uniforms[ "tSpecular" ].value = result.textures[ matJSON.parameters.specularMap ];
+				uniforms[ "enableSpecular" ].value = true;
+
+			}
+
+			if ( matJSON.parameters.displacementMap ) {
+
+				uniforms[ "tDisplacement" ].value = result.textures[ matJSON.parameters.displacementMap ];
+				uniforms[ "enableDisplacement" ].value = true;
+
+				uniforms[ "uDisplacementBias" ].value = matJSON.parameters.displacementBias;
+				uniforms[ "uDisplacementScale" ].value = matJSON.parameters.displacementScale;
+
+			}
+
+			uniforms[ "uDiffuseColor" ].value.setHex( diffuse );
+			uniforms[ "uSpecularColor" ].value.setHex( specular );
+			uniforms[ "uAmbientColor" ].value.setHex( ambient );
+
+			uniforms[ "uShininess" ].value = shininess;
+
+			if ( matJSON.parameters.opacity ) {
+
+				uniforms[ "uOpacity" ].value = matJSON.parameters.opacity;
+
+			}
+
+			var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: true };
+
+			material = new THREE.ShaderMaterial( parameters );
+
+		} else {
+
+			material = new THREE[ matJSON.type ]( matJSON.parameters );
+
+		}
+
+		material.name = matID;
+
+		result.materials[ matID ] = material;
+
+	}
+
+	// second pass through all materials to initialize MeshFaceMaterials
+	// that could be referring to other materials out of order
+
+	for ( matID in data.materials ) {
+
+		matJSON = data.materials[ matID ];
+
+		if ( matJSON.parameters.materials ) {
+
+			var materialArray = [];
+
+			for ( var i = 0; i < matJSON.parameters.materials.length; i ++ ) {
+
+				var label = matJSON.parameters.materials[ i ];
+				materialArray.push( result.materials[ label ] );
+
+			}
+
+			result.materials[ matID ].materials = materialArray;
+
+		}
+
+	}
+
+	// objects ( synchronous init of procedural primitives )
+
+	handle_objects();
+
+	// defaults
+
+	if ( result.cameras && data.defaults.camera ) {
+
+		result.currentCamera = result.cameras[ data.defaults.camera ];
+
+	}
+
+	if ( result.fogs && data.defaults.fog ) {
+
+		result.scene.fog = result.fogs[ data.defaults.fog ];
+
+	}
+
+	// synchronous callback
+
+	scope.callbackSync( result );
+
+	// just in case there are no async elements
+
+	async_callback_gate();
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.TextureLoader = function () {
+
+	this.crossOrigin = null;
+
+};
+
+THREE.TextureLoader.prototype = {
+
+	constructor: THREE.TextureLoader,
+
+	addEventListener: THREE.EventDispatcher.prototype.addEventListener,
+	hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
+	removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
+	dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent,
+
+	load: function ( url ) {
+
+		var scope = this;
+
+		var image = new Image();
+
+		image.addEventListener( 'load', function () {
+
+			var texture = new THREE.Texture( image );
+			texture.needsUpdate = true;
+
+			scope.dispatchEvent( { type: 'load', content: texture } );
+
+		}, false );
+
+		image.addEventListener( 'error', function () {
+
+			scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );
+
+		}, false );
+
+		if ( scope.crossOrigin ) image.crossOrigin = scope.crossOrigin;
+
+		image.src = url;
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Material = function () {
+
+	this.id = THREE.MaterialIdCount ++;
+
+	this.name = '';
+
+	this.side = THREE.FrontSide;
+
+	this.opacity = 1;
+	this.transparent = false;
+
+	this.blending = THREE.NormalBlending;
+
+	this.blendSrc = THREE.SrcAlphaFactor;
+	this.blendDst = THREE.OneMinusSrcAlphaFactor;
+	this.blendEquation = THREE.AddEquation;
+
+	this.depthTest = true;
+	this.depthWrite = true;
+
+	this.polygonOffset = false;
+	this.polygonOffsetFactor = 0;
+	this.polygonOffsetUnits = 0;
+
+	this.alphaTest = 0;
+
+	this.overdraw = false; // Boolean for fixing antialiasing gaps in CanvasRenderer
+
+	this.visible = true;
+
+	this.needsUpdate = true;
+
+};
+
+THREE.Material.prototype = {
+
+	constructor: THREE.Material,
+
+	addEventListener: THREE.EventDispatcher.prototype.addEventListener,
+	hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
+	removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
+	dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent,
+
+	setValues: function ( values ) {
+
+		if ( values === undefined ) return;
+
+		for ( var key in values ) {
+
+			var newValue = values[ key ];
+
+			if ( newValue === undefined ) {
+
+				console.warn( 'THREE.Material: \'' + key + '\' parameter is undefined.' );
+				continue;
+
+			}
+
+			if ( key in this ) {
+
+				var currentValue = this[ key ];
+
+				if ( currentValue instanceof THREE.Color ) {
+
+					currentValue.set( newValue );
+
+				} else if ( currentValue instanceof THREE.Vector3 && newValue instanceof THREE.Vector3 ) {
+
+					currentValue.copy( newValue );
+
+				} else {
+
+					this[ key ] = newValue;
+
+				}
+
+			}
+
+		}
+
+	},
+
+	clone: function ( material ) {
+
+		if ( material === undefined ) material = new THREE.Material();
+
+		material.name = this.name;
+
+		material.side = this.side;
+
+		material.opacity = this.opacity;
+		material.transparent = this.transparent;
+
+		material.blending = this.blending;
+
+		material.blendSrc = this.blendSrc;
+		material.blendDst = this.blendDst;
+		material.blendEquation = this.blendEquation;
+
+		material.depthTest = this.depthTest;
+		material.depthWrite = this.depthWrite;
+
+		material.polygonOffset = this.polygonOffset;
+		material.polygonOffsetFactor = this.polygonOffsetFactor;
+		material.polygonOffsetUnits = this.polygonOffsetUnits;
+
+		material.alphaTest = this.alphaTest;
+
+		material.overdraw = this.overdraw;
+
+		material.visible = this.visible;
+
+		return material;
+
+	},
+
+	dispose: function () {
+
+		this.dispatchEvent( { type: 'dispose' } );
+
+	}
+
+};
+
+THREE.MaterialIdCount = 0;
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  linewidth: <float>,
+ *  linecap: "round",
+ *  linejoin: "round",
+ *
+ *  vertexColors: <bool>
+ *
+ *  fog: <bool>
+ * }
+ */
+
+THREE.LineBasicMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.color = new THREE.Color( 0xffffff );
+
+	this.linewidth = 1;
+	this.linecap = 'round';
+	this.linejoin = 'round';
+
+	this.vertexColors = false;
+
+	this.fog = true;
+
+	this.setValues( parameters );
+
+};
+
+THREE.LineBasicMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.LineBasicMaterial.prototype.clone = function () {
+
+	var material = new THREE.LineBasicMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.color.copy( this.color );
+
+	material.linewidth = this.linewidth;
+	material.linecap = this.linecap;
+	material.linejoin = this.linejoin;
+
+	material.vertexColors = this.vertexColors;
+
+	material.fog = this.fog;
+
+	return material;
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  linewidth: <float>,
+ *
+ *  scale: <float>,
+ *  dashSize: <float>,
+ *  gapSize: <float>,
+ *
+ *  vertexColors: <bool>
+ *
+ *  fog: <bool>
+ * }
+ */
+
+THREE.LineDashedMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.color = new THREE.Color( 0xffffff );
+
+	this.linewidth = 1;
+
+	this.scale = 1;
+	this.dashSize = 3;
+	this.gapSize = 1;
+
+	this.vertexColors = false;
+
+	this.fog = true;
+
+	this.setValues( parameters );
+
+};
+
+THREE.LineDashedMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.LineDashedMaterial.prototype.clone = function () {
+
+	var material = new THREE.LineDashedMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.color.copy( this.color );
+
+	material.linewidth = this.linewidth;
+
+	material.scale = this.scale;
+	material.dashSize = this.dashSize;
+	material.gapSize = this.gapSize;
+
+	material.vertexColors = this.vertexColors;
+
+	material.fog = this.fog;
+
+	return material;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *
+ *  fog: <bool>
+ * }
+ */
+
+THREE.MeshBasicMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.color = new THREE.Color( 0xffffff ); // emissive
+
+	this.map = null;
+
+	this.lightMap = null;
+
+	this.specularMap = null;
+
+	this.envMap = null;
+	this.combine = THREE.MultiplyOperation;
+	this.reflectivity = 1;
+	this.refractionRatio = 0.98;
+
+	this.fog = true;
+
+	this.shading = THREE.SmoothShading;
+
+	this.wireframe = false;
+	this.wireframeLinewidth = 1;
+	this.wireframeLinecap = 'round';
+	this.wireframeLinejoin = 'round';
+
+	this.vertexColors = THREE.NoColors;
+
+	this.skinning = false;
+	this.morphTargets = false;
+
+	this.setValues( parameters );
+
+};
+
+THREE.MeshBasicMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshBasicMaterial.prototype.clone = function () {
+
+	var material = new THREE.MeshBasicMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.color.copy( this.color );
+
+	material.map = this.map;
+
+	material.lightMap = this.lightMap;
+
+	material.specularMap = this.specularMap;
+
+	material.envMap = this.envMap;
+	material.combine = this.combine;
+	material.reflectivity = this.reflectivity;
+	material.refractionRatio = this.refractionRatio;
+
+	material.fog = this.fog;
+
+	material.shading = this.shading;
+
+	material.wireframe = this.wireframe;
+	material.wireframeLinewidth = this.wireframeLinewidth;
+	material.wireframeLinecap = this.wireframeLinecap;
+	material.wireframeLinejoin = this.wireframeLinejoin;
+
+	material.vertexColors = this.vertexColors;
+
+	material.skinning = this.skinning;
+	material.morphTargets = this.morphTargets;
+
+	return material;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  ambient: <hex>,
+ *  emissive: <hex>,
+ *  opacity: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>,
+ *
+ *	fog: <bool>
+ * }
+ */
+
+THREE.MeshLambertMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.color = new THREE.Color( 0xffffff ); // diffuse
+	this.ambient = new THREE.Color( 0xffffff );
+	this.emissive = new THREE.Color( 0x000000 );
+
+	this.wrapAround = false;
+	this.wrapRGB = new THREE.Vector3( 1, 1, 1 );
+
+	this.map = null;
+
+	this.lightMap = null;
+
+	this.specularMap = null;
+
+	this.envMap = null;
+	this.combine = THREE.MultiplyOperation;
+	this.reflectivity = 1;
+	this.refractionRatio = 0.98;
+
+	this.fog = true;
+
+	this.shading = THREE.SmoothShading;
+
+	this.wireframe = false;
+	this.wireframeLinewidth = 1;
+	this.wireframeLinecap = 'round';
+	this.wireframeLinejoin = 'round';
+
+	this.vertexColors = THREE.NoColors;
+
+	this.skinning = false;
+	this.morphTargets = false;
+	this.morphNormals = false;
+
+	this.setValues( parameters );
+
+};
+
+THREE.MeshLambertMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshLambertMaterial.prototype.clone = function () {
+
+	var material = new THREE.MeshLambertMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.color.copy( this.color );
+	material.ambient.copy( this.ambient );
+	material.emissive.copy( this.emissive );
+
+	material.wrapAround = this.wrapAround;
+	material.wrapRGB.copy( this.wrapRGB );
+
+	material.map = this.map;
+
+	material.lightMap = this.lightMap;
+
+	material.specularMap = this.specularMap;
+
+	material.envMap = this.envMap;
+	material.combine = this.combine;
+	material.reflectivity = this.reflectivity;
+	material.refractionRatio = this.refractionRatio;
+
+	material.fog = this.fog;
+
+	material.shading = this.shading;
+
+	material.wireframe = this.wireframe;
+	material.wireframeLinewidth = this.wireframeLinewidth;
+	material.wireframeLinecap = this.wireframeLinecap;
+	material.wireframeLinejoin = this.wireframeLinejoin;
+
+	material.vertexColors = this.vertexColors;
+
+	material.skinning = this.skinning;
+	material.morphTargets = this.morphTargets;
+	material.morphNormals = this.morphNormals;
+
+	return material;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  ambient: <hex>,
+ *  emissive: <hex>,
+ *  specular: <hex>,
+ *  shininess: <float>,
+ *  opacity: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *
+ *  bumpMap: new THREE.Texture( <Image> ),
+ *  bumpScale: <float>,
+ *
+ *  normalMap: new THREE.Texture( <Image> ),
+ *  normalScale: <Vector2>,
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>,
+ *
+ *	fog: <bool>
+ * }
+ */
+
+THREE.MeshPhongMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.color = new THREE.Color( 0xffffff ); // diffuse
+	this.ambient = new THREE.Color( 0xffffff );
+	this.emissive = new THREE.Color( 0x000000 );
+	this.specular = new THREE.Color( 0x111111 );
+	this.shininess = 30;
+
+	this.metal = false;
+	this.perPixel = true;
+
+	this.wrapAround = false;
+	this.wrapRGB = new THREE.Vector3( 1, 1, 1 );
+
+	this.map = null;
+
+	this.lightMap = null;
+
+	this.bumpMap = null;
+	this.bumpScale = 1;
+
+	this.normalMap = null;
+	this.normalScale = new THREE.Vector2( 1, 1 );
+
+	this.specularMap = null;
+
+	this.envMap = null;
+	this.combine = THREE.MultiplyOperation;
+	this.reflectivity = 1;
+	this.refractionRatio = 0.98;
+
+	this.fog = true;
+
+	this.shading = THREE.SmoothShading;
+
+	this.wireframe = false;
+	this.wireframeLinewidth = 1;
+	this.wireframeLinecap = 'round';
+	this.wireframeLinejoin = 'round';
+
+	this.vertexColors = THREE.NoColors;
+
+	this.skinning = false;
+	this.morphTargets = false;
+	this.morphNormals = false;
+
+	this.setValues( parameters );
+
+};
+
+THREE.MeshPhongMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshPhongMaterial.prototype.clone = function () {
+
+	var material = new THREE.MeshPhongMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.color.copy( this.color );
+	material.ambient.copy( this.ambient );
+	material.emissive.copy( this.emissive );
+	material.specular.copy( this.specular );
+	material.shininess = this.shininess;
+
+	material.metal = this.metal;
+	material.perPixel = this.perPixel;
+
+	material.wrapAround = this.wrapAround;
+	material.wrapRGB.copy( this.wrapRGB );
+
+	material.map = this.map;
+
+	material.lightMap = this.lightMap;
+
+	material.bumpMap = this.bumpMap;
+	material.bumpScale = this.bumpScale;
+
+	material.normalMap = this.normalMap;
+	material.normalScale.copy( this.normalScale );
+
+	material.specularMap = this.specularMap;
+
+	material.envMap = this.envMap;
+	material.combine = this.combine;
+	material.reflectivity = this.reflectivity;
+	material.refractionRatio = this.refractionRatio;
+
+	material.fog = this.fog;
+
+	material.shading = this.shading;
+
+	material.wireframe = this.wireframe;
+	material.wireframeLinewidth = this.wireframeLinewidth;
+	material.wireframeLinecap = this.wireframeLinecap;
+	material.wireframeLinejoin = this.wireframeLinejoin;
+
+	material.vertexColors = this.vertexColors;
+
+	material.skinning = this.skinning;
+	material.morphTargets = this.morphTargets;
+	material.morphNormals = this.morphNormals;
+
+	return material;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  opacity: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>
+ * }
+ */
+
+THREE.MeshDepthMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.wireframe = false;
+	this.wireframeLinewidth = 1;
+
+	this.setValues( parameters );
+
+};
+
+THREE.MeshDepthMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshDepthMaterial.prototype.clone = function () {
+
+	var material = new THREE.MeshDepthMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.wireframe = this.wireframe;
+	material.wireframeLinewidth = this.wireframeLinewidth;
+
+	return material;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ *
+ * parameters = {
+ *  opacity: <float>,
+ *
+ *  shading: THREE.FlatShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>
+ * }
+ */
+
+THREE.MeshNormalMaterial = function ( parameters ) {
+
+	THREE.Material.call( this, parameters );
+
+	this.shading = THREE.FlatShading;
+
+	this.wireframe = false;
+	this.wireframeLinewidth = 1;
+
+	this.morphTargets = false;
+
+	this.setValues( parameters );
+
+};
+
+THREE.MeshNormalMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshNormalMaterial.prototype.clone = function () {
+
+	var material = new THREE.MeshNormalMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.shading = this.shading;
+
+	material.wireframe = this.wireframe;
+	material.wireframeLinewidth = this.wireframeLinewidth;
+
+	return material;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.MeshFaceMaterial = function ( materials ) {
+
+	this.materials = materials instanceof Array ? materials : [];
+
+};
+
+THREE.MeshFaceMaterial.prototype.clone = function () {
+
+	return new THREE.MeshFaceMaterial( this.materials.slice( 0 ) );
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  size: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  vertexColors: <bool>,
+ *
+ *  fog: <bool>
+ * }
+ */
+
+THREE.ParticleBasicMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.color = new THREE.Color( 0xffffff );
+
+	this.map = null;
+
+	this.size = 1;
+	this.sizeAttenuation = true;
+
+	this.vertexColors = false;
+
+	this.fog = true;
+
+	this.setValues( parameters );
+
+};
+
+THREE.ParticleBasicMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ParticleBasicMaterial.prototype.clone = function () {
+
+	var material = new THREE.ParticleBasicMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.color.copy( this.color );
+
+	material.map = this.map;
+
+	material.size = this.size;
+	material.sizeAttenuation = this.sizeAttenuation;
+
+	material.vertexColors = this.vertexColors;
+
+	material.fog = this.fog;
+
+	return material;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  program: <function>,
+ *  opacity: <float>,
+ *  blending: THREE.NormalBlending
+ * }
+ */
+
+THREE.ParticleCanvasMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.color = new THREE.Color( 0xffffff );
+	this.program = function ( context, color ) {};
+
+	this.setValues( parameters );
+
+};
+
+THREE.ParticleCanvasMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ParticleCanvasMaterial.prototype.clone = function () {
+
+	var material = new THREE.ParticleCanvasMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.color.copy( this.color );
+	material.program = this.program;
+
+	return material;
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  fragmentShader: <string>,
+ *  vertexShader: <string>,
+ *
+ *  uniforms: { "parameter1": { type: "f", value: 1.0 }, "parameter2": { type: "i" value2: 2 } },
+ *
+ *  defines: { "label" : "value" },
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  lights: <bool>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>,
+ *
+ *	fog: <bool>
+ * }
+ */
+
+THREE.ShaderMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.fragmentShader = "void main() {}";
+	this.vertexShader = "void main() {}";
+	this.uniforms = {};
+	this.defines = {};
+	this.attributes = null;
+
+	this.shading = THREE.SmoothShading;
+
+	this.linewidth = 1;
+
+	this.wireframe = false;
+	this.wireframeLinewidth = 1;
+
+	this.fog = false; // set to use scene fog
+
+	this.lights = false; // set to use scene lights
+
+	this.vertexColors = THREE.NoColors; // set to use "color" attribute stream
+
+	this.skinning = false; // set to use skinning attribute streams
+
+	this.morphTargets = false; // set to use morph targets
+	this.morphNormals = false; // set to use morph normals
+
+	this.setValues( parameters );
+
+};
+
+THREE.ShaderMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ShaderMaterial.prototype.clone = function () {
+
+	var material = new THREE.ShaderMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.fragmentShader = this.fragmentShader;
+	material.vertexShader = this.vertexShader;
+
+	material.uniforms = THREE.UniformsUtils.clone( this.uniforms );
+
+	material.attributes = this.attributes;
+	material.defines = this.defines;
+
+	material.shading = this.shading;
+
+	material.wireframe = this.wireframe;
+	material.wireframeLinewidth = this.wireframeLinewidth;
+
+	material.fog = this.fog;
+
+	material.lights = this.lights;
+
+	material.vertexColors = this.vertexColors;
+
+	material.skinning = this.skinning;
+
+	material.morphTargets = this.morphTargets;
+	material.morphNormals = this.morphNormals;
+
+	return material;
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  useScreenCoordinates: <bool>,
+ *  sizeAttenuation: <bool>,
+ *  scaleByViewport: <bool>,
+ *  alignment: THREE.SpriteAlignment.center,
+ *
+ *	uvOffset: new THREE.Vector2(),
+ *	uvScale: new THREE.Vector2(),
+ *
+ *  fog: <bool>
+ * }
+ */
+
+THREE.SpriteMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	// defaults
+
+	this.color = new THREE.Color( 0xffffff );
+	this.map = new THREE.Texture();
+
+	this.useScreenCoordinates = true;
+	this.depthTest = !this.useScreenCoordinates;
+	this.sizeAttenuation = !this.useScreenCoordinates;
+	this.scaleByViewport = !this.sizeAttenuation;
+	this.alignment = THREE.SpriteAlignment.center.clone();
+
+	this.fog = false;
+
+	this.uvOffset = new THREE.Vector2( 0, 0 );
+	this.uvScale  = new THREE.Vector2( 1, 1 );
+
+	// set parameters
+
+	this.setValues( parameters );
+
+	// override coupled defaults if not specified explicitly by parameters
+
+	parameters = parameters || {};
+
+	if ( parameters.depthTest === undefined ) this.depthTest = !this.useScreenCoordinates;
+	if ( parameters.sizeAttenuation === undefined ) this.sizeAttenuation = !this.useScreenCoordinates;
+	if ( parameters.scaleByViewport === undefined ) this.scaleByViewport = !this.sizeAttenuation;
+
+};
+
+THREE.SpriteMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.SpriteMaterial.prototype.clone = function () {
+
+	var material = new THREE.SpriteMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.color.copy( this.color );
+	material.map = this.map;
+
+	material.useScreenCoordinates = this.useScreenCoordinates;
+	material.sizeAttenuation = this.sizeAttenuation;
+	material.scaleByViewport = this.scaleByViewport;
+	material.alignment.copy( this.alignment );
+
+	material.uvOffset.copy( this.uvOffset );
+	material.uvScale.copy( this.uvScale );
+
+	material.fog = this.fog;
+
+	return material;
+
+};
+
+// Alignment enums
+
+THREE.SpriteAlignment = {};
+THREE.SpriteAlignment.topLeft = new THREE.Vector2( 1, -1 );
+THREE.SpriteAlignment.topCenter = new THREE.Vector2( 0, -1 );
+THREE.SpriteAlignment.topRight = new THREE.Vector2( -1, -1 );
+THREE.SpriteAlignment.centerLeft = new THREE.Vector2( 1, 0 );
+THREE.SpriteAlignment.center = new THREE.Vector2( 0, 0 );
+THREE.SpriteAlignment.centerRight = new THREE.Vector2( -1, 0 );
+THREE.SpriteAlignment.bottomLeft = new THREE.Vector2( 1, 1 );
+THREE.SpriteAlignment.bottomCenter = new THREE.Vector2( 0, 1 );
+THREE.SpriteAlignment.bottomRight = new THREE.Vector2( -1, 1 );
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author szimek / https://github.com/szimek/
+ */
+
+THREE.Texture = function ( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+
+	this.id = THREE.TextureIdCount ++;
+
+	this.name = '';
+
+	this.image = image;
+	this.mipmaps = [];
+
+	this.mapping = mapping !== undefined ? mapping : new THREE.UVMapping();
+
+	this.wrapS = wrapS !== undefined ? wrapS : THREE.ClampToEdgeWrapping;
+	this.wrapT = wrapT !== undefined ? wrapT : THREE.ClampToEdgeWrapping;
+
+	this.magFilter = magFilter !== undefined ? magFilter : THREE.LinearFilter;
+	this.minFilter = minFilter !== undefined ? minFilter : THREE.LinearMipMapLinearFilter;
+
+	this.anisotropy = anisotropy !== undefined ? anisotropy : 1;
+
+	this.format = format !== undefined ? format : THREE.RGBAFormat;
+	this.type = type !== undefined ? type : THREE.UnsignedByteType;
+
+	this.offset = new THREE.Vector2( 0, 0 );
+	this.repeat = new THREE.Vector2( 1, 1 );
+
+	this.generateMipmaps = true;
+	this.premultiplyAlpha = false;
+	this.flipY = true;
+	this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)
+
+	this.needsUpdate = false;
+	this.onUpdate = null;
+
+};
+
+THREE.Texture.prototype = {
+
+	constructor: THREE.Texture,
+
+	addEventListener: THREE.EventDispatcher.prototype.addEventListener,
+	hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
+	removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
+	dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent,
+
+	clone: function ( texture ) {
+
+		if ( texture === undefined ) texture = new THREE.Texture();
+
+		texture.image = this.image;
+		texture.mipmaps = this.mipmaps.slice(0);
+
+		texture.mapping = this.mapping;
+
+		texture.wrapS = this.wrapS;
+		texture.wrapT = this.wrapT;
+
+		texture.magFilter = this.magFilter;
+		texture.minFilter = this.minFilter;
+
+		texture.anisotropy = this.anisotropy;
+
+		texture.format = this.format;
+		texture.type = this.type;
+
+		texture.offset.copy( this.offset );
+		texture.repeat.copy( this.repeat );
+
+		texture.generateMipmaps = this.generateMipmaps;
+		texture.premultiplyAlpha = this.premultiplyAlpha;
+		texture.flipY = this.flipY;
+		texture.unpackAlignment = this.unpackAlignment;
+
+		return texture;
+
+	},
+
+	dispose: function () {
+
+		this.dispatchEvent( { type: 'dispose' } );
+
+	}
+
+};
+
+THREE.TextureIdCount = 0;
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.CompressedTexture = function ( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) {
+
+	THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+	this.image = { width: width, height: height };
+	this.mipmaps = mipmaps;
+
+	this.generateMipmaps = false; // WebGL currently can't generate mipmaps for compressed textures, they must be embedded in DDS file
+
+};
+
+THREE.CompressedTexture.prototype = Object.create( THREE.Texture.prototype );
+
+THREE.CompressedTexture.prototype.clone = function () {
+
+	var texture = new THREE.CompressedTexture();
+
+	THREE.Texture.prototype.clone.call( this, texture );
+
+	return texture;
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.DataTexture = function ( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) {
+
+	THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+	this.image = { data: data, width: width, height: height };
+
+};
+
+THREE.DataTexture.prototype = Object.create( THREE.Texture.prototype );
+
+THREE.DataTexture.prototype.clone = function () {
+
+	var texture = new THREE.DataTexture();
+
+	THREE.Texture.prototype.clone.call( this, texture );
+
+	return texture;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Particle = function ( material ) {
+
+	THREE.Object3D.call( this );
+
+	this.material = material;
+
+};
+
+THREE.Particle.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Particle.prototype.clone = function ( object ) {
+
+	if ( object === undefined ) object = new THREE.Particle( this.material );
+
+	THREE.Object3D.prototype.clone.call( this, object );
+
+	return object;
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.ParticleSystem = function ( geometry, material ) {
+
+	THREE.Object3D.call( this );
+
+	this.geometry = geometry;
+	this.material = ( material !== undefined ) ? material : new THREE.ParticleBasicMaterial( { color: Math.random() * 0xffffff } );
+
+	this.sortParticles = false;
+
+	if ( this.geometry ) {
+
+		// calc bound radius
+
+		if( this.geometry.boundingSphere === null ) {
+
+			this.geometry.computeBoundingSphere();
+
+		}
+
+	}
+
+	this.frustumCulled = false;
+
+};
+
+THREE.ParticleSystem.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.ParticleSystem.prototype.clone = function ( object ) {
+
+	if ( object === undefined ) object = new THREE.ParticleSystem( this.geometry, this.material );
+	object.sortParticles = this.sortParticles;
+
+	THREE.Object3D.prototype.clone.call( this, object );
+
+	return object;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Line = function ( geometry, material, type ) {
+
+	THREE.Object3D.call( this );
+
+	this.geometry = geometry;
+	this.material = ( material !== undefined ) ? material : new THREE.LineBasicMaterial( { color: Math.random() * 0xffffff } );
+	this.type = ( type !== undefined ) ? type : THREE.LineStrip;
+
+	if ( this.geometry ) {
+
+		if ( ! this.geometry.boundingSphere ) {
+
+			this.geometry.computeBoundingSphere();
+
+		}
+
+	}
+
+};
+
+THREE.LineStrip = 0;
+THREE.LinePieces = 1;
+
+THREE.Line.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Line.prototype.clone = function ( object ) {
+
+	if ( object === undefined ) object = new THREE.Line( this.geometry, this.material, this.type );
+
+	THREE.Object3D.prototype.clone.call( this, object );
+
+	return object;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author jonobr1 / http://jonobr1.com/
+ */
+
+THREE.Mesh = function ( geometry, material ) {
+
+	THREE.Object3D.call( this );
+
+	this.geometry = null;
+	this.material = null;
+
+	this.setGeometry( geometry );
+	this.setMaterial( material );
+
+};
+
+THREE.Mesh.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Mesh.prototype.setGeometry = function ( geometry ) {
+
+	if ( geometry !== undefined ) {
+
+		this.geometry = geometry;
+
+		if ( this.geometry.boundingSphere === null ) {
+
+			this.geometry.computeBoundingSphere();
+
+		}
+
+		this.updateMorphTargets();
+
+	}
+
+};
+
+THREE.Mesh.prototype.setMaterial = function ( material ) {
+
+	if ( material !== undefined ) {
+
+		this.material = material;
+
+	} else {
+
+		this.material = new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, wireframe: true } );
+
+	}
+
+};
+
+THREE.Mesh.prototype.updateMorphTargets = function () {
+
+	if ( this.geometry.morphTargets.length > 0 ) {
+
+		this.morphTargetBase = -1;
+		this.morphTargetForcedOrder = [];
+		this.morphTargetInfluences = [];
+		this.morphTargetDictionary = {};
+
+		for ( var m = 0, ml = this.geometry.morphTargets.length; m < ml; m ++ ) {
+
+			this.morphTargetInfluences.push( 0 );
+			this.morphTargetDictionary[ this.geometry.morphTargets[ m ].name ] = m;
+
+		}
+
+	}
+
+};
+
+THREE.Mesh.prototype.getMorphTargetIndexByName = function ( name ) {
+
+	if ( this.morphTargetDictionary[ name ] !== undefined ) {
+
+		return this.morphTargetDictionary[ name ];
+
+	}
+
+	console.log( "THREE.Mesh.getMorphTargetIndexByName: morph target " + name + " does not exist. Returning 0." );
+
+	return 0;
+
+};
+
+THREE.Mesh.prototype.clone = function ( object ) {
+
+	if ( object === undefined ) object = new THREE.Mesh( this.geometry, this.material );
+
+	THREE.Object3D.prototype.clone.call( this, object );
+
+	return object;
+
+};
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Bone = function( belongsToSkin ) {
+
+	THREE.Object3D.call( this );
+
+	this.skin = belongsToSkin;
+	this.skinMatrix = new THREE.Matrix4();
+
+};
+
+THREE.Bone.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Bone.prototype.update = function ( parentSkinMatrix, forceUpdate ) {
+
+	// update local
+
+	if ( this.matrixAutoUpdate ) {
+
+		forceUpdate |= this.updateMatrix();
+
+	}
+
+	// update skin matrix
+
+	if ( forceUpdate || this.matrixWorldNeedsUpdate ) {
+
+		if( parentSkinMatrix ) {
+
+			this.skinMatrix.multiplyMatrices( parentSkinMatrix, this.matrix );
+
+		} else {
+
+			this.skinMatrix.copy( this.matrix );
+
+		}
+
+		this.matrixWorldNeedsUpdate = false;
+		forceUpdate = true;
+
+	}
+
+	// update children
+
+	var child, i, l = this.children.length;
+
+	for ( i = 0; i < l; i ++ ) {
+
+		this.children[ i ].update( this.skinMatrix, forceUpdate );
+
+	}
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) {
+
+	THREE.Mesh.call( this, geometry, material );
+
+	//
+
+	this.useVertexTexture = useVertexTexture !== undefined ? useVertexTexture : true;
+
+	// init bones
+
+	this.identityMatrix = new THREE.Matrix4();
+
+	this.bones = [];
+	this.boneMatrices = [];
+
+	var b, bone, gbone, p, q, s;
+
+	if ( this.geometry && this.geometry.bones !== undefined ) {
+
+		for ( b = 0; b < this.geometry.bones.length; b ++ ) {
+
+			gbone = this.geometry.bones[ b ];
+
+			p = gbone.pos;
+			q = gbone.rotq;
+			s = gbone.scl;
+
+			bone = this.addBone();
+
+			bone.name = gbone.name;
+			bone.position.set( p[0], p[1], p[2] );
+			bone.quaternion.set( q[0], q[1], q[2], q[3] );
+			bone.useQuaternion = true;
+
+			if ( s !== undefined ) {
+
+				bone.scale.set( s[0], s[1], s[2] );
+
+			} else {
+
+				bone.scale.set( 1, 1, 1 );
+
+			}
+
+		}
+
+		for ( b = 0; b < this.bones.length; b ++ ) {
+
+			gbone = this.geometry.bones[ b ];
+			bone = this.bones[ b ];
+
+			if ( gbone.parent === -1 ) {
+
+				this.add( bone );
+
+			} else {
+
+				this.bones[ gbone.parent ].add( bone );
+
+			}
+
+		}
+
+		//
+
+		var nBones = this.bones.length;
+
+		if ( this.useVertexTexture ) {
+
+			// layout (1 matrix = 4 pixels)
+			//	RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
+			//  with  8x8  pixel texture max   16 bones  (8 * 8  / 4)
+			//  	 16x16 pixel texture max   64 bones (16 * 16 / 4)
+			//  	 32x32 pixel texture max  256 bones (32 * 32 / 4)
+			//  	 64x64 pixel texture max 1024 bones (64 * 64 / 4)
+
+			var size;
+
+			if ( nBones > 256 )
+				size = 64;
+			else if ( nBones > 64 )
+				size = 32;
+			else if ( nBones > 16 )
+				size = 16;
+			else
+				size = 8;
+
+			this.boneTextureWidth = size;
+			this.boneTextureHeight = size;
+
+			this.boneMatrices = new Float32Array( this.boneTextureWidth * this.boneTextureHeight * 4 ); // 4 floats per RGBA pixel
+			this.boneTexture = new THREE.DataTexture( this.boneMatrices, this.boneTextureWidth, this.boneTextureHeight, THREE.RGBAFormat, THREE.FloatType );
+			this.boneTexture.minFilter = THREE.NearestFilter;
+			this.boneTexture.magFilter = THREE.NearestFilter;
+			this.boneTexture.generateMipmaps = false;
+			this.boneTexture.flipY = false;
+
+		} else {
+
+			this.boneMatrices = new Float32Array( 16 * nBones );
+
+		}
+
+		this.pose();
+
+	}
+
+};
+
+THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype );
+
+THREE.SkinnedMesh.prototype.addBone = function( bone ) {
+
+	if ( bone === undefined ) {
+
+		bone = new THREE.Bone( this );
+
+	}
+
+	this.bones.push( bone );
+
+	return bone;
+
+};
+
+THREE.SkinnedMesh.prototype.updateMatrixWorld = function ( force ) {
+
+	this.matrixAutoUpdate && this.updateMatrix();
+
+	// update matrixWorld
+
+	if ( this.matrixWorldNeedsUpdate || force ) {
+
+		if ( this.parent ) {
+
+			this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+		} else {
+
+			this.matrixWorld.copy( this.matrix );
+
+		}
+
+		this.matrixWorldNeedsUpdate = false;
+
+		force = true;
+
+	}
+
+	// update children
+
+	for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+		var child = this.children[ i ];
+
+		if ( child instanceof THREE.Bone ) {
+
+			child.update( this.identityMatrix, false );
+
+		} else {
+
+			child.updateMatrixWorld( true );
+
+		}
+
+	}
+
+	// make a snapshot of the bones' rest position
+
+	if ( this.boneInverses == undefined ) {
+
+		this.boneInverses = [];
+
+		for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
+
+			var inverse = new THREE.Matrix4();
+
+			inverse.getInverse( this.bones[ b ].skinMatrix );
+
+			this.boneInverses.push( inverse );
+
+		}
+
+	}
+
+	// flatten bone matrices to array
+
+	for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
+
+		// compute the offset between the current and the original transform;
+
+		//TODO: we could get rid of this multiplication step if the skinMatrix
+		// was already representing the offset; however, this requires some
+		// major changes to the animation system
+
+		THREE.SkinnedMesh.offsetMatrix.multiplyMatrices( this.bones[ b ].skinMatrix, this.boneInverses[ b ] );
+
+		THREE.SkinnedMesh.offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 );
+
+	}
+
+	if ( this.useVertexTexture ) {
+
+		this.boneTexture.needsUpdate = true;
+
+	}
+
+};
+
+THREE.SkinnedMesh.prototype.pose = function () {
+
+	this.updateMatrixWorld( true );
+
+	for ( var i = 0; i < this.geometry.skinIndices.length; i ++ ) {
+
+		// normalize weights
+
+		var sw = this.geometry.skinWeights[ i ];
+
+		var scale = 1.0 / sw.lengthManhattan();
+
+		if ( scale !== Infinity ) {
+
+			sw.multiplyScalar( scale );
+
+		} else {
+
+			sw.set( 1 ); // this will be normalized by the shader anyway
+
+		}
+
+	}
+
+};
+
+THREE.SkinnedMesh.prototype.clone = function ( object ) {
+
+	if ( object === undefined ) object = new THREE.SkinnedMesh( this.geometry, this.material, this.useVertexTexture );
+
+	THREE.Mesh.prototype.clone.call( this, object );
+
+	return object;
+
+};
+
+THREE.SkinnedMesh.offsetMatrix = new THREE.Matrix4();
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.MorphAnimMesh = function ( geometry, material ) {
+
+	THREE.Mesh.call( this, geometry, material );
+
+	// API
+
+	this.duration = 1000; // milliseconds
+	this.mirroredLoop = false;
+	this.time = 0;
+
+	// internals
+
+	this.lastKeyframe = 0;
+	this.currentKeyframe = 0;
+
+	this.direction = 1;
+	this.directionBackwards = false;
+
+	this.setFrameRange( 0, this.geometry.morphTargets.length - 1 );
+
+};
+
+THREE.MorphAnimMesh.prototype = Object.create( THREE.Mesh.prototype );
+
+THREE.MorphAnimMesh.prototype.setFrameRange = function ( start, end ) {
+
+	this.startKeyframe = start;
+	this.endKeyframe = end;
+
+	this.length = this.endKeyframe - this.startKeyframe + 1;
+
+};
+
+THREE.MorphAnimMesh.prototype.setDirectionForward = function () {
+
+	this.direction = 1;
+	this.directionBackwards = false;
+
+};
+
+THREE.MorphAnimMesh.prototype.setDirectionBackward = function () {
+
+	this.direction = -1;
+	this.directionBackwards = true;
+
+};
+
+THREE.MorphAnimMesh.prototype.parseAnimations = function () {
+
+	var geometry = this.geometry;
+
+	if ( ! geometry.animations ) geometry.animations = {};
+
+	var firstAnimation, animations = geometry.animations;
+
+	var pattern = /([a-z]+)(\d+)/;
+
+	for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) {
+
+		var morph = geometry.morphTargets[ i ];
+		var parts = morph.name.match( pattern );
+
+		if ( parts && parts.length > 1 ) {
+
+			var label = parts[ 1 ];
+			var num = parts[ 2 ];
+
+			if ( ! animations[ label ] ) animations[ label ] = { start: Infinity, end: -Infinity };
+
+			var animation = animations[ label ];
+
+			if ( i < animation.start ) animation.start = i;
+			if ( i > animation.end ) animation.end = i;
+
+			if ( ! firstAnimation ) firstAnimation = label;
+
+		}
+
+	}
+
+	geometry.firstAnimation = firstAnimation;
+
+};
+
+THREE.MorphAnimMesh.prototype.setAnimationLabel = function ( label, start, end ) {
+
+	if ( ! this.geometry.animations ) this.geometry.animations = {};
+
+	this.geometry.animations[ label ] = { start: start, end: end };
+
+};
+
+THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
+
+	var animation = this.geometry.animations[ label ];
+
+	if ( animation ) {
+
+		this.setFrameRange( animation.start, animation.end );
+		this.duration = 1000 * ( ( animation.end - animation.start ) / fps );
+		this.time = 0;
+
+	} else {
+
+		console.warn( "animation[" + label + "] undefined" );
+
+	}
+
+};
+
+THREE.MorphAnimMesh.prototype.updateAnimation = function ( delta ) {
+
+	var frameTime = this.duration / this.length;
+
+	this.time += this.direction * delta;
+
+	if ( this.mirroredLoop ) {
+
+		if ( this.time > this.duration || this.time < 0 ) {
+
+			this.direction *= -1;
+
+			if ( this.time > this.duration ) {
+
+				this.time = this.duration;
+				this.directionBackwards = true;
+
+			}
+
+			if ( this.time < 0 ) {
+
+				this.time = 0;
+				this.directionBackwards = false;
+
+			}
+
+		}
+
+	} else {
+
+		this.time = this.time % this.duration;
+
+		if ( this.time < 0 ) this.time += this.duration;
+
+	}
+
+	var keyframe = this.startKeyframe + THREE.Math.clamp( Math.floor( this.time / frameTime ), 0, this.length - 1 );
+
+	if ( keyframe !== this.currentKeyframe ) {
+
+		this.morphTargetInfluences[ this.lastKeyframe ] = 0;
+		this.morphTargetInfluences[ this.currentKeyframe ] = 1;
+
+		this.morphTargetInfluences[ keyframe ] = 0;
+
+		this.lastKeyframe = this.currentKeyframe;
+		this.currentKeyframe = keyframe;
+
+	}
+
+	var mix = ( this.time % frameTime ) / frameTime;
+
+	if ( this.directionBackwards ) {
+
+		mix = 1 - mix;
+
+	}
+
+	this.morphTargetInfluences[ this.currentKeyframe ] = mix;
+	this.morphTargetInfluences[ this.lastKeyframe ] = 1 - mix;
+
+};
+
+THREE.MorphAnimMesh.prototype.clone = function ( object ) {
+
+	if ( object === undefined ) object = new THREE.MorphAnimMesh( this.geometry, this.material );
+
+	object.duration = this.duration;
+	object.mirroredLoop = this.mirroredLoop;
+	object.time = this.time;
+
+	object.lastKeyframe = this.lastKeyframe;
+	object.currentKeyframe = this.currentKeyframe;
+
+	object.direction = this.direction;
+	object.directionBackwards = this.directionBackwards;
+
+	THREE.Mesh.prototype.clone.call( this, object );
+
+	return object;
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Ribbon = function ( geometry, material ) {
+
+	THREE.Object3D.call( this );
+
+	this.geometry = geometry;
+	this.material = material;
+
+};
+
+THREE.Ribbon.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Ribbon.prototype.clone = function ( object ) {
+
+	if ( object === undefined ) object = new THREE.Ribbon( this.geometry, this.material );
+
+	THREE.Object3D.prototype.clone.call( this, object );
+
+	return object;
+
+};
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.LOD = function () {
+
+	THREE.Object3D.call( this );
+
+	this.objects = [];
+
+};
+
+
+THREE.LOD.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.LOD.prototype.addLevel = function ( object, distance ) {
+
+	if ( distance === undefined ) distance = 0;
+
+	distance = Math.abs( distance );
+
+	for ( var l = 0; l < this.objects.length; l ++ ) {
+
+		if ( distance < this.objects[ l ].distance ) {
+
+			break;
+
+		}
+
+	}
+
+	this.objects.splice( l, 0, { distance: distance, object: object } );
+	this.add( object );
+
+};
+
+THREE.LOD.prototype.getObjectForDistance = function ( distance ) {
+
+	for ( var i = 1, l = this.objects.length; i < l; i ++ ) {
+
+		if ( distance < this.objects[ i ].distance ) {
+
+			break;
+
+		}
+
+	}
+
+	return this.objects[ i - 1 ].object;
+
+};
+
+THREE.LOD.prototype.update = function () {
+
+	var v1 = new THREE.Vector3();
+	var v2 = new THREE.Vector3();
+
+	return function ( camera ) {
+
+		if ( this.objects.length > 1 ) {
+
+			v1.getPositionFromMatrix( camera.matrixWorld );
+			v2.getPositionFromMatrix( this.matrixWorld );
+
+			var distance = v1.distanceTo( v2 );
+
+			this.objects[ 0 ].object.visible = true;
+
+			for ( var i = 1, l = this.objects.length; i < l; i ++ ) {
+
+				if ( distance >= this.objects[ i ].distance ) {
+
+					this.objects[ i - 1 ].object.visible = false;
+					this.objects[ i     ].object.visible = true;
+
+				} else {
+
+					break;
+
+				}
+
+			}
+
+			for( ; i < l; i ++ ) {
+
+				this.objects[ i ].object.visible = false;
+
+			}
+
+		}
+
+	};
+
+}();
+
+THREE.LOD.prototype.clone = function () {
+
+	// TODO
+
+};
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Sprite = function ( material ) {
+
+	THREE.Object3D.call( this );
+
+	this.material = ( material !== undefined ) ? material : new THREE.SpriteMaterial();
+
+	this.rotation3d = this.rotation;
+	this.rotation = 0;
+
+};
+
+THREE.Sprite.prototype = Object.create( THREE.Object3D.prototype );
+
+/*
+ * Custom update matrix
+ */
+
+THREE.Sprite.prototype.updateMatrix = function () {
+
+	this.rotation3d.set( 0, 0, this.rotation );
+	this.quaternion.setFromEuler( this.rotation3d, this.eulerOrder );
+	this.matrix.makeFromPositionQuaternionScale( this.position, this.quaternion, this.scale );
+
+	this.matrixWorldNeedsUpdate = true;
+
+};
+
+THREE.Sprite.prototype.clone = function ( object ) {
+
+	if ( object === undefined ) object = new THREE.Sprite( this.material );
+
+	THREE.Object3D.prototype.clone.call( this, object );
+
+	return object;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Scene = function () {
+
+	THREE.Object3D.call( this );
+
+	this.fog = null;
+	this.overrideMaterial = null;
+
+	this.autoUpdate = true; // checked by the renderer
+	this.matrixAutoUpdate = false;
+
+	this.__objects = [];
+	this.__lights = [];
+
+	this.__objectsAdded = [];
+	this.__objectsRemoved = [];
+
+};
+
+THREE.Scene.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Scene.prototype.__addObject = function ( object ) {
+
+	if ( object instanceof THREE.Light ) {
+
+		if ( this.__lights.indexOf( object ) === - 1 ) {
+
+			this.__lights.push( object );
+
+		}
+
+		if ( object.target && object.target.parent === undefined ) {
+
+			this.add( object.target );
+
+		}
+
+	} else if ( !( object instanceof THREE.Camera || object instanceof THREE.Bone ) ) {
+
+		if ( this.__objects.indexOf( object ) === - 1 ) {
+
+			this.__objects.push( object );
+			this.__objectsAdded.push( object );
+
+			// check if previously removed
+
+			var i = this.__objectsRemoved.indexOf( object );
+
+			if ( i !== -1 ) {
+
+				this.__objectsRemoved.splice( i, 1 );
+
+			}
+
+		}
+
+	}
+
+	for ( var c = 0; c < object.children.length; c ++ ) {
+
+		this.__addObject( object.children[ c ] );
+
+	}
+
+};
+
+THREE.Scene.prototype.__removeObject = function ( object ) {
+
+	if ( object instanceof THREE.Light ) {
+
+		var i = this.__lights.indexOf( object );
+
+		if ( i !== -1 ) {
+
+			this.__lights.splice( i, 1 );
+
+		}
+
+	} else if ( !( object instanceof THREE.Camera ) ) {
+
+		var i = this.__objects.indexOf( object );
+
+		if( i !== -1 ) {
+
+			this.__objects.splice( i, 1 );
+			this.__objectsRemoved.push( object );
+
+			// check if previously added
+
+			var ai = this.__objectsAdded.indexOf( object );
+
+			if ( ai !== -1 ) {
+
+				this.__objectsAdded.splice( ai, 1 );
+
+			}
+
+		}
+
+	}
+
+	for ( var c = 0; c < object.children.length; c ++ ) {
+
+		this.__removeObject( object.children[ c ] );
+
+	}
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Fog = function ( hex, near, far ) {
+
+	this.name = '';
+
+	this.color = new THREE.Color( hex );
+
+	this.near = ( near !== undefined ) ? near : 1;
+	this.far = ( far !== undefined ) ? far : 1000;
+
+};
+
+THREE.Fog.prototype.clone = function () {
+
+	return new THREE.Fog( this.color.getHex(), this.near, this.far );
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.FogExp2 = function ( hex, density ) {
+
+	this.name = '';
+	this.color = new THREE.Color( hex );
+	this.density = ( density !== undefined ) ? density : 0.00025;
+
+};
+
+THREE.FogExp2.prototype.clone = function () {
+
+	return new THREE.FogExp2( this.color.getHex(), this.density );
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.CanvasRenderer = function ( parameters ) {
+
+	console.log( 'THREE.CanvasRenderer', THREE.REVISION );
+
+	var smoothstep = THREE.Math.smoothstep;
+
+	parameters = parameters || {};
+
+	var _this = this,
+	_renderData, _elements, _lights,
+	_projector = new THREE.Projector(),
+
+	_canvas = parameters.canvas !== undefined
+			? parameters.canvas
+			: document.createElement( 'canvas' ),
+
+	_canvasWidth, _canvasHeight, _canvasWidthHalf, _canvasHeightHalf,
+	_context = _canvas.getContext( '2d' ),
+
+	_clearColor = new THREE.Color( 0x000000 ),
+	_clearAlpha = 0,
+
+	_contextGlobalAlpha = 1,
+	_contextGlobalCompositeOperation = 0,
+	_contextStrokeStyle = null,
+	_contextFillStyle = null,
+	_contextLineWidth = null,
+	_contextLineCap = null,
+	_contextLineJoin = null,
+	_contextDashSize = null,
+	_contextGapSize = 0,
+
+	_v1, _v2, _v3, _v4,
+	_v5 = new THREE.RenderableVertex(),
+	_v6 = new THREE.RenderableVertex(),
+
+	_v1x, _v1y, _v2x, _v2y, _v3x, _v3y,
+	_v4x, _v4y, _v5x, _v5y, _v6x, _v6y,
+
+	_color = new THREE.Color(),
+	_color1 = new THREE.Color(),
+	_color2 = new THREE.Color(),
+	_color3 = new THREE.Color(),
+	_color4 = new THREE.Color(),
+
+	_diffuseColor = new THREE.Color(),
+	_emissiveColor = new THREE.Color(),
+
+	_lightColor = new THREE.Color(),
+
+	_patterns = {}, _imagedatas = {},
+
+	_near, _far,
+
+	_image, _uvs,
+	_uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y,
+
+	_clipBox = new THREE.Box2(),
+	_clearBox = new THREE.Box2(),
+	_elemBox = new THREE.Box2(),
+
+	_ambientLight = new THREE.Color(),
+	_directionalLights = new THREE.Color(),
+	_pointLights = new THREE.Color(),
+
+	_vector3 = new THREE.Vector3(), // Needed for PointLight
+
+	_pixelMap, _pixelMapContext, _pixelMapImage, _pixelMapData,
+	_gradientMap, _gradientMapContext, _gradientMapQuality = 16;
+
+	_pixelMap = document.createElement( 'canvas' );
+	_pixelMap.width = _pixelMap.height = 2;
+
+	_pixelMapContext = _pixelMap.getContext( '2d' );
+	_pixelMapContext.fillStyle = 'rgba(0,0,0,1)';
+	_pixelMapContext.fillRect( 0, 0, 2, 2 );
+
+	_pixelMapImage = _pixelMapContext.getImageData( 0, 0, 2, 2 );
+	_pixelMapData = _pixelMapImage.data;
+
+	_gradientMap = document.createElement( 'canvas' );
+	_gradientMap.width = _gradientMap.height = _gradientMapQuality;
+
+	_gradientMapContext = _gradientMap.getContext( '2d' );
+	_gradientMapContext.translate( - _gradientMapQuality / 2, - _gradientMapQuality / 2 );
+	_gradientMapContext.scale( _gradientMapQuality, _gradientMapQuality );
+
+	_gradientMapQuality --; // Fix UVs
+
+	// dash+gap fallbacks for Firefox and everything else
+
+	if ( _context.setLineDash === undefined ) {
+
+		if ( _context.mozDash !== undefined ) {
+
+			_context.setLineDash = function ( values ) {
+
+				_context.mozDash = values[ 0 ] !== null ? values : null;
+
+			}
+
+		} else {
+
+			_context.setLineDash = function () {}
+
+		}
+
+	}
+
+	this.domElement = _canvas;
+
+	this.devicePixelRatio = parameters.devicePixelRatio !== undefined
+				? parameters.devicePixelRatio
+				: window.devicePixelRatio !== undefined
+					? window.devicePixelRatio
+					: 1;
+
+	this.autoClear = true;
+	this.sortObjects = true;
+	this.sortElements = true;
+
+	this.info = {
+
+		render: {
+
+			vertices: 0,
+			faces: 0
+
+		}
+
+	}
+
+	// WebGLRenderer compatibility
+
+	this.supportsVertexTextures = function () {};
+	this.setFaceCulling = function () {};
+
+	this.setSize = function ( width, height, updateStyle ) {
+
+		_canvasWidth = width * this.devicePixelRatio;
+		_canvasHeight = height * this.devicePixelRatio;
+
+		_canvasWidthHalf = Math.floor( _canvasWidth / 2 );
+		_canvasHeightHalf = Math.floor( _canvasHeight / 2 );
+
+		_canvas.width = _canvasWidth;
+		_canvas.height = _canvasHeight;
+
+		if ( this.devicePixelRatio !== 1 && updateStyle !== false ) {
+
+			_canvas.style.width = width + 'px';
+			_canvas.style.height = height + 'px';
+
+		}
+
+		_clipBox.set(
+			new THREE.Vector2( - _canvasWidthHalf, - _canvasHeightHalf ),
+			new THREE.Vector2( _canvasWidthHalf, _canvasHeightHalf )
+		);
+
+		_clearBox.set(
+			new THREE.Vector2( - _canvasWidthHalf, - _canvasHeightHalf ),
+			new THREE.Vector2( _canvasWidthHalf, _canvasHeightHalf )
+		);
+
+		_contextGlobalAlpha = 1;
+		_contextGlobalCompositeOperation = 0;
+		_contextStrokeStyle = null;
+		_contextFillStyle = null;
+		_contextLineWidth = null;
+		_contextLineCap = null;
+		_contextLineJoin = null;
+
+	};
+
+	this.setClearColor = function ( color, alpha ) {
+
+		_clearColor.set( color );
+		_clearAlpha = alpha !== undefined ? alpha : 1;
+
+		_clearBox.set(
+			new THREE.Vector2( - _canvasWidthHalf, - _canvasHeightHalf ),
+			new THREE.Vector2( _canvasWidthHalf, _canvasHeightHalf )
+		);
+
+	};
+
+	this.setClearColorHex = function ( hex, alpha ) {
+
+		console.warn( 'DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.' );
+		this.setClearColor( hex, alpha );
+
+	};
+
+	this.getMaxAnisotropy = function () {
+
+		return 0;
+
+	};
+
+	this.clear = function () {
+
+		_context.setTransform( 1, 0, 0, - 1, _canvasWidthHalf, _canvasHeightHalf );
+
+		if ( _clearBox.empty() === false ) {
+
+			_clearBox.intersect( _clipBox );
+			_clearBox.expandByScalar( 2 );
+
+			if ( _clearAlpha < 1 ) {
+
+				_context.clearRect(
+					_clearBox.min.x | 0,
+					_clearBox.min.y | 0,
+					( _clearBox.max.x - _clearBox.min.x ) | 0,
+					( _clearBox.max.y - _clearBox.min.y ) | 0
+				);
+
+			}
+
+			if ( _clearAlpha > 0 ) {
+
+				setBlending( THREE.NormalBlending );
+				setOpacity( 1 );
+
+				setFillStyle( 'rgba(' + Math.floor( _clearColor.r * 255 ) + ',' + Math.floor( _clearColor.g * 255 ) + ',' + Math.floor( _clearColor.b * 255 ) + ',' + _clearAlpha + ')' );
+
+				_context.fillRect(
+					_clearBox.min.x | 0,
+					_clearBox.min.y | 0,
+					( _clearBox.max.x - _clearBox.min.x ) | 0,
+					( _clearBox.max.y - _clearBox.min.y ) | 0
+				);
+
+			}
+
+			_clearBox.makeEmpty();
+
+		}
+
+
+	};
+
+	this.render = function ( scene, camera ) {
+
+		if ( camera instanceof THREE.Camera === false ) {
+
+			console.error( 'THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.' );
+			return;
+
+		}
+
+		if ( this.autoClear === true ) this.clear();
+
+		_context.setTransform( 1, 0, 0, - 1, _canvasWidthHalf, _canvasHeightHalf );
+
+		_this.info.render.vertices = 0;
+		_this.info.render.faces = 0;
+
+		_renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements );
+		_elements = _renderData.elements;
+		_lights = _renderData.lights;
+
+		/* DEBUG
+		setFillStyle( 'rgba( 0, 255, 255, 0.5 )' );
+		_context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y );
+		*/
+
+		calculateLights();
+
+		for ( var e = 0, el = _elements.length; e < el; e++ ) {
+
+			var element = _elements[ e ];
+
+			var material = element.material;
+
+			if ( material === undefined || material.visible === false ) continue;
+
+			_elemBox.makeEmpty();
+
+			if ( element instanceof THREE.RenderableParticle ) {
+
+				_v1 = element;
+				_v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf;
+
+				renderParticle( _v1, element, material );
+
+			} else if ( element instanceof THREE.RenderableLine ) {
+
+				_v1 = element.v1; _v2 = element.v2;
+
+				_v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
+				_v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
+
+				_elemBox.setFromPoints( [
+					_v1.positionScreen,
+					_v2.positionScreen
+				] );
+
+				if ( _clipBox.isIntersectionBox( _elemBox ) === true ) {
+
+					renderLine( _v1, _v2, element, material );
+
+				}
+
+			} else if ( element instanceof THREE.RenderableFace3 ) {
+
+				_v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
+
+				if ( _v1.positionScreen.z < -1 || _v1.positionScreen.z > 1 ) continue;
+				if ( _v2.positionScreen.z < -1 || _v2.positionScreen.z > 1 ) continue;
+				if ( _v3.positionScreen.z < -1 || _v3.positionScreen.z > 1 ) continue;
+
+				_v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
+				_v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
+				_v3.positionScreen.x *= _canvasWidthHalf; _v3.positionScreen.y *= _canvasHeightHalf;
+
+				if ( material.overdraw === true ) {
+
+					expand( _v1.positionScreen, _v2.positionScreen );
+					expand( _v2.positionScreen, _v3.positionScreen );
+					expand( _v3.positionScreen, _v1.positionScreen );
+
+				}
+
+				_elemBox.setFromPoints( [
+					_v1.positionScreen,
+					_v2.positionScreen,
+					_v3.positionScreen
+				] );
+
+				if ( _clipBox.isIntersectionBox( _elemBox ) === true ) {
+
+					renderFace3( _v1, _v2, _v3, 0, 1, 2, element, material );
+
+				}
+
+			} else if ( element instanceof THREE.RenderableFace4 ) {
+
+				_v1 = element.v1; _v2 = element.v2; _v3 = element.v3; _v4 = element.v4;
+
+				if ( _v1.positionScreen.z < -1 || _v1.positionScreen.z > 1 ) continue;
+				if ( _v2.positionScreen.z < -1 || _v2.positionScreen.z > 1 ) continue;
+				if ( _v3.positionScreen.z < -1 || _v3.positionScreen.z > 1 ) continue;
+				if ( _v4.positionScreen.z < -1 || _v4.positionScreen.z > 1 ) continue;
+
+				_v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
+				_v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
+				_v3.positionScreen.x *= _canvasWidthHalf; _v3.positionScreen.y *= _canvasHeightHalf;
+				_v4.positionScreen.x *= _canvasWidthHalf; _v4.positionScreen.y *= _canvasHeightHalf;
+
+				_v5.positionScreen.copy( _v2.positionScreen );
+				_v6.positionScreen.copy( _v4.positionScreen );
+
+				if ( material.overdraw === true ) {
+
+					expand( _v1.positionScreen, _v2.positionScreen );
+					expand( _v2.positionScreen, _v4.positionScreen );
+					expand( _v4.positionScreen, _v1.positionScreen );
+
+					expand( _v3.positionScreen, _v5.positionScreen );
+					expand( _v3.positionScreen, _v6.positionScreen );
+
+				}
+
+				_elemBox.setFromPoints( [
+					_v1.positionScreen,
+					_v2.positionScreen,
+					_v3.positionScreen,
+					_v4.positionScreen
+				] );
+
+				if ( _clipBox.isIntersectionBox( _elemBox ) === true ) {
+
+					renderFace4( _v1, _v2, _v3, _v4, _v5, _v6, element, material );
+
+				}
+
+			}
+
+			/* DEBUG
+			setLineWidth( 1 );
+			setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' );
+			_context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y );
+			*/
+
+			_clearBox.union( _elemBox );
+
+		}
+
+		/* DEBUG
+		setLineWidth( 1 );
+		setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' );
+		_context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y );
+		*/
+
+		_context.setTransform( 1, 0, 0, 1, 0, 0 );
+
+		//
+
+		function calculateLights() {
+
+			_ambientLight.setRGB( 0, 0, 0 );
+			_directionalLights.setRGB( 0, 0, 0 );
+			_pointLights.setRGB( 0, 0, 0 );
+
+			for ( var l = 0, ll = _lights.length; l < ll; l ++ ) {
+
+				var light = _lights[ l ];
+				var lightColor = light.color;
+
+				if ( light instanceof THREE.AmbientLight ) {
+
+					_ambientLight.add( lightColor );
+
+				} else if ( light instanceof THREE.DirectionalLight ) {
+
+					// for particles
+
+					_directionalLights.add( lightColor );
+
+				} else if ( light instanceof THREE.PointLight ) {
+
+					// for particles
+
+					_pointLights.add( lightColor );
+
+				}
+
+			}
+
+		}
+
+		function calculateLight( position, normal, color ) {
+
+			for ( var l = 0, ll = _lights.length; l < ll; l ++ ) {
+
+				var light = _lights[ l ];
+
+				_lightColor.copy( light.color );
+
+				if ( light instanceof THREE.DirectionalLight ) {
+
+					var lightPosition = _vector3.getPositionFromMatrix( light.matrixWorld ).normalize();
+
+					var amount = normal.dot( lightPosition );
+
+					if ( amount <= 0 ) continue;
+
+					amount *= light.intensity;
+
+					color.add( _lightColor.multiplyScalar( amount ) );
+
+				} else if ( light instanceof THREE.PointLight ) {
+
+					var lightPosition = _vector3.getPositionFromMatrix( light.matrixWorld );
+
+					var amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() );
+
+					if ( amount <= 0 ) continue;
+
+					amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 );
+
+					if ( amount == 0 ) continue;
+
+					amount *= light.intensity;
+
+					color.add( _lightColor.multiplyScalar( amount ) );
+
+				}
+
+			}
+
+		}
+
+		function renderParticle( v1, element, material ) {
+
+			setOpacity( material.opacity );
+			setBlending( material.blending );
+
+			var width, height, scaleX, scaleY,
+			bitmap, bitmapWidth, bitmapHeight;
+
+			if ( material instanceof THREE.ParticleBasicMaterial ) {
+
+				if ( material.map === null ) {
+
+					scaleX = element.object.scale.x;
+					scaleY = element.object.scale.y;
+
+					// TODO: Be able to disable this
+
+					scaleX *= element.scale.x * _canvasWidthHalf;
+					scaleY *= element.scale.y * _canvasHeightHalf;
+
+					_elemBox.min.set( v1.x - scaleX, v1.y - scaleY );
+					_elemBox.max.set( v1.x + scaleX, v1.y + scaleY );
+
+					if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+						_elemBox.makeEmpty();
+						return;
+
+					}
+
+					setFillStyle( material.color.getStyle() );
+
+					_context.save();
+					_context.translate( v1.x, v1.y );
+					_context.rotate( - element.rotation );
+					_context.scale( scaleX, scaleY );
+					_context.fillRect( -1, -1, 2, 2 );
+					_context.restore();
+
+				} else {
+
+					bitmap = material.map.image;
+					bitmapWidth = bitmap.width >> 1;
+					bitmapHeight = bitmap.height >> 1;
+
+					scaleX = element.scale.x * _canvasWidthHalf;
+					scaleY = element.scale.y * _canvasHeightHalf;
+
+					width = scaleX * bitmapWidth;
+					height = scaleY * bitmapHeight;
+
+					// TODO: Rotations break this...
+
+					_elemBox.min.set( v1.x - width, v1.y - height );
+					_elemBox.max.set( v1.x + width, v1.y + height );
+
+					if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+						_elemBox.makeEmpty();
+						return;
+
+					}
+
+					_context.save();
+					_context.translate( v1.x, v1.y );
+					_context.rotate( - element.rotation );
+					_context.scale( scaleX, - scaleY );
+
+					_context.translate( - bitmapWidth, - bitmapHeight );
+					_context.drawImage( bitmap, 0, 0 );
+					_context.restore();
+
+				}
+
+				/* DEBUG
+				setStrokeStyle( 'rgb(255,255,0)' );
+				_context.beginPath();
+				_context.moveTo( v1.x - 10, v1.y );
+				_context.lineTo( v1.x + 10, v1.y );
+				_context.moveTo( v1.x, v1.y - 10 );
+				_context.lineTo( v1.x, v1.y + 10 );
+				_context.stroke();
+				*/
+
+			} else if ( material instanceof THREE.ParticleCanvasMaterial ) {
+
+				width = element.scale.x * _canvasWidthHalf;
+				height = element.scale.y * _canvasHeightHalf;
+
+				_elemBox.min.set( v1.x - width, v1.y - height );
+				_elemBox.max.set( v1.x + width, v1.y + height );
+
+				if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+					_elemBox.makeEmpty();
+					return;
+
+				}
+
+				setStrokeStyle( material.color.getStyle() );
+				setFillStyle( material.color.getStyle() );
+
+				_context.save();
+				_context.translate( v1.x, v1.y );
+				_context.rotate( - element.rotation );
+				_context.scale( width, height );
+
+				material.program( _context );
+
+				_context.restore();
+
+			}
+
+		}
+
+		function renderLine( v1, v2, element, material ) {
+
+			setOpacity( material.opacity );
+			setBlending( material.blending );
+
+			_context.beginPath();
+			_context.moveTo( v1.positionScreen.x, v1.positionScreen.y );
+			_context.lineTo( v2.positionScreen.x, v2.positionScreen.y );
+
+			if ( material instanceof THREE.LineBasicMaterial ) {
+
+				setLineWidth( material.linewidth );
+				setLineCap( material.linecap );
+				setLineJoin( material.linejoin );
+
+				if ( material.vertexColors !== THREE.VertexColors ) {
+
+					setStrokeStyle( material.color.getStyle() );
+
+				} else {
+
+					var colorStyle1 = element.vertexColors[0].getStyle();
+					var colorStyle2 = element.vertexColors[1].getStyle();
+
+					if ( colorStyle1 === colorStyle2 ) {
+
+						setStrokeStyle( colorStyle1 );
+
+					} else {
+
+						try {
+
+							var grad = _context.createLinearGradient(
+								v1.positionScreen.x,
+								v1.positionScreen.y,
+								v2.positionScreen.x,
+								v2.positionScreen.y
+							);
+							grad.addColorStop( 0, colorStyle1 );
+							grad.addColorStop( 1, colorStyle2 );
+
+						} catch ( exception ) {
+
+							grad = colorStyle1;
+
+						}
+
+						setStrokeStyle( grad );
+
+					}
+
+				}
+
+				_context.stroke();
+				_elemBox.expandByScalar( material.linewidth * 2 );
+
+			} else if ( material instanceof THREE.LineDashedMaterial ) {
+
+				setLineWidth( material.linewidth );
+				setLineCap( material.linecap );
+				setLineJoin( material.linejoin );
+				setStrokeStyle( material.color.getStyle() );
+				setDashAndGap( material.dashSize, material.gapSize );
+
+				_context.stroke();
+
+				_elemBox.expandByScalar( material.linewidth * 2 );
+
+				setDashAndGap( null, null );
+
+			}
+
+		}
+
+		function renderFace3( v1, v2, v3, uv1, uv2, uv3, element, material ) {
+
+			_this.info.render.vertices += 3;
+			_this.info.render.faces ++;
+
+			setOpacity( material.opacity );
+			setBlending( material.blending );
+
+			_v1x = v1.positionScreen.x; _v1y = v1.positionScreen.y;
+			_v2x = v2.positionScreen.x; _v2y = v2.positionScreen.y;
+			_v3x = v3.positionScreen.x; _v3y = v3.positionScreen.y;
+
+			drawTriangle( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y );
+
+			if ( ( material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) && material.map === null ) {
+
+				_diffuseColor.copy( material.color );
+				_emissiveColor.copy( material.emissive );
+
+				if ( material.vertexColors === THREE.FaceColors ) {
+
+					_diffuseColor.multiply( element.color );
+
+				}
+
+				if ( material.wireframe === false && material.shading == THREE.SmoothShading && element.vertexNormalsLength == 3 ) {
+
+					_color1.copy( _ambientLight );
+					_color2.copy( _ambientLight );
+					_color3.copy( _ambientLight );
+
+					calculateLight( element.v1.positionWorld, element.vertexNormalsModel[ 0 ], _color1 );
+					calculateLight( element.v2.positionWorld, element.vertexNormalsModel[ 1 ], _color2 );
+					calculateLight( element.v3.positionWorld, element.vertexNormalsModel[ 2 ], _color3 );
+
+					_color1.multiply( _diffuseColor ).add( _emissiveColor );
+					_color2.multiply( _diffuseColor ).add( _emissiveColor );
+					_color3.multiply( _diffuseColor ).add( _emissiveColor );
+					_color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 );
+
+					_image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+					clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image );
+
+				} else {
+
+					_color.copy( _ambientLight );
+
+					calculateLight( element.centroidModel, element.normalModel, _color );
+
+					_color.multiply( _diffuseColor ).add( _emissiveColor );
+
+					material.wireframe === true
+						? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+						: fillPath( _color );
+
+				}
+
+			} else if ( material instanceof THREE.MeshBasicMaterial || material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) {
+
+				if ( material.map !== null ) {
+
+					if ( material.map.mapping instanceof THREE.UVMapping ) {
+
+						_uvs = element.uvs[ 0 ];
+						patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[ uv1 ].x, _uvs[ uv1 ].y, _uvs[ uv2 ].x, _uvs[ uv2 ].y, _uvs[ uv3 ].x, _uvs[ uv3 ].y, material.map );
+
+					}
+
+
+				} else if ( material.envMap !== null ) {
+
+					if ( material.envMap.mapping instanceof THREE.SphericalReflectionMapping ) {
+
+						_vector3.copy( element.vertexNormalsModelView[ uv1 ] );
+						_uv1x = 0.5 * _vector3.x + 0.5;
+						_uv1y = 0.5 * _vector3.y + 0.5;
+
+						_vector3.copy( element.vertexNormalsModelView[ uv2 ] );
+						_uv2x = 0.5 * _vector3.x + 0.5;
+						_uv2y = 0.5 * _vector3.y + 0.5;
+
+						_vector3.copy( element.vertexNormalsModelView[ uv3 ] );
+						_uv3x = 0.5 * _vector3.x + 0.5;
+						_uv3y = 0.5 * _vector3.y + 0.5;
+
+						patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap );
+
+					}/* else if ( material.envMap.mapping == THREE.SphericalRefractionMapping ) {
+
+
+
+					}*/
+
+
+				} else {
+
+					_color.copy( material.color );
+
+					if ( material.vertexColors === THREE.FaceColors ) {
+
+						_color.multiply( element.color );
+
+					}
+
+					material.wireframe === true
+						? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+						: fillPath( _color );
+
+				}
+
+			} else if ( material instanceof THREE.MeshDepthMaterial ) {
+
+				_near = camera.near;
+				_far = camera.far;
+
+				_color1.r = _color1.g = _color1.b = 1 - smoothstep( v1.positionScreen.z * v1.positionScreen.w, _near, _far );
+				_color2.r = _color2.g = _color2.b = 1 - smoothstep( v2.positionScreen.z * v2.positionScreen.w, _near, _far );
+				_color3.r = _color3.g = _color3.b = 1 - smoothstep( v3.positionScreen.z * v3.positionScreen.w, _near, _far );
+				_color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 );
+
+				_image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+				clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image );
+
+			} else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+				var normal;
+
+				if ( material.shading == THREE.FlatShading ) {
+
+					normal = element.normalModelView;
+
+					_color.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+					material.wireframe === true
+						? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+						: fillPath( _color );
+
+				} else if ( material.shading == THREE.SmoothShading ) {
+
+					normal = element.vertexNormalsModelView[ uv1 ];
+					_color1.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+					normal = element.vertexNormalsModelView[ uv2 ];
+					_color2.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+					normal = element.vertexNormalsModelView[ uv3 ];
+					_color3.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+					_color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 );
+
+					_image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+					clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image );
+
+				}
+
+			}
+
+		}
+
+		function renderFace4( v1, v2, v3, v4, v5, v6, element, material ) {
+
+			_this.info.render.vertices += 4;
+			_this.info.render.faces ++;
+
+			setOpacity( material.opacity );
+			setBlending( material.blending );
+
+			if ( ( material.map !== undefined && material.map !== null ) || ( material.envMap !== undefined && material.envMap !== null ) ) {
+
+				// Let renderFace3() handle this
+
+				renderFace3( v1, v2, v4, 0, 1, 3, element, material );
+				renderFace3( v5, v3, v6, 1, 2, 3, element, material );
+
+				return;
+
+			}
+
+			_v1x = v1.positionScreen.x; _v1y = v1.positionScreen.y;
+			_v2x = v2.positionScreen.x; _v2y = v2.positionScreen.y;
+			_v3x = v3.positionScreen.x; _v3y = v3.positionScreen.y;
+			_v4x = v4.positionScreen.x; _v4y = v4.positionScreen.y;
+			_v5x = v5.positionScreen.x; _v5y = v5.positionScreen.y;
+			_v6x = v6.positionScreen.x; _v6y = v6.positionScreen.y;
+
+			if ( material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) {
+
+				_diffuseColor.copy( material.color );
+				_emissiveColor.copy( material.emissive );
+
+				if ( material.vertexColors === THREE.FaceColors ) {
+
+					_diffuseColor.multiply( element.color );
+
+				}
+
+				if ( material.wireframe === false && material.shading == THREE.SmoothShading && element.vertexNormalsLength == 4 ) {
+
+					_color1.copy( _ambientLight );
+					_color2.copy( _ambientLight );
+					_color3.copy( _ambientLight );
+					_color4.copy( _ambientLight );
+
+					calculateLight( element.v1.positionWorld, element.vertexNormalsModel[ 0 ], _color1 );
+					calculateLight( element.v2.positionWorld, element.vertexNormalsModel[ 1 ], _color2 );
+					calculateLight( element.v4.positionWorld, element.vertexNormalsModel[ 3 ], _color3 );
+					calculateLight( element.v3.positionWorld, element.vertexNormalsModel[ 2 ], _color4 );
+
+					_color1.multiply( _diffuseColor ).add( _emissiveColor );
+					_color2.multiply( _diffuseColor ).add( _emissiveColor );
+					_color3.multiply( _diffuseColor ).add( _emissiveColor );
+					_color4.multiply( _diffuseColor ).add( _emissiveColor );
+
+					_image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+					// TODO: UVs are incorrect, v4->v3?
+
+					drawTriangle( _v1x, _v1y, _v2x, _v2y, _v4x, _v4y );
+					clipImage( _v1x, _v1y, _v2x, _v2y, _v4x, _v4y, 0, 0, 1, 0, 0, 1, _image );
+
+					drawTriangle( _v5x, _v5y, _v3x, _v3y, _v6x, _v6y );
+					clipImage( _v5x, _v5y, _v3x, _v3y, _v6x, _v6y, 1, 0, 1, 1, 0, 1, _image );
+
+				} else {
+
+					_color.copy( _ambientLight );
+
+					calculateLight( element.centroidModel, element.normalModel, _color );
+
+					_color.multiply( _diffuseColor ).add( _emissiveColor );
+
+					drawQuad( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _v4x, _v4y );
+
+					material.wireframe === true
+						? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+						: fillPath( _color );
+
+				}
+
+			} else if ( material instanceof THREE.MeshBasicMaterial ) {
+
+				_color.copy( material.color );
+
+				if ( material.vertexColors === THREE.FaceColors ) {
+
+					_color.multiply( element.color );
+
+				}
+
+				drawQuad( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _v4x, _v4y );
+
+				material.wireframe === true
+					? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+					: fillPath( _color );
+
+			} else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+				var normal;
+
+				if ( material.shading == THREE.FlatShading ) {
+
+					normal = element.normalModelView;
+					_color.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+					drawQuad( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _v4x, _v4y );
+
+					material.wireframe === true
+						? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+						: fillPath( _color );
+
+				} else if ( material.shading == THREE.SmoothShading ) {
+
+					normal = element.vertexNormalsModelView[ 0 ];
+					_color1.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+					normal = element.vertexNormalsModelView[ 1 ];
+					_color2.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+					normal = element.vertexNormalsModelView[ 3 ];
+					_color3.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+					normal = element.vertexNormalsModelView[ 2 ];
+					_color4.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+					_image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+					drawTriangle( _v1x, _v1y, _v2x, _v2y, _v4x, _v4y );
+					clipImage( _v1x, _v1y, _v2x, _v2y, _v4x, _v4y, 0, 0, 1, 0, 0, 1, _image );
+
+					drawTriangle( _v5x, _v5y, _v3x, _v3y, _v6x, _v6y );
+					clipImage( _v5x, _v5y, _v3x, _v3y, _v6x, _v6y, 1, 0, 1, 1, 0, 1, _image );
+
+				}
+
+			} else if ( material instanceof THREE.MeshDepthMaterial ) {
+
+				_near = camera.near;
+				_far = camera.far;
+
+				_color1.r = _color1.g = _color1.b = 1 - smoothstep( v1.positionScreen.z * v1.positionScreen.w, _near, _far );
+				_color2.r = _color2.g = _color2.b = 1 - smoothstep( v2.positionScreen.z * v2.positionScreen.w, _near, _far );
+				_color3.r = _color3.g = _color3.b = 1 - smoothstep( v4.positionScreen.z * v4.positionScreen.w, _near, _far );
+				_color4.r = _color4.g = _color4.b = 1 - smoothstep( v3.positionScreen.z * v3.positionScreen.w, _near, _far );
+
+				_image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+				// TODO: UVs are incorrect, v4->v3?
+
+				drawTriangle( _v1x, _v1y, _v2x, _v2y, _v4x, _v4y );
+				clipImage( _v1x, _v1y, _v2x, _v2y, _v4x, _v4y, 0, 0, 1, 0, 0, 1, _image );
+
+				drawTriangle( _v5x, _v5y, _v3x, _v3y, _v6x, _v6y );
+				clipImage( _v5x, _v5y, _v3x, _v3y, _v6x, _v6y, 1, 0, 1, 1, 0, 1, _image );
+
+			}
+
+		}
+
+		//
+
+		function drawTriangle( x0, y0, x1, y1, x2, y2 ) {
+
+			_context.beginPath();
+			_context.moveTo( x0, y0 );
+			_context.lineTo( x1, y1 );
+			_context.lineTo( x2, y2 );
+			_context.closePath();
+
+		}
+
+		function drawQuad( x0, y0, x1, y1, x2, y2, x3, y3 ) {
+
+			_context.beginPath();
+			_context.moveTo( x0, y0 );
+			_context.lineTo( x1, y1 );
+			_context.lineTo( x2, y2 );
+			_context.lineTo( x3, y3 );
+			_context.closePath();
+
+		}
+
+		function strokePath( color, linewidth, linecap, linejoin ) {
+
+			setLineWidth( linewidth );
+			setLineCap( linecap );
+			setLineJoin( linejoin );
+			setStrokeStyle( color.getStyle() );
+
+			_context.stroke();
+
+			_elemBox.expandByScalar( linewidth * 2 );
+
+		}
+
+		function fillPath( color ) {
+
+			setFillStyle( color.getStyle() );
+			_context.fill();
+
+		}
+
+		function patternPath( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture ) {
+
+			if ( texture instanceof THREE.DataTexture || texture.image === undefined || texture.image.width == 0 ) return;
+
+			if ( texture.needsUpdate === true ) {
+
+				var repeatX = texture.wrapS == THREE.RepeatWrapping;
+				var repeatY = texture.wrapT == THREE.RepeatWrapping;
+
+				_patterns[ texture.id ] = _context.createPattern(
+					texture.image, repeatX === true && repeatY === true
+						? 'repeat'
+						: repeatX === true && repeatY === false
+							? 'repeat-x'
+							: repeatX === false && repeatY === true
+								? 'repeat-y'
+								: 'no-repeat'
+				);
+
+				texture.needsUpdate = false;
+
+			}
+
+			_patterns[ texture.id ] === undefined
+				? setFillStyle( 'rgba(0,0,0,1)' )
+				: setFillStyle( _patterns[ texture.id ] );
+
+			// http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
+
+			var a, b, c, d, e, f, det, idet,
+			offsetX = texture.offset.x / texture.repeat.x,
+			offsetY = texture.offset.y / texture.repeat.y,
+			width = texture.image.width * texture.repeat.x,
+			height = texture.image.height * texture.repeat.y;
+
+			u0 = ( u0 + offsetX ) * width;
+			v0 = ( 1.0 - v0 + offsetY ) * height;
+
+			u1 = ( u1 + offsetX ) * width;
+			v1 = ( 1.0 - v1 + offsetY ) * height;
+
+			u2 = ( u2 + offsetX ) * width;
+			v2 = ( 1.0 - v2 + offsetY ) * height;
+
+			x1 -= x0; y1 -= y0;
+			x2 -= x0; y2 -= y0;
+
+			u1 -= u0; v1 -= v0;
+			u2 -= u0; v2 -= v0;
+
+			det = u1 * v2 - u2 * v1;
+
+			if ( det === 0 ) {
+
+				if ( _imagedatas[ texture.id ] === undefined ) {
+
+					var canvas = document.createElement( 'canvas' )
+					canvas.width = texture.image.width;
+					canvas.height = texture.image.height;
+
+					var context = canvas.getContext( '2d' );
+					context.drawImage( texture.image, 0, 0 );
+
+					_imagedatas[ texture.id ] = context.getImageData( 0, 0, texture.image.width, texture.image.height ).data;
+
+				}
+
+				var data = _imagedatas[ texture.id ];
+				var index = ( Math.floor( u0 ) + Math.floor( v0 ) * texture.image.width ) * 4;
+
+				_color.setRGB( data[ index ] / 255, data[ index + 1 ] / 255, data[ index + 2 ] / 255 );
+				fillPath( _color );
+
+				return;
+
+			}
+
+			idet = 1 / det;
+
+			a = ( v2 * x1 - v1 * x2 ) * idet;
+			b = ( v2 * y1 - v1 * y2 ) * idet;
+			c = ( u1 * x2 - u2 * x1 ) * idet;
+			d = ( u1 * y2 - u2 * y1 ) * idet;
+
+			e = x0 - a * u0 - c * v0;
+			f = y0 - b * u0 - d * v0;
+
+			_context.save();
+			_context.transform( a, b, c, d, e, f );
+			_context.fill();
+			_context.restore();
+
+		}
+
+		function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) {
+
+			// http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
+
+			var a, b, c, d, e, f, det, idet,
+			width = image.width - 1,
+			height = image.height - 1;
+
+			u0 *= width; v0 *= height;
+			u1 *= width; v1 *= height;
+			u2 *= width; v2 *= height;
+
+			x1 -= x0; y1 -= y0;
+			x2 -= x0; y2 -= y0;
+
+			u1 -= u0; v1 -= v0;
+			u2 -= u0; v2 -= v0;
+
+			det = u1 * v2 - u2 * v1;
+
+			idet = 1 / det;
+
+			a = ( v2 * x1 - v1 * x2 ) * idet;
+			b = ( v2 * y1 - v1 * y2 ) * idet;
+			c = ( u1 * x2 - u2 * x1 ) * idet;
+			d = ( u1 * y2 - u2 * y1 ) * idet;
+
+			e = x0 - a * u0 - c * v0;
+			f = y0 - b * u0 - d * v0;
+
+			_context.save();
+			_context.transform( a, b, c, d, e, f );
+			_context.clip();
+			_context.drawImage( image, 0, 0 );
+			_context.restore();
+
+		}
+
+		function getGradientTexture( color1, color2, color3, color4 ) {
+
+			// http://mrdoob.com/blog/post/710
+
+			_pixelMapData[ 0 ] = ( color1.r * 255 ) | 0;
+			_pixelMapData[ 1 ] = ( color1.g * 255 ) | 0;
+			_pixelMapData[ 2 ] = ( color1.b * 255 ) | 0;
+
+			_pixelMapData[ 4 ] = ( color2.r * 255 ) | 0;
+			_pixelMapData[ 5 ] = ( color2.g * 255 ) | 0;
+			_pixelMapData[ 6 ] = ( color2.b * 255 ) | 0;
+
+			_pixelMapData[ 8 ] = ( color3.r * 255 ) | 0;
+			_pixelMapData[ 9 ] = ( color3.g * 255 ) | 0;
+			_pixelMapData[ 10 ] = ( color3.b * 255 ) | 0;
+
+			_pixelMapData[ 12 ] = ( color4.r * 255 ) | 0;
+			_pixelMapData[ 13 ] = ( color4.g * 255 ) | 0;
+			_pixelMapData[ 14 ] = ( color4.b * 255 ) | 0;
+
+			_pixelMapContext.putImageData( _pixelMapImage, 0, 0 );
+			_gradientMapContext.drawImage( _pixelMap, 0, 0 );
+
+			return _gradientMap;
+
+		}
+
+		// Hide anti-alias gaps
+
+		function expand( v1, v2 ) {
+
+			var x = v2.x - v1.x, y = v2.y - v1.y,
+			det = x * x + y * y, idet;
+
+			if ( det === 0 ) return;
+
+			idet = 1 / Math.sqrt( det );
+
+			x *= idet; y *= idet;
+
+			v2.x += x; v2.y += y;
+			v1.x -= x; v1.y -= y;
+
+		}
+	};
+
+	// Context cached methods.
+
+	function setOpacity( value ) {
+
+		if ( _contextGlobalAlpha !== value ) {
+
+			_context.globalAlpha = value;
+			_contextGlobalAlpha = value;
+
+		}
+
+	}
+
+	function setBlending( value ) {
+
+		if ( _contextGlobalCompositeOperation !== value ) {
+
+			if ( value === THREE.NormalBlending ) {
+
+				_context.globalCompositeOperation = 'source-over';
+
+			} else if ( value === THREE.AdditiveBlending ) {
+
+				_context.globalCompositeOperation = 'lighter';
+
+			} else if ( value === THREE.SubtractiveBlending ) {
+
+				_context.globalCompositeOperation = 'darker';
+
+			}
+
+			_contextGlobalCompositeOperation = value;
+
+		}
+
+	}
+
+	function setLineWidth( value ) {
+
+		if ( _contextLineWidth !== value ) {
+
+			_context.lineWidth = value;
+			_contextLineWidth = value;
+
+		}
+
+	}
+
+	function setLineCap( value ) {
+
+		// "butt", "round", "square"
+
+		if ( _contextLineCap !== value ) {
+
+			_context.lineCap = value;
+			_contextLineCap = value;
+
+		}
+
+	}
+
+	function setLineJoin( value ) {
+
+		// "round", "bevel", "miter"
+
+		if ( _contextLineJoin !== value ) {
+
+			_context.lineJoin = value;
+			_contextLineJoin = value;
+
+		}
+
+	}
+
+	function setStrokeStyle( value ) {
+
+		if ( _contextStrokeStyle !== value ) {
+
+			_context.strokeStyle = value;
+			_contextStrokeStyle = value;
+
+		}
+
+	}
+
+	function setFillStyle( value ) {
+
+		if ( _contextFillStyle !== value ) {
+
+			_context.fillStyle = value;
+			_contextFillStyle = value;
+
+		}
+
+	}
+
+	function setDashAndGap( dashSizeValue, gapSizeValue ) {
+
+		if ( _contextDashSize !== dashSizeValue || _contextGapSize !== gapSizeValue ) {
+
+			_context.setLineDash( [ dashSizeValue, gapSizeValue ] );
+			_contextDashSize = dashSizeValue;
+			_contextGapSize = gapSizeValue;
+
+		}
+
+	}
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ */
+
+THREE.ShaderChunk = {
+
+	// FOG
+
+	fog_pars_fragment: [
+
+		"#ifdef USE_FOG",
+
+			"uniform vec3 fogColor;",
+
+			"#ifdef FOG_EXP2",
+
+				"uniform float fogDensity;",
+
+			"#else",
+
+				"uniform float fogNear;",
+				"uniform float fogFar;",
+
+			"#endif",
+
+		"#endif"
+
+	].join("\n"),
+
+	fog_fragment: [
+
+		"#ifdef USE_FOG",
+
+			"float depth = gl_FragCoord.z / gl_FragCoord.w;",
+
+			"#ifdef FOG_EXP2",
+
+				"const float LOG2 = 1.442695;",
+				"float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );",
+				"fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );",
+
+			"#else",
+
+				"float fogFactor = smoothstep( fogNear, fogFar, depth );",
+
+			"#endif",
+
+			"gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );",
+
+		"#endif"
+
+	].join("\n"),
+
+	// ENVIRONMENT MAP
+
+	envmap_pars_fragment: [
+
+		"#ifdef USE_ENVMAP",
+
+			"uniform float reflectivity;",
+			"uniform samplerCube envMap;",
+			"uniform float flipEnvMap;",
+			"uniform int combine;",
+
+			"#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )",
+
+				"uniform bool useRefract;",
+				"uniform float refractionRatio;",
+
+			"#else",
+
+				"varying vec3 vReflect;",
+
+			"#endif",
+
+		"#endif"
+
+	].join("\n"),
+
+	envmap_fragment: [
+
+		"#ifdef USE_ENVMAP",
+
+			"vec3 reflectVec;",
+
+			"#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )",
+
+				"vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );",
+
+				"if ( useRefract ) {",
+
+					"reflectVec = refract( cameraToVertex, normal, refractionRatio );",
+
+				"} else { ",
+
+					"reflectVec = reflect( cameraToVertex, normal );",
+
+				"}",
+
+			"#else",
+
+				"reflectVec = vReflect;",
+
+			"#endif",
+
+			"#ifdef DOUBLE_SIDED",
+
+				"float flipNormal = ( -1.0 + 2.0 * float( gl_FrontFacing ) );",
+				"vec4 cubeColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );",
+
+			"#else",
+
+				"vec4 cubeColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );",
+
+			"#endif",
+
+			"#ifdef GAMMA_INPUT",
+
+				"cubeColor.xyz *= cubeColor.xyz;",
+
+			"#endif",
+
+			"if ( combine == 1 ) {",
+
+				"gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularStrength * reflectivity );",
+
+			"} else if ( combine == 2 ) {",
+
+				"gl_FragColor.xyz += cubeColor.xyz * specularStrength * reflectivity;",
+
+			"} else {",
+
+				"gl_FragColor.xyz = mix( gl_FragColor.xyz, gl_FragColor.xyz * cubeColor.xyz, specularStrength * reflectivity );",
+
+			"}",
+
+		"#endif"
+
+	].join("\n"),
+
+	envmap_pars_vertex: [
+
+		"#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )",
+
+			"varying vec3 vReflect;",
+
+			"uniform float refractionRatio;",
+			"uniform bool useRefract;",
+
+		"#endif"
+
+	].join("\n"),
+
+	worldpos_vertex : [
+
+		"#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )",
+
+			"#ifdef USE_SKINNING",
+
+				"vec4 worldPosition = modelMatrix * skinned;",
+
+			"#endif",
+
+			"#if defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )",
+
+				"vec4 worldPosition = modelMatrix * vec4( morphed, 1.0 );",
+
+			"#endif",
+
+			"#if ! defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )",
+
+				"vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
+
+			"#endif",
+
+		"#endif"
+
+	].join("\n"),
+
+	envmap_vertex : [
+
+		"#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )",
+
+			"vec3 worldNormal = mat3( modelMatrix[ 0 ].xyz, modelMatrix[ 1 ].xyz, modelMatrix[ 2 ].xyz ) * objectNormal;",
+			"worldNormal = normalize( worldNormal );",
+
+			"vec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );",
+
+			"if ( useRefract ) {",
+
+				"vReflect = refract( cameraToVertex, worldNormal, refractionRatio );",
+
+			"} else {",
+
+				"vReflect = reflect( cameraToVertex, worldNormal );",
+
+			"}",
+
+		"#endif"
+
+	].join("\n"),
+
+	// COLOR MAP (particles)
+
+	map_particle_pars_fragment: [
+
+		"#ifdef USE_MAP",
+
+			"uniform sampler2D map;",
+
+		"#endif"
+
+	].join("\n"),
+
+
+	map_particle_fragment: [
+
+		"#ifdef USE_MAP",
+
+			"gl_FragColor = gl_FragColor * texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) );",
+
+		"#endif"
+
+	].join("\n"),
+
+	// COLOR MAP (triangles)
+
+	map_pars_vertex: [
+
+		"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )",
+
+			"varying vec2 vUv;",
+			"uniform vec4 offsetRepeat;",
+
+		"#endif"
+
+	].join("\n"),
+
+	map_pars_fragment: [
+
+		"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )",
+
+			"varying vec2 vUv;",
+
+		"#endif",
+
+		"#ifdef USE_MAP",
+
+			"uniform sampler2D map;",
+
+		"#endif"
+
+	].join("\n"),
+
+	map_vertex: [
+
+		"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )",
+
+			"vUv = uv * offsetRepeat.zw + offsetRepeat.xy;",
+
+		"#endif"
+
+	].join("\n"),
+
+	map_fragment: [
+
+		"#ifdef USE_MAP",
+
+			"vec4 texelColor = texture2D( map, vUv );",
+
+			"#ifdef GAMMA_INPUT",
+
+				"texelColor.xyz *= texelColor.xyz;",
+
+			"#endif",
+
+			"gl_FragColor = gl_FragColor * texelColor;",
+
+		"#endif"
+
+	].join("\n"),
+
+	// LIGHT MAP
+
+	lightmap_pars_fragment: [
+
+		"#ifdef USE_LIGHTMAP",
+
+			"varying vec2 vUv2;",
+			"uniform sampler2D lightMap;",
+
+		"#endif"
+
+	].join("\n"),
+
+	lightmap_pars_vertex: [
+
+		"#ifdef USE_LIGHTMAP",
+
+			"varying vec2 vUv2;",
+
+		"#endif"
+
+	].join("\n"),
+
+	lightmap_fragment: [
+
+		"#ifdef USE_LIGHTMAP",
+
+			"gl_FragColor = gl_FragColor * texture2D( lightMap, vUv2 );",
+
+		"#endif"
+
+	].join("\n"),
+
+	lightmap_vertex: [
+
+		"#ifdef USE_LIGHTMAP",
+
+			"vUv2 = uv2;",
+
+		"#endif"
+
+	].join("\n"),
+
+	// BUMP MAP
+
+	bumpmap_pars_fragment: [
+
+		"#ifdef USE_BUMPMAP",
+
+			"uniform sampler2D bumpMap;",
+			"uniform float bumpScale;",
+
+			// Derivative maps - bump mapping unparametrized surfaces by Morten Mikkelsen
+			//	http://mmikkelsen3d.blogspot.sk/2011/07/derivative-maps.html
+
+			// Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)
+
+			"vec2 dHdxy_fwd() {",
+
+				"vec2 dSTdx = dFdx( vUv );",
+				"vec2 dSTdy = dFdy( vUv );",
+
+				"float Hll = bumpScale * texture2D( bumpMap, vUv ).x;",
+				"float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;",
+				"float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;",
+
+				"return vec2( dBx, dBy );",
+
+			"}",
+
+			"vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {",
+
+				"vec3 vSigmaX = dFdx( surf_pos );",
+				"vec3 vSigmaY = dFdy( surf_pos );",
+				"vec3 vN = surf_norm;",		// normalized
+
+				"vec3 R1 = cross( vSigmaY, vN );",
+				"vec3 R2 = cross( vN, vSigmaX );",
+
+				"float fDet = dot( vSigmaX, R1 );",
+
+				"vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );",
+				"return normalize( abs( fDet ) * surf_norm - vGrad );",
+
+			"}",
+
+		"#endif"
+
+	].join("\n"),
+
+	// NORMAL MAP
+
+	normalmap_pars_fragment: [
+
+		"#ifdef USE_NORMALMAP",
+
+			"uniform sampler2D normalMap;",
+			"uniform vec2 normalScale;",
+
+			// Per-Pixel Tangent Space Normal Mapping
+			// http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html
+
+			"vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {",
+
+				"vec3 q0 = dFdx( eye_pos.xyz );",
+				"vec3 q1 = dFdy( eye_pos.xyz );",
+				"vec2 st0 = dFdx( vUv.st );",
+				"vec2 st1 = dFdy( vUv.st );",
+
+				"vec3 S = normalize(  q0 * st1.t - q1 * st0.t );",
+				"vec3 T = normalize( -q0 * st1.s + q1 * st0.s );",
+				"vec3 N = normalize( surf_norm );",
+
+				"vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;",
+				"mapN.xy = normalScale * mapN.xy;",
+				"mat3 tsn = mat3( S, T, N );",
+				"return normalize( tsn * mapN );",
+
+			"}",
+
+		"#endif"
+
+	].join("\n"),
+
+	// SPECULAR MAP
+
+	specularmap_pars_fragment: [
+
+		"#ifdef USE_SPECULARMAP",
+
+			"uniform sampler2D specularMap;",
+
+		"#endif"
+
+	].join("\n"),
+
+	specularmap_fragment: [
+
+		"float specularStrength;",
+
+		"#ifdef USE_SPECULARMAP",
+
+			"vec4 texelSpecular = texture2D( specularMap, vUv );",
+			"specularStrength = texelSpecular.r;",
+
+		"#else",
+
+			"specularStrength = 1.0;",
+
+		"#endif"
+
+	].join("\n"),
+
+	// LIGHTS LAMBERT
+
+	lights_lambert_pars_vertex: [
+
+		"uniform vec3 ambient;",
+		"uniform vec3 diffuse;",
+		"uniform vec3 emissive;",
+
+		"uniform vec3 ambientLightColor;",
+
+		"#if MAX_DIR_LIGHTS > 0",
+
+			"uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+			"uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+
+		"#endif",
+
+		"#if MAX_HEMI_LIGHTS > 0",
+
+			"uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
+			"uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
+			"uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
+
+		"#endif",
+
+		"#if MAX_POINT_LIGHTS > 0",
+
+			"uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+			"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+			"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+		"#endif",
+
+		"#if MAX_SPOT_LIGHTS > 0",
+
+			"uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];",
+			"uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+			"uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];",
+			"uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+			"uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];",
+			"uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];",
+
+		"#endif",
+
+		"#ifdef WRAP_AROUND",
+
+			"uniform vec3 wrapRGB;",
+
+		"#endif"
+
+	].join("\n"),
+
+	lights_lambert_vertex: [
+
+		"vLightFront = vec3( 0.0 );",
+
+		"#ifdef DOUBLE_SIDED",
+
+			"vLightBack = vec3( 0.0 );",
+
+		"#endif",
+
+		"transformedNormal = normalize( transformedNormal );",
+
+		"#if MAX_DIR_LIGHTS > 0",
+
+		"for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {",
+
+			"vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+			"vec3 dirVector = normalize( lDirection.xyz );",
+
+			"float dotProduct = dot( transformedNormal, dirVector );",
+			"vec3 directionalLightWeighting = vec3( max( dotProduct, 0.0 ) );",
+
+			"#ifdef DOUBLE_SIDED",
+
+				"vec3 directionalLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );",
+
+				"#ifdef WRAP_AROUND",
+
+					"vec3 directionalLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );",
+
+				"#endif",
+
+			"#endif",
+
+			"#ifdef WRAP_AROUND",
+
+				"vec3 directionalLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );",
+				"directionalLightWeighting = mix( directionalLightWeighting, directionalLightWeightingHalf, wrapRGB );",
+
+				"#ifdef DOUBLE_SIDED",
+
+					"directionalLightWeightingBack = mix( directionalLightWeightingBack, directionalLightWeightingHalfBack, wrapRGB );",
+
+				"#endif",
+
+			"#endif",
+
+			"vLightFront += directionalLightColor[ i ] * directionalLightWeighting;",
+
+			"#ifdef DOUBLE_SIDED",
+
+				"vLightBack += directionalLightColor[ i ] * directionalLightWeightingBack;",
+
+			"#endif",
+
+		"}",
+
+		"#endif",
+
+		"#if MAX_POINT_LIGHTS > 0",
+
+			"for( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+				"vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+				"vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+				"float lDistance = 1.0;",
+				"if ( pointLightDistance[ i ] > 0.0 )",
+					"lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+				"lVector = normalize( lVector );",
+				"float dotProduct = dot( transformedNormal, lVector );",
+
+				"vec3 pointLightWeighting = vec3( max( dotProduct, 0.0 ) );",
+
+				"#ifdef DOUBLE_SIDED",
+
+					"vec3 pointLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );",
+
+					"#ifdef WRAP_AROUND",
+
+						"vec3 pointLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );",
+
+					"#endif",
+
+				"#endif",
+
+				"#ifdef WRAP_AROUND",
+
+					"vec3 pointLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );",
+					"pointLightWeighting = mix( pointLightWeighting, pointLightWeightingHalf, wrapRGB );",
+
+					"#ifdef DOUBLE_SIDED",
+
+						"pointLightWeightingBack = mix( pointLightWeightingBack, pointLightWeightingHalfBack, wrapRGB );",
+
+					"#endif",
+
+				"#endif",
+
+				"vLightFront += pointLightColor[ i ] * pointLightWeighting * lDistance;",
+
+				"#ifdef DOUBLE_SIDED",
+
+					"vLightBack += pointLightColor[ i ] * pointLightWeightingBack * lDistance;",
+
+				"#endif",
+
+			"}",
+
+		"#endif",
+
+		"#if MAX_SPOT_LIGHTS > 0",
+
+			"for( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+				"vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+				"vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+				"float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - worldPosition.xyz ) );",
+
+				"if ( spotEffect > spotLightAngleCos[ i ] ) {",
+
+					"spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );",
+
+					"float lDistance = 1.0;",
+					"if ( spotLightDistance[ i ] > 0.0 )",
+						"lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+					"lVector = normalize( lVector );",
+
+					"float dotProduct = dot( transformedNormal, lVector );",
+					"vec3 spotLightWeighting = vec3( max( dotProduct, 0.0 ) );",
+
+					"#ifdef DOUBLE_SIDED",
+
+						"vec3 spotLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );",
+
+						"#ifdef WRAP_AROUND",
+
+							"vec3 spotLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );",
+
+						"#endif",
+
+					"#endif",
+
+					"#ifdef WRAP_AROUND",
+
+						"vec3 spotLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );",
+						"spotLightWeighting = mix( spotLightWeighting, spotLightWeightingHalf, wrapRGB );",
+
+						"#ifdef DOUBLE_SIDED",
+
+							"spotLightWeightingBack = mix( spotLightWeightingBack, spotLightWeightingHalfBack, wrapRGB );",
+
+						"#endif",
+
+					"#endif",
+
+					"vLightFront += spotLightColor[ i ] * spotLightWeighting * lDistance * spotEffect;",
+
+					"#ifdef DOUBLE_SIDED",
+
+						"vLightBack += spotLightColor[ i ] * spotLightWeightingBack * lDistance * spotEffect;",
+
+					"#endif",
+
+				"}",
+
+			"}",
+
+		"#endif",
+
+		"#if MAX_HEMI_LIGHTS > 0",
+
+			"for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {",
+
+				"vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );",
+				"vec3 lVector = normalize( lDirection.xyz );",
+
+				"float dotProduct = dot( transformedNormal, lVector );",
+
+				"float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
+				"float hemiDiffuseWeightBack = -0.5 * dotProduct + 0.5;",
+
+				"vLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );",
+
+				"#ifdef DOUBLE_SIDED",
+
+					"vLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );",
+
+				"#endif",
+
+			"}",
+
+		"#endif",
+
+		"vLightFront = vLightFront * diffuse + ambient * ambientLightColor + emissive;",
+
+		"#ifdef DOUBLE_SIDED",
+
+			"vLightBack = vLightBack * diffuse + ambient * ambientLightColor + emissive;",
+
+		"#endif"
+
+	].join("\n"),
+
+	// LIGHTS PHONG
+
+	lights_phong_pars_vertex: [
+
+		"#ifndef PHONG_PER_PIXEL",
+
+		"#if MAX_POINT_LIGHTS > 0",
+
+			"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+			"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+			"varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
+
+		"#endif",
+
+		"#if MAX_SPOT_LIGHTS > 0",
+
+			"uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+			"uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+
+			"varying vec4 vSpotLight[ MAX_SPOT_LIGHTS ];",
+
+		"#endif",
+
+		"#endif",
+
+		"#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )",
+
+			"varying vec3 vWorldPosition;",
+
+		"#endif"
+
+	].join("\n"),
+
+
+	lights_phong_vertex: [
+
+		"#ifndef PHONG_PER_PIXEL",
+
+		"#if MAX_POINT_LIGHTS > 0",
+
+			"for( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+				"vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+				"vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+				"float lDistance = 1.0;",
+				"if ( pointLightDistance[ i ] > 0.0 )",
+					"lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+				"vPointLight[ i ] = vec4( lVector, lDistance );",
+
+			"}",
+
+		"#endif",
+
+		"#if MAX_SPOT_LIGHTS > 0",
+
+			"for( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+				"vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+				"vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+				"float lDistance = 1.0;",
+				"if ( spotLightDistance[ i ] > 0.0 )",
+					"lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+				"vSpotLight[ i ] = vec4( lVector, lDistance );",
+
+			"}",
+
+		"#endif",
+
+		"#endif",
+
+		"#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )",
+
+			"vWorldPosition = worldPosition.xyz;",
+
+		"#endif"
+
+	].join("\n"),
+
+	lights_phong_pars_fragment: [
+
+		"uniform vec3 ambientLightColor;",
+
+		"#if MAX_DIR_LIGHTS > 0",
+
+			"uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+			"uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+
+		"#endif",
+
+		"#if MAX_HEMI_LIGHTS > 0",
+
+			"uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
+			"uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
+			"uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
+
+		"#endif",
+
+		"#if MAX_POINT_LIGHTS > 0",
+
+			"uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+
+			"#ifdef PHONG_PER_PIXEL",
+
+				"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+				"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+			"#else",
+
+				"varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
+
+			"#endif",
+
+		"#endif",
+
+		"#if MAX_SPOT_LIGHTS > 0",
+
+			"uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];",
+			"uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+			"uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];",
+			"uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];",
+			"uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];",
+
+			"#ifdef PHONG_PER_PIXEL",
+
+				"uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+
+			"#else",
+
+				"varying vec4 vSpotLight[ MAX_SPOT_LIGHTS ];",
+
+			"#endif",
+
+		"#endif",
+
+		"#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )",
+
+			"varying vec3 vWorldPosition;",
+
+		"#endif",
+
+		"#ifdef WRAP_AROUND",
+
+			"uniform vec3 wrapRGB;",
+
+		"#endif",
+
+		"varying vec3 vViewPosition;",
+		"varying vec3 vNormal;"
+
+	].join("\n"),
+
+	lights_phong_fragment: [
+
+		"vec3 normal = normalize( vNormal );",
+		"vec3 viewPosition = normalize( vViewPosition );",
+
+		"#ifdef DOUBLE_SIDED",
+
+			"normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );",
+
+		"#endif",
+
+		"#ifdef USE_NORMALMAP",
+
+			"normal = perturbNormal2Arb( -vViewPosition, normal );",
+
+		"#elif defined( USE_BUMPMAP )",
+
+			"normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );",
+
+		"#endif",
+
+		"#if MAX_POINT_LIGHTS > 0",
+
+			"vec3 pointDiffuse  = vec3( 0.0 );",
+			"vec3 pointSpecular = vec3( 0.0 );",
+
+			"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+				"#ifdef PHONG_PER_PIXEL",
+
+					"vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+					"vec3 lVector = lPosition.xyz + vViewPosition.xyz;",
+
+					"float lDistance = 1.0;",
+					"if ( pointLightDistance[ i ] > 0.0 )",
+						"lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+					"lVector = normalize( lVector );",
+
+				"#else",
+
+					"vec3 lVector = normalize( vPointLight[ i ].xyz );",
+					"float lDistance = vPointLight[ i ].w;",
+
+				"#endif",
+
+				// diffuse
+
+				"float dotProduct = dot( normal, lVector );",
+
+				"#ifdef WRAP_AROUND",
+
+					"float pointDiffuseWeightFull = max( dotProduct, 0.0 );",
+					"float pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );",
+
+					"vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );",
+
+				"#else",
+
+					"float pointDiffuseWeight = max( dotProduct, 0.0 );",
+
+				"#endif",
+
+				"pointDiffuse  += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;",
+
+				// specular
+
+				"vec3 pointHalfVector = normalize( lVector + viewPosition );",
+				"float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );",
+				"float pointSpecularWeight = specularStrength * max( pow( pointDotNormalHalf, shininess ), 0.0 );",
+
+				"#ifdef PHYSICALLY_BASED_SHADING",
+
+					// 2.0 => 2.0001 is hack to work around ANGLE bug
+
+					"float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+					"vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, pointHalfVector ), 5.0 );",
+					"pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance * specularNormalization;",
+
+				"#else",
+
+					"pointSpecular += specular * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance;",
+
+				"#endif",
+
+			"}",
+
+		"#endif",
+
+		"#if MAX_SPOT_LIGHTS > 0",
+
+			"vec3 spotDiffuse  = vec3( 0.0 );",
+			"vec3 spotSpecular = vec3( 0.0 );",
+
+			"for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+				"#ifdef PHONG_PER_PIXEL",
+
+					"vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+					"vec3 lVector = lPosition.xyz + vViewPosition.xyz;",
+
+					"float lDistance = 1.0;",
+					"if ( spotLightDistance[ i ] > 0.0 )",
+						"lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+					"lVector = normalize( lVector );",
+
+				"#else",
+
+					"vec3 lVector = normalize( vSpotLight[ i ].xyz );",
+					"float lDistance = vSpotLight[ i ].w;",
+
+				"#endif",
+
+				"float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );",
+
+				"if ( spotEffect > spotLightAngleCos[ i ] ) {",
+
+					"spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );",
+
+					// diffuse
+
+					"float dotProduct = dot( normal, lVector );",
+
+					"#ifdef WRAP_AROUND",
+
+						"float spotDiffuseWeightFull = max( dotProduct, 0.0 );",
+						"float spotDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );",
+
+						"vec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );",
+
+					"#else",
+
+						"float spotDiffuseWeight = max( dotProduct, 0.0 );",
+
+					"#endif",
+
+					"spotDiffuse += diffuse * spotLightColor[ i ] * spotDiffuseWeight * lDistance * spotEffect;",
+
+					// specular
+
+					"vec3 spotHalfVector = normalize( lVector + viewPosition );",
+					"float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );",
+					"float spotSpecularWeight = specularStrength * max( pow( spotDotNormalHalf, shininess ), 0.0 );",
+
+					"#ifdef PHYSICALLY_BASED_SHADING",
+
+						// 2.0 => 2.0001 is hack to work around ANGLE bug
+
+						"float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+						"vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, spotHalfVector ), 5.0 );",
+						"spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * specularNormalization * spotEffect;",
+
+					"#else",
+
+						"spotSpecular += specular * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * spotEffect;",
+
+					"#endif",
+
+				"}",
+
+			"}",
+
+		"#endif",
+
+		"#if MAX_DIR_LIGHTS > 0",
+
+			"vec3 dirDiffuse  = vec3( 0.0 );",
+			"vec3 dirSpecular = vec3( 0.0 );" ,
+
+			"for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {",
+
+				"vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+				"vec3 dirVector = normalize( lDirection.xyz );",
+
+				// diffuse
+
+				"float dotProduct = dot( normal, dirVector );",
+
+				"#ifdef WRAP_AROUND",
+
+					"float dirDiffuseWeightFull = max( dotProduct, 0.0 );",
+					"float dirDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );",
+
+					"vec3 dirDiffuseWeight = mix( vec3( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), wrapRGB );",
+
+				"#else",
+
+					"float dirDiffuseWeight = max( dotProduct, 0.0 );",
+
+				"#endif",
+
+				"dirDiffuse  += diffuse * directionalLightColor[ i ] * dirDiffuseWeight;",
+
+				// specular
+
+				"vec3 dirHalfVector = normalize( dirVector + viewPosition );",
+				"float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
+				"float dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );",
+
+				"#ifdef PHYSICALLY_BASED_SHADING",
+
+					/*
+					// fresnel term from skin shader
+					"const float F0 = 0.128;",
+
+					"float base = 1.0 - dot( viewPosition, dirHalfVector );",
+					"float exponential = pow( base, 5.0 );",
+
+					"float fresnel = exponential + F0 * ( 1.0 - exponential );",
+					*/
+
+					/*
+					// fresnel term from fresnel shader
+					"const float mFresnelBias = 0.08;",
+					"const float mFresnelScale = 0.3;",
+					"const float mFresnelPower = 5.0;",
+
+					"float fresnel = mFresnelBias + mFresnelScale * pow( 1.0 + dot( normalize( -viewPosition ), normal ), mFresnelPower );",
+					*/
+
+					// 2.0 => 2.0001 is hack to work around ANGLE bug
+
+					"float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+					//"dirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization * fresnel;",
+
+					"vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );",
+					"dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;",
+
+				"#else",
+
+					"dirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight;",
+
+				"#endif",
+
+			"}",
+
+		"#endif",
+
+		"#if MAX_HEMI_LIGHTS > 0",
+
+			"vec3 hemiDiffuse  = vec3( 0.0 );",
+			"vec3 hemiSpecular = vec3( 0.0 );" ,
+
+			"for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {",
+
+				"vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );",
+				"vec3 lVector = normalize( lDirection.xyz );",
+
+				// diffuse
+
+				"float dotProduct = dot( normal, lVector );",
+				"float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
+
+				"vec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );",
+
+				"hemiDiffuse += diffuse * hemiColor;",
+
+				// specular (sky light)
+
+				"vec3 hemiHalfVectorSky = normalize( lVector + viewPosition );",
+				"float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;",
+				"float hemiSpecularWeightSky = specularStrength * max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );",
+
+				// specular (ground light)
+
+				"vec3 lVectorGround = -lVector;",
+
+				"vec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );",
+				"float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;",
+				"float hemiSpecularWeightGround = specularStrength * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );",
+
+				"#ifdef PHYSICALLY_BASED_SHADING",
+
+					"float dotProductGround = dot( normal, lVectorGround );",
+
+					// 2.0 => 2.0001 is hack to work around ANGLE bug
+
+					"float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+					"vec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );",
+					"vec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );",
+					"hemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );",
+
+				"#else",
+
+					"hemiSpecular += specular * hemiColor * ( hemiSpecularWeightSky + hemiSpecularWeightGround ) * hemiDiffuseWeight;",
+
+				"#endif",
+
+			"}",
+
+		"#endif",
+
+		"vec3 totalDiffuse = vec3( 0.0 );",
+		"vec3 totalSpecular = vec3( 0.0 );",
+
+		"#if MAX_DIR_LIGHTS > 0",
+
+			"totalDiffuse += dirDiffuse;",
+			"totalSpecular += dirSpecular;",
+
+		"#endif",
+
+		"#if MAX_HEMI_LIGHTS > 0",
+
+			"totalDiffuse += hemiDiffuse;",
+			"totalSpecular += hemiSpecular;",
+
+		"#endif",
+
+		"#if MAX_POINT_LIGHTS > 0",
+
+			"totalDiffuse += pointDiffuse;",
+			"totalSpecular += pointSpecular;",
+
+		"#endif",
+
+		"#if MAX_SPOT_LIGHTS > 0",
+
+			"totalDiffuse += spotDiffuse;",
+			"totalSpecular += spotSpecular;",
+
+		"#endif",
+
+		"#ifdef METAL",
+
+			"gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient + totalSpecular );",
+
+		"#else",
+
+			"gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient ) + totalSpecular;",
+
+		"#endif"
+
+	].join("\n"),
+
+	// VERTEX COLORS
+
+	color_pars_fragment: [
+
+		"#ifdef USE_COLOR",
+
+			"varying vec3 vColor;",
+
+		"#endif"
+
+	].join("\n"),
+
+
+	color_fragment: [
+
+		"#ifdef USE_COLOR",
+
+			"gl_FragColor = gl_FragColor * vec4( vColor, opacity );",
+
+		"#endif"
+
+	].join("\n"),
+
+	color_pars_vertex: [
+
+		"#ifdef USE_COLOR",
+
+			"varying vec3 vColor;",
+
+		"#endif"
+
+	].join("\n"),
+
+
+	color_vertex: [
+
+		"#ifdef USE_COLOR",
+
+			"#ifdef GAMMA_INPUT",
+
+				"vColor = color * color;",
+
+			"#else",
+
+				"vColor = color;",
+
+			"#endif",
+
+		"#endif"
+
+	].join("\n"),
+
+	// SKINNING
+
+	skinning_pars_vertex: [
+
+		"#ifdef USE_SKINNING",
+
+			"#ifdef BONE_TEXTURE",
+
+				"uniform sampler2D boneTexture;",
+
+				"mat4 getBoneMatrix( const in float i ) {",
+
+					"float j = i * 4.0;",
+					"float x = mod( j, N_BONE_PIXEL_X );",
+					"float y = floor( j / N_BONE_PIXEL_X );",
+
+					"const float dx = 1.0 / N_BONE_PIXEL_X;",
+					"const float dy = 1.0 / N_BONE_PIXEL_Y;",
+
+					"y = dy * ( y + 0.5 );",
+
+					"vec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );",
+					"vec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );",
+					"vec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );",
+					"vec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );",
+
+					"mat4 bone = mat4( v1, v2, v3, v4 );",
+
+					"return bone;",
+
+				"}",
+
+			"#else",
+
+				"uniform mat4 boneGlobalMatrices[ MAX_BONES ];",
+
+				"mat4 getBoneMatrix( const in float i ) {",
+
+					"mat4 bone = boneGlobalMatrices[ int(i) ];",
+					"return bone;",
+
+				"}",
+
+			"#endif",
+
+		"#endif"
+
+	].join("\n"),
+
+	skinbase_vertex: [
+
+		"#ifdef USE_SKINNING",
+
+			"mat4 boneMatX = getBoneMatrix( skinIndex.x );",
+			"mat4 boneMatY = getBoneMatrix( skinIndex.y );",
+
+		"#endif"
+
+	].join("\n"),
+
+	skinning_vertex: [
+
+		"#ifdef USE_SKINNING",
+
+			"#ifdef USE_MORPHTARGETS",
+
+			"vec4 skinVertex = vec4( morphed, 1.0 );",
+
+			"#else",
+
+			"vec4 skinVertex = vec4( position, 1.0 );",
+
+			"#endif",
+
+			"vec4 skinned  = boneMatX * skinVertex * skinWeight.x;",
+			"skinned 	  += boneMatY * skinVertex * skinWeight.y;",
+
+		"#endif"
+
+	].join("\n"),
+
+	// MORPHING
+
+	morphtarget_pars_vertex: [
+
+		"#ifdef USE_MORPHTARGETS",
+
+			"#ifndef USE_MORPHNORMALS",
+
+			"uniform float morphTargetInfluences[ 8 ];",
+
+			"#else",
+
+			"uniform float morphTargetInfluences[ 4 ];",
+
+			"#endif",
+
+		"#endif"
+
+	].join("\n"),
+
+	morphtarget_vertex: [
+
+		"#ifdef USE_MORPHTARGETS",
+
+			"vec3 morphed = vec3( 0.0 );",
+			"morphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];",
+			"morphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];",
+			"morphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];",
+			"morphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];",
+
+			"#ifndef USE_MORPHNORMALS",
+
+			"morphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];",
+			"morphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];",
+			"morphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];",
+			"morphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];",
+
+			"#endif",
+
+			"morphed += position;",
+
+		"#endif"
+
+	].join("\n"),
+
+	default_vertex : [
+
+		"vec4 mvPosition;",
+
+		"#ifdef USE_SKINNING",
+
+			"mvPosition = modelViewMatrix * skinned;",
+
+		"#endif",
+
+		"#if !defined( USE_SKINNING ) && defined( USE_MORPHTARGETS )",
+
+			"mvPosition = modelViewMatrix * vec4( morphed, 1.0 );",
+
+		"#endif",
+
+		"#if !defined( USE_SKINNING ) && ! defined( USE_MORPHTARGETS )",
+
+			"mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+
+		"#endif",
+
+		"gl_Position = projectionMatrix * mvPosition;"
+
+	].join("\n"),
+
+	morphnormal_vertex: [
+
+		"#ifdef USE_MORPHNORMALS",
+
+			"vec3 morphedNormal = vec3( 0.0 );",
+
+			"morphedNormal +=  ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];",
+			"morphedNormal +=  ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];",
+			"morphedNormal +=  ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];",
+			"morphedNormal +=  ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];",
+
+			"morphedNormal += normal;",
+
+		"#endif"
+
+	].join("\n"),
+
+	skinnormal_vertex: [
+
+		"#ifdef USE_SKINNING",
+
+			"mat4 skinMatrix = skinWeight.x * boneMatX;",
+			"skinMatrix 	+= skinWeight.y * boneMatY;",
+
+			"#ifdef USE_MORPHNORMALS",
+
+			"vec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );",
+
+			"#else",
+
+			"vec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );",
+
+			"#endif",
+
+		"#endif"
+
+	].join("\n"),
+
+	defaultnormal_vertex: [
+
+		"vec3 objectNormal;",
+
+		"#ifdef USE_SKINNING",
+
+			"objectNormal = skinnedNormal.xyz;",
+
+		"#endif",
+
+		"#if !defined( USE_SKINNING ) && defined( USE_MORPHNORMALS )",
+
+			"objectNormal = morphedNormal;",
+
+		"#endif",
+
+		"#if !defined( USE_SKINNING ) && ! defined( USE_MORPHNORMALS )",
+
+			"objectNormal = normal;",
+
+		"#endif",
+
+		"#ifdef FLIP_SIDED",
+
+			"objectNormal = -objectNormal;",
+
+		"#endif",
+
+		"vec3 transformedNormal = normalMatrix * objectNormal;"
+
+	].join("\n"),
+
+	// SHADOW MAP
+
+	// based on SpiderGL shadow map and Fabien Sanglard's GLSL shadow mapping examples
+	//  http://spidergl.org/example.php?id=6
+	// 	http://fabiensanglard.net/shadowmapping
+
+	shadowmap_pars_fragment: [
+
+		"#ifdef USE_SHADOWMAP",
+
+			"uniform sampler2D shadowMap[ MAX_SHADOWS ];",
+			"uniform vec2 shadowMapSize[ MAX_SHADOWS ];",
+
+			"uniform float shadowDarkness[ MAX_SHADOWS ];",
+			"uniform float shadowBias[ MAX_SHADOWS ];",
+
+			"varying vec4 vShadowCoord[ MAX_SHADOWS ];",
+
+			"float unpackDepth( const in vec4 rgba_depth ) {",
+
+				"const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );",
+				"float depth = dot( rgba_depth, bit_shift );",
+				"return depth;",
+
+			"}",
+
+		"#endif"
+
+	].join("\n"),
+
+	shadowmap_fragment: [
+
+		"#ifdef USE_SHADOWMAP",
+
+			"#ifdef SHADOWMAP_DEBUG",
+
+				"vec3 frustumColors[3];",
+				"frustumColors[0] = vec3( 1.0, 0.5, 0.0 );",
+				"frustumColors[1] = vec3( 0.0, 1.0, 0.8 );",
+				"frustumColors[2] = vec3( 0.0, 0.5, 1.0 );",
+
+			"#endif",
+
+			"#ifdef SHADOWMAP_CASCADE",
+
+				"int inFrustumCount = 0;",
+
+			"#endif",
+
+			"float fDepth;",
+			"vec3 shadowColor = vec3( 1.0 );",
+
+			"for( int i = 0; i < MAX_SHADOWS; i ++ ) {",
+
+				"vec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;",
+
+				// "if ( something && something )" 		 breaks ATI OpenGL shader compiler
+				// "if ( all( something, something ) )"  using this instead
+
+				"bvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );",
+				"bool inFrustum = all( inFrustumVec );",
+
+				// don't shadow pixels outside of light frustum
+				// use just first frustum (for cascades)
+				// don't shadow pixels behind far plane of light frustum
+
+				"#ifdef SHADOWMAP_CASCADE",
+
+					"inFrustumCount += int( inFrustum );",
+					"bvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );",
+
+				"#else",
+
+					"bvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );",
+
+				"#endif",
+
+				"bool frustumTest = all( frustumTestVec );",
+
+				"if ( frustumTest ) {",
+
+					"shadowCoord.z += shadowBias[ i ];",
+
+					"#if defined( SHADOWMAP_TYPE_PCF )",
+
+						// Percentage-close filtering
+						// (9 pixel kernel)
+						// http://fabiensanglard.net/shadowmappingPCF/
+
+						"float shadow = 0.0;",
+
+						/*
+						// nested loops breaks shader compiler / validator on some ATI cards when using OpenGL
+						// must enroll loop manually
+
+						"for ( float y = -1.25; y <= 1.25; y += 1.25 )",
+							"for ( float x = -1.25; x <= 1.25; x += 1.25 ) {",
+
+								"vec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );",
+
+								// doesn't seem to produce any noticeable visual difference compared to simple "texture2D" lookup
+								//"vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );",
+
+								"float fDepth = unpackDepth( rgbaDepth );",
+
+								"if ( fDepth < shadowCoord.z )",
+									"shadow += 1.0;",
+
+						"}",
+
+						"shadow /= 9.0;",
+
+						*/
+
+						"const float shadowDelta = 1.0 / 9.0;",
+
+						"float xPixelOffset = 1.0 / shadowMapSize[ i ].x;",
+						"float yPixelOffset = 1.0 / shadowMapSize[ i ].y;",
+
+						"float dx0 = -1.25 * xPixelOffset;",
+						"float dy0 = -1.25 * yPixelOffset;",
+						"float dx1 = 1.25 * xPixelOffset;",
+						"float dy1 = 1.25 * yPixelOffset;",
+
+						"fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );",
+						"if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+						"fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );",
+						"if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+						"fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );",
+						"if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+						"fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );",
+						"if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+						"fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );",
+						"if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+						"fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );",
+						"if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+						"fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );",
+						"if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+						"fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );",
+						"if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+						"fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );",
+						"if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+						"shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );",
+
+					"#elif defined( SHADOWMAP_TYPE_PCF_SOFT )",
+
+						// Percentage-close filtering
+						// (9 pixel kernel)
+						// http://fabiensanglard.net/shadowmappingPCF/
+
+						"float shadow = 0.0;",
+
+						"float xPixelOffset = 1.0 / shadowMapSize[ i ].x;",
+						"float yPixelOffset = 1.0 / shadowMapSize[ i ].y;",
+
+						"float dx0 = -1.0 * xPixelOffset;",
+						"float dy0 = -1.0 * yPixelOffset;",
+						"float dx1 = 1.0 * xPixelOffset;",
+						"float dy1 = 1.0 * yPixelOffset;",
+
+						"mat3 shadowKernel;",
+						"mat3 depthKernel;",
+
+						"depthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );",
+						"depthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );",
+						"depthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );",
+						"depthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );",
+						"depthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );",
+						"depthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );",
+						"depthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );",
+						"depthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );",
+						"depthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );",
+
+						"vec3 shadowZ = vec3( shadowCoord.z );",
+						"shadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));",
+						"shadowKernel[0] *= vec3(0.25);",
+													
+						"shadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));",
+						"shadowKernel[1] *= vec3(0.25);",
+
+						"shadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));",
+						"shadowKernel[2] *= vec3(0.25);",
+
+						"vec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );",
+
+						"shadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );",
+						"shadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );",
+
+						"vec4 shadowValues;",
+						"shadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );",
+						"shadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );",
+						"shadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );",
+						"shadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );",
+
+						"shadow = dot( shadowValues, vec4( 1.0 ) );",
+
+						"shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );",
+
+					"#else",
+
+						"vec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );",
+						"float fDepth = unpackDepth( rgbaDepth );",
+
+						"if ( fDepth < shadowCoord.z )",
+
+							// spot with multiple shadows is darker
+
+							"shadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );",
+
+							// spot with multiple shadows has the same color as single shadow spot
+
+							//"shadowColor = min( shadowColor, vec3( shadowDarkness[ i ] ) );",
+
+					"#endif",
+
+				"}",
+
+
+				"#ifdef SHADOWMAP_DEBUG",
+
+					"#ifdef SHADOWMAP_CASCADE",
+
+						"if ( inFrustum && inFrustumCount == 1 ) gl_FragColor.xyz *= frustumColors[ i ];",
+
+					"#else",
+
+						"if ( inFrustum ) gl_FragColor.xyz *= frustumColors[ i ];",
+
+					"#endif",
+
+				"#endif",
+
+			"}",
+
+			"#ifdef GAMMA_OUTPUT",
+
+				"shadowColor *= shadowColor;",
+
+			"#endif",
+
+			"gl_FragColor.xyz = gl_FragColor.xyz * shadowColor;",
+
+		"#endif"
+
+	].join("\n"),
+
+	shadowmap_pars_vertex: [
+
+		"#ifdef USE_SHADOWMAP",
+
+			"varying vec4 vShadowCoord[ MAX_SHADOWS ];",
+			"uniform mat4 shadowMatrix[ MAX_SHADOWS ];",
+
+		"#endif"
+
+	].join("\n"),
+
+	shadowmap_vertex: [
+
+		"#ifdef USE_SHADOWMAP",
+
+			"for( int i = 0; i < MAX_SHADOWS; i ++ ) {",
+
+				"vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;",
+
+			"}",
+
+		"#endif"
+
+	].join("\n"),
+
+	// ALPHATEST
+
+	alphatest_fragment: [
+
+		"#ifdef ALPHATEST",
+
+			"if ( gl_FragColor.a < ALPHATEST ) discard;",
+
+		"#endif"
+
+	].join("\n"),
+
+	// LINEAR SPACE
+
+	linear_to_gamma_fragment: [
+
+		"#ifdef GAMMA_OUTPUT",
+
+			"gl_FragColor.xyz = sqrt( gl_FragColor.xyz );",
+
+		"#endif"
+
+	].join("\n")
+
+
+};
+
+THREE.UniformsUtils = {
+
+	merge: function ( uniforms ) {
+
+		var u, p, tmp, merged = {};
+
+		for ( u = 0; u < uniforms.length; u ++ ) {
+
+			tmp = this.clone( uniforms[ u ] );
+
+			for ( p in tmp ) {
+
+				merged[ p ] = tmp[ p ];
+
+			}
+
+		}
+
+		return merged;
+
+	},
+
+	clone: function ( uniforms_src ) {
+
+		var u, p, parameter, parameter_src, uniforms_dst = {};
+
+		for ( u in uniforms_src ) {
+
+			uniforms_dst[ u ] = {};
+
+			for ( p in uniforms_src[ u ] ) {
+
+				parameter_src = uniforms_src[ u ][ p ];
+
+				if ( parameter_src instanceof THREE.Color ||
+					 parameter_src instanceof THREE.Vector2 ||
+					 parameter_src instanceof THREE.Vector3 ||
+					 parameter_src instanceof THREE.Vector4 ||
+					 parameter_src instanceof THREE.Matrix4 ||
+					 parameter_src instanceof THREE.Texture ) {
+
+					uniforms_dst[ u ][ p ] = parameter_src.clone();
+
+				} else if ( parameter_src instanceof Array ) {
+
+					uniforms_dst[ u ][ p ] = parameter_src.slice();
+
+				} else {
+
+					uniforms_dst[ u ][ p ] = parameter_src;
+
+				}
+
+			}
+
+		}
+
+		return uniforms_dst;
+
+	}
+
+};
+
+THREE.UniformsLib = {
+
+	common: {
+
+		"diffuse" : { type: "c", value: new THREE.Color( 0xeeeeee ) },
+		"opacity" : { type: "f", value: 1.0 },
+
+		"map" : { type: "t", value: null },
+		"offsetRepeat" : { type: "v4", value: new THREE.Vector4( 0, 0, 1, 1 ) },
+
+		"lightMap" : { type: "t", value: null },
+		"specularMap" : { type: "t", value: null },
+
+		"envMap" : { type: "t", value: null },
+		"flipEnvMap" : { type: "f", value: -1 },
+		"useRefract" : { type: "i", value: 0 },
+		"reflectivity" : { type: "f", value: 1.0 },
+		"refractionRatio" : { type: "f", value: 0.98 },
+		"combine" : { type: "i", value: 0 },
+
+		"morphTargetInfluences" : { type: "f", value: 0 }
+
+	},
+
+	bump: {
+
+		"bumpMap" : { type: "t", value: null },
+		"bumpScale" : { type: "f", value: 1 }
+
+	},
+
+	normalmap: {
+
+		"normalMap" : { type: "t", value: null },
+		"normalScale" : { type: "v2", value: new THREE.Vector2( 1, 1 ) }
+	},
+
+	fog : {
+
+		"fogDensity" : { type: "f", value: 0.00025 },
+		"fogNear" : { type: "f", value: 1 },
+		"fogFar" : { type: "f", value: 2000 },
+		"fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) }
+
+	},
+
+	lights: {
+
+		"ambientLightColor" : { type: "fv", value: [] },
+
+		"directionalLightDirection" : { type: "fv", value: [] },
+		"directionalLightColor" : { type: "fv", value: [] },
+
+		"hemisphereLightDirection" : { type: "fv", value: [] },
+		"hemisphereLightSkyColor" : { type: "fv", value: [] },
+		"hemisphereLightGroundColor" : { type: "fv", value: [] },
+
+		"pointLightColor" : { type: "fv", value: [] },
+		"pointLightPosition" : { type: "fv", value: [] },
+		"pointLightDistance" : { type: "fv1", value: [] },
+
+		"spotLightColor" : { type: "fv", value: [] },
+		"spotLightPosition" : { type: "fv", value: [] },
+		"spotLightDirection" : { type: "fv", value: [] },
+		"spotLightDistance" : { type: "fv1", value: [] },
+		"spotLightAngleCos" : { type: "fv1", value: [] },
+		"spotLightExponent" : { type: "fv1", value: [] }
+
+	},
+
+	particle: {
+
+		"psColor" : { type: "c", value: new THREE.Color( 0xeeeeee ) },
+		"opacity" : { type: "f", value: 1.0 },
+		"size" : { type: "f", value: 1.0 },
+		"scale" : { type: "f", value: 1.0 },
+		"map" : { type: "t", value: null },
+
+		"fogDensity" : { type: "f", value: 0.00025 },
+		"fogNear" : { type: "f", value: 1 },
+		"fogFar" : { type: "f", value: 2000 },
+		"fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) }
+
+	},
+
+	shadowmap: {
+
+		"shadowMap": { type: "tv", value: [] },
+		"shadowMapSize": { type: "v2v", value: [] },
+
+		"shadowBias" : { type: "fv1", value: [] },
+		"shadowDarkness": { type: "fv1", value: [] },
+
+		"shadowMatrix" : { type: "m4v", value: [] }
+
+	}
+
+};
+
+THREE.ShaderLib = {
+
+	'basic': {
+
+		uniforms: THREE.UniformsUtils.merge( [
+
+			THREE.UniformsLib[ "common" ],
+			THREE.UniformsLib[ "fog" ],
+			THREE.UniformsLib[ "shadowmap" ]
+
+		] ),
+
+		vertexShader: [
+
+			THREE.ShaderChunk[ "map_pars_vertex" ],
+			THREE.ShaderChunk[ "lightmap_pars_vertex" ],
+			THREE.ShaderChunk[ "envmap_pars_vertex" ],
+			THREE.ShaderChunk[ "color_pars_vertex" ],
+			THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+			THREE.ShaderChunk[ "skinning_pars_vertex" ],
+			THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+			"void main() {",
+
+				THREE.ShaderChunk[ "map_vertex" ],
+				THREE.ShaderChunk[ "lightmap_vertex" ],
+				THREE.ShaderChunk[ "color_vertex" ],
+				THREE.ShaderChunk[ "skinbase_vertex" ],
+
+				"#ifdef USE_ENVMAP",
+
+				THREE.ShaderChunk[ "morphnormal_vertex" ],
+				THREE.ShaderChunk[ "skinnormal_vertex" ],
+				THREE.ShaderChunk[ "defaultnormal_vertex" ],
+
+				"#endif",
+
+				THREE.ShaderChunk[ "morphtarget_vertex" ],
+				THREE.ShaderChunk[ "skinning_vertex" ],
+				THREE.ShaderChunk[ "default_vertex" ],
+
+				THREE.ShaderChunk[ "worldpos_vertex" ],
+				THREE.ShaderChunk[ "envmap_vertex" ],
+				THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform vec3 diffuse;",
+			"uniform float opacity;",
+
+			THREE.ShaderChunk[ "color_pars_fragment" ],
+			THREE.ShaderChunk[ "map_pars_fragment" ],
+			THREE.ShaderChunk[ "lightmap_pars_fragment" ],
+			THREE.ShaderChunk[ "envmap_pars_fragment" ],
+			THREE.ShaderChunk[ "fog_pars_fragment" ],
+			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+			THREE.ShaderChunk[ "specularmap_pars_fragment" ],
+
+			"void main() {",
+
+				"gl_FragColor = vec4( diffuse, opacity );",
+
+				THREE.ShaderChunk[ "map_fragment" ],
+				THREE.ShaderChunk[ "alphatest_fragment" ],
+				THREE.ShaderChunk[ "specularmap_fragment" ],
+				THREE.ShaderChunk[ "lightmap_fragment" ],
+				THREE.ShaderChunk[ "color_fragment" ],
+				THREE.ShaderChunk[ "envmap_fragment" ],
+				THREE.ShaderChunk[ "shadowmap_fragment" ],
+
+				THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+
+				THREE.ShaderChunk[ "fog_fragment" ],
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	'lambert': {
+
+		uniforms: THREE.UniformsUtils.merge( [
+
+			THREE.UniformsLib[ "common" ],
+			THREE.UniformsLib[ "fog" ],
+			THREE.UniformsLib[ "lights" ],
+			THREE.UniformsLib[ "shadowmap" ],
+
+			{
+				"ambient"  : { type: "c", value: new THREE.Color( 0xffffff ) },
+				"emissive" : { type: "c", value: new THREE.Color( 0x000000 ) },
+				"wrapRGB"  : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+			}
+
+		] ),
+
+		vertexShader: [
+
+			"#define LAMBERT",
+
+			"varying vec3 vLightFront;",
+
+			"#ifdef DOUBLE_SIDED",
+
+				"varying vec3 vLightBack;",
+
+			"#endif",
+
+			THREE.ShaderChunk[ "map_pars_vertex" ],
+			THREE.ShaderChunk[ "lightmap_pars_vertex" ],
+			THREE.ShaderChunk[ "envmap_pars_vertex" ],
+			THREE.ShaderChunk[ "lights_lambert_pars_vertex" ],
+			THREE.ShaderChunk[ "color_pars_vertex" ],
+			THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+			THREE.ShaderChunk[ "skinning_pars_vertex" ],
+			THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+			"void main() {",
+
+				THREE.ShaderChunk[ "map_vertex" ],
+				THREE.ShaderChunk[ "lightmap_vertex" ],
+				THREE.ShaderChunk[ "color_vertex" ],
+
+				THREE.ShaderChunk[ "morphnormal_vertex" ],
+				THREE.ShaderChunk[ "skinbase_vertex" ],
+				THREE.ShaderChunk[ "skinnormal_vertex" ],
+				THREE.ShaderChunk[ "defaultnormal_vertex" ],
+
+				THREE.ShaderChunk[ "morphtarget_vertex" ],
+				THREE.ShaderChunk[ "skinning_vertex" ],
+				THREE.ShaderChunk[ "default_vertex" ],
+
+				THREE.ShaderChunk[ "worldpos_vertex" ],
+				THREE.ShaderChunk[ "envmap_vertex" ],
+				THREE.ShaderChunk[ "lights_lambert_vertex" ],
+				THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float opacity;",
+
+			"varying vec3 vLightFront;",
+
+			"#ifdef DOUBLE_SIDED",
+
+				"varying vec3 vLightBack;",
+
+			"#endif",
+
+			THREE.ShaderChunk[ "color_pars_fragment" ],
+			THREE.ShaderChunk[ "map_pars_fragment" ],
+			THREE.ShaderChunk[ "lightmap_pars_fragment" ],
+			THREE.ShaderChunk[ "envmap_pars_fragment" ],
+			THREE.ShaderChunk[ "fog_pars_fragment" ],
+			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+			THREE.ShaderChunk[ "specularmap_pars_fragment" ],
+
+			"void main() {",
+
+				"gl_FragColor = vec4( vec3 ( 1.0 ), opacity );",
+
+				THREE.ShaderChunk[ "map_fragment" ],
+				THREE.ShaderChunk[ "alphatest_fragment" ],
+				THREE.ShaderChunk[ "specularmap_fragment" ],
+
+				"#ifdef DOUBLE_SIDED",
+
+					//"float isFront = float( gl_FrontFacing );",
+					//"gl_FragColor.xyz *= isFront * vLightFront + ( 1.0 - isFront ) * vLightBack;",
+
+					"if ( gl_FrontFacing )",
+						"gl_FragColor.xyz *= vLightFront;",
+					"else",
+						"gl_FragColor.xyz *= vLightBack;",
+
+				"#else",
+
+					"gl_FragColor.xyz *= vLightFront;",
+
+				"#endif",
+
+				THREE.ShaderChunk[ "lightmap_fragment" ],
+				THREE.ShaderChunk[ "color_fragment" ],
+				THREE.ShaderChunk[ "envmap_fragment" ],
+				THREE.ShaderChunk[ "shadowmap_fragment" ],
+
+				THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+
+				THREE.ShaderChunk[ "fog_fragment" ],
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	'phong': {
+
+		uniforms: THREE.UniformsUtils.merge( [
+
+			THREE.UniformsLib[ "common" ],
+			THREE.UniformsLib[ "bump" ],
+			THREE.UniformsLib[ "normalmap" ],
+			THREE.UniformsLib[ "fog" ],
+			THREE.UniformsLib[ "lights" ],
+			THREE.UniformsLib[ "shadowmap" ],
+
+			{
+				"ambient"  : { type: "c", value: new THREE.Color( 0xffffff ) },
+				"emissive" : { type: "c", value: new THREE.Color( 0x000000 ) },
+				"specular" : { type: "c", value: new THREE.Color( 0x111111 ) },
+				"shininess": { type: "f", value: 30 },
+				"wrapRGB"  : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+			}
+
+		] ),
+
+		vertexShader: [
+
+			"#define PHONG",
+
+			"varying vec3 vViewPosition;",
+			"varying vec3 vNormal;",
+
+			THREE.ShaderChunk[ "map_pars_vertex" ],
+			THREE.ShaderChunk[ "lightmap_pars_vertex" ],
+			THREE.ShaderChunk[ "envmap_pars_vertex" ],
+			THREE.ShaderChunk[ "lights_phong_pars_vertex" ],
+			THREE.ShaderChunk[ "color_pars_vertex" ],
+			THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+			THREE.ShaderChunk[ "skinning_pars_vertex" ],
+			THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+			"void main() {",
+
+				THREE.ShaderChunk[ "map_vertex" ],
+				THREE.ShaderChunk[ "lightmap_vertex" ],
+				THREE.ShaderChunk[ "color_vertex" ],
+
+				THREE.ShaderChunk[ "morphnormal_vertex" ],
+				THREE.ShaderChunk[ "skinbase_vertex" ],
+				THREE.ShaderChunk[ "skinnormal_vertex" ],
+				THREE.ShaderChunk[ "defaultnormal_vertex" ],
+
+				"vNormal = normalize( transformedNormal );",
+
+				THREE.ShaderChunk[ "morphtarget_vertex" ],
+				THREE.ShaderChunk[ "skinning_vertex" ],
+				THREE.ShaderChunk[ "default_vertex" ],
+
+				"vViewPosition = -mvPosition.xyz;",
+
+				THREE.ShaderChunk[ "worldpos_vertex" ],
+				THREE.ShaderChunk[ "envmap_vertex" ],
+				THREE.ShaderChunk[ "lights_phong_vertex" ],
+				THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform vec3 diffuse;",
+			"uniform float opacity;",
+
+			"uniform vec3 ambient;",
+			"uniform vec3 emissive;",
+			"uniform vec3 specular;",
+			"uniform float shininess;",
+
+			THREE.ShaderChunk[ "color_pars_fragment" ],
+			THREE.ShaderChunk[ "map_pars_fragment" ],
+			THREE.ShaderChunk[ "lightmap_pars_fragment" ],
+			THREE.ShaderChunk[ "envmap_pars_fragment" ],
+			THREE.ShaderChunk[ "fog_pars_fragment" ],
+			THREE.ShaderChunk[ "lights_phong_pars_fragment" ],
+			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+			THREE.ShaderChunk[ "bumpmap_pars_fragment" ],
+			THREE.ShaderChunk[ "normalmap_pars_fragment" ],
+			THREE.ShaderChunk[ "specularmap_pars_fragment" ],
+
+			"void main() {",
+
+				"gl_FragColor = vec4( vec3 ( 1.0 ), opacity );",
+
+				THREE.ShaderChunk[ "map_fragment" ],
+				THREE.ShaderChunk[ "alphatest_fragment" ],
+				THREE.ShaderChunk[ "specularmap_fragment" ],
+
+				THREE.ShaderChunk[ "lights_phong_fragment" ],
+
+				THREE.ShaderChunk[ "lightmap_fragment" ],
+				THREE.ShaderChunk[ "color_fragment" ],
+				THREE.ShaderChunk[ "envmap_fragment" ],
+				THREE.ShaderChunk[ "shadowmap_fragment" ],
+
+				THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+
+				THREE.ShaderChunk[ "fog_fragment" ],
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	'particle_basic': {
+
+		uniforms:  THREE.UniformsUtils.merge( [
+
+			THREE.UniformsLib[ "particle" ],
+			THREE.UniformsLib[ "shadowmap" ]
+
+		] ),
+
+		vertexShader: [
+
+			"uniform float size;",
+			"uniform float scale;",
+
+			THREE.ShaderChunk[ "color_pars_vertex" ],
+			THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+			"void main() {",
+
+				THREE.ShaderChunk[ "color_vertex" ],
+
+				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+
+				"#ifdef USE_SIZEATTENUATION",
+					"gl_PointSize = size * ( scale / length( mvPosition.xyz ) );",
+				"#else",
+					"gl_PointSize = size;",
+				"#endif",
+
+				"gl_Position = projectionMatrix * mvPosition;",
+
+				THREE.ShaderChunk[ "worldpos_vertex" ],
+				THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform vec3 psColor;",
+			"uniform float opacity;",
+
+			THREE.ShaderChunk[ "color_pars_fragment" ],
+			THREE.ShaderChunk[ "map_particle_pars_fragment" ],
+			THREE.ShaderChunk[ "fog_pars_fragment" ],
+			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+
+			"void main() {",
+
+				"gl_FragColor = vec4( psColor, opacity );",
+
+				THREE.ShaderChunk[ "map_particle_fragment" ],
+				THREE.ShaderChunk[ "alphatest_fragment" ],
+				THREE.ShaderChunk[ "color_fragment" ],
+				THREE.ShaderChunk[ "shadowmap_fragment" ],
+				THREE.ShaderChunk[ "fog_fragment" ],
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	'dashed': {
+
+		uniforms: THREE.UniformsUtils.merge( [
+
+			THREE.UniformsLib[ "common" ],
+			THREE.UniformsLib[ "fog" ],
+
+			{
+				"scale":     { type: "f", value: 1 },
+				"dashSize":  { type: "f", value: 1 },
+				"totalSize": { type: "f", value: 2 }
+			}
+
+		] ),
+
+		vertexShader: [
+
+			"uniform float scale;",
+			"attribute float lineDistance;",
+
+			"varying float vLineDistance;",
+
+			THREE.ShaderChunk[ "color_pars_vertex" ],
+
+			"void main() {",
+
+				THREE.ShaderChunk[ "color_vertex" ],
+
+				"vLineDistance = scale * lineDistance;",
+
+				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+				"gl_Position = projectionMatrix * mvPosition;",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform vec3 diffuse;",
+			"uniform float opacity;",
+
+			"uniform float dashSize;",
+			"uniform float totalSize;",
+
+			"varying float vLineDistance;",
+
+			THREE.ShaderChunk[ "color_pars_fragment" ],
+			THREE.ShaderChunk[ "fog_pars_fragment" ],
+
+			"void main() {",
+
+				"if ( mod( vLineDistance, totalSize ) > dashSize ) {",
+
+					"discard;",
+
+				"}",
+
+				"gl_FragColor = vec4( diffuse, opacity );",
+
+				THREE.ShaderChunk[ "color_fragment" ],
+				THREE.ShaderChunk[ "fog_fragment" ],
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	'depth': {
+
+		uniforms: {
+
+			"mNear": { type: "f", value: 1.0 },
+			"mFar" : { type: "f", value: 2000.0 },
+			"opacity" : { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"void main() {",
+
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float mNear;",
+			"uniform float mFar;",
+			"uniform float opacity;",
+
+			"void main() {",
+
+				"float depth = gl_FragCoord.z / gl_FragCoord.w;",
+				"float color = 1.0 - smoothstep( mNear, mFar, depth );",
+				"gl_FragColor = vec4( vec3( color ), opacity );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	'normal': {
+
+		uniforms: {
+
+			"opacity" : { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec3 vNormal;",
+
+			THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+
+			"void main() {",
+
+				"vNormal = normalize( normalMatrix * normal );",
+
+				THREE.ShaderChunk[ "morphtarget_vertex" ],
+				THREE.ShaderChunk[ "default_vertex" ],
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float opacity;",
+			"varying vec3 vNormal;",
+
+			"void main() {",
+
+				"gl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Normal map shader
+	//		- Blinn-Phong
+	//		- normal + diffuse + specular + AO + displacement + reflection + shadow maps
+	//		- point and directional lights (use with "lights: true" material option)
+	 ------------------------------------------------------------------------- */
+
+	'normalmap' : {
+
+		uniforms: THREE.UniformsUtils.merge( [
+
+			THREE.UniformsLib[ "fog" ],
+			THREE.UniformsLib[ "lights" ],
+			THREE.UniformsLib[ "shadowmap" ],
+
+			{
+
+			"enableAO"		  : { type: "i", value: 0 },
+			"enableDiffuse"	  : { type: "i", value: 0 },
+			"enableSpecular"  : { type: "i", value: 0 },
+			"enableReflection": { type: "i", value: 0 },
+			"enableDisplacement": { type: "i", value: 0 },
+
+			"tDisplacement": { type: "t", value: null }, // must go first as this is vertex texture
+			"tDiffuse"	   : { type: "t", value: null },
+			"tCube"		   : { type: "t", value: null },
+			"tNormal"	   : { type: "t", value: null },
+			"tSpecular"	   : { type: "t", value: null },
+			"tAO"		   : { type: "t", value: null },
+
+			"uNormalScale": { type: "v2", value: new THREE.Vector2( 1, 1 ) },
+
+			"uDisplacementBias": { type: "f", value: 0.0 },
+			"uDisplacementScale": { type: "f", value: 1.0 },
+
+			"uDiffuseColor": { type: "c", value: new THREE.Color( 0xffffff ) },
+			"uSpecularColor": { type: "c", value: new THREE.Color( 0x111111 ) },
+			"uAmbientColor": { type: "c", value: new THREE.Color( 0xffffff ) },
+			"uShininess": { type: "f", value: 30 },
+			"uOpacity": { type: "f", value: 1 },
+
+			"useRefract": { type: "i", value: 0 },
+			"uRefractionRatio": { type: "f", value: 0.98 },
+			"uReflectivity": { type: "f", value: 0.5 },
+
+			"uOffset" : { type: "v2", value: new THREE.Vector2( 0, 0 ) },
+			"uRepeat" : { type: "v2", value: new THREE.Vector2( 1, 1 ) },
+
+			"wrapRGB"  : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+
+			}
+
+		] ),
+
+		fragmentShader: [
+
+			"uniform vec3 uAmbientColor;",
+			"uniform vec3 uDiffuseColor;",
+			"uniform vec3 uSpecularColor;",
+			"uniform float uShininess;",
+			"uniform float uOpacity;",
+
+			"uniform bool enableDiffuse;",
+			"uniform bool enableSpecular;",
+			"uniform bool enableAO;",
+			"uniform bool enableReflection;",
+
+			"uniform sampler2D tDiffuse;",
+			"uniform sampler2D tNormal;",
+			"uniform sampler2D tSpecular;",
+			"uniform sampler2D tAO;",
+
+			"uniform samplerCube tCube;",
+
+			"uniform vec2 uNormalScale;",
+
+			"uniform bool useRefract;",
+			"uniform float uRefractionRatio;",
+			"uniform float uReflectivity;",
+
+			"varying vec3 vTangent;",
+			"varying vec3 vBinormal;",
+			"varying vec3 vNormal;",
+			"varying vec2 vUv;",
+
+			"uniform vec3 ambientLightColor;",
+
+			"#if MAX_DIR_LIGHTS > 0",
+
+				"uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+				"uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+
+			"#endif",
+
+			"#if MAX_HEMI_LIGHTS > 0",
+
+				"uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
+				"uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
+				"uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
+
+			"#endif",
+
+			"#if MAX_POINT_LIGHTS > 0",
+
+				"uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+				"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+				"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+			"#endif",
+
+			"#if MAX_SPOT_LIGHTS > 0",
+
+				"uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];",
+				"uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+				"uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];",
+				"uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];",
+				"uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];",
+				"uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+
+			"#endif",
+
+			"#ifdef WRAP_AROUND",
+
+				"uniform vec3 wrapRGB;",
+
+			"#endif",
+
+			"varying vec3 vWorldPosition;",
+			"varying vec3 vViewPosition;",
+
+			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+			THREE.ShaderChunk[ "fog_pars_fragment" ],
+
+			"void main() {",
+
+				"gl_FragColor = vec4( vec3( 1.0 ), uOpacity );",
+
+				"vec3 specularTex = vec3( 1.0 );",
+
+				"vec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;",
+				"normalTex.xy *= uNormalScale;",
+				"normalTex = normalize( normalTex );",
+
+				"if( enableDiffuse ) {",
+
+					"#ifdef GAMMA_INPUT",
+
+						"vec4 texelColor = texture2D( tDiffuse, vUv );",
+						"texelColor.xyz *= texelColor.xyz;",
+
+						"gl_FragColor = gl_FragColor * texelColor;",
+
+					"#else",
+
+						"gl_FragColor = gl_FragColor * texture2D( tDiffuse, vUv );",
+
+					"#endif",
+
+				"}",
+
+				"if( enableAO ) {",
+
+					"#ifdef GAMMA_INPUT",
+
+						"vec4 aoColor = texture2D( tAO, vUv );",
+						"aoColor.xyz *= aoColor.xyz;",
+
+						"gl_FragColor.xyz = gl_FragColor.xyz * aoColor.xyz;",
+
+					"#else",
+
+						"gl_FragColor.xyz = gl_FragColor.xyz * texture2D( tAO, vUv ).xyz;",
+
+					"#endif",
+
+				"}",
+
+				"if( enableSpecular )",
+					"specularTex = texture2D( tSpecular, vUv ).xyz;",
+
+				"mat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( vNormal ) );",
+				"vec3 finalNormal = tsb * normalTex;",
+
+				"#ifdef FLIP_SIDED",
+
+					"finalNormal = -finalNormal;",
+
+				"#endif",
+
+				"vec3 normal = normalize( finalNormal );",
+				"vec3 viewPosition = normalize( vViewPosition );",
+
+				// point lights
+
+				"#if MAX_POINT_LIGHTS > 0",
+
+					"vec3 pointDiffuse = vec3( 0.0 );",
+					"vec3 pointSpecular = vec3( 0.0 );",
+
+					"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+						"vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+						"vec3 pointVector = lPosition.xyz + vViewPosition.xyz;",
+
+						"float pointDistance = 1.0;",
+						"if ( pointLightDistance[ i ] > 0.0 )",
+							"pointDistance = 1.0 - min( ( length( pointVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+						"pointVector = normalize( pointVector );",
+
+						// diffuse
+
+						"#ifdef WRAP_AROUND",
+
+							"float pointDiffuseWeightFull = max( dot( normal, pointVector ), 0.0 );",
+							"float pointDiffuseWeightHalf = max( 0.5 * dot( normal, pointVector ) + 0.5, 0.0 );",
+
+							"vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );",
+
+						"#else",
+
+							"float pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );",
+
+						"#endif",
+
+						"pointDiffuse += pointDistance * pointLightColor[ i ] * uDiffuseColor * pointDiffuseWeight;",
+
+						// specular
+
+						"vec3 pointHalfVector = normalize( pointVector + viewPosition );",
+						"float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );",
+						"float pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, uShininess ), 0.0 );",
+
+						"#ifdef PHYSICALLY_BASED_SHADING",
+
+							// 2.0 => 2.0001 is hack to work around ANGLE bug
+
+							"float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+							"vec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( pointVector, pointHalfVector ), 5.0 );",
+							"pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;",
+
+						"#else",
+
+							"pointSpecular += pointDistance * pointLightColor[ i ] * uSpecularColor * pointSpecularWeight * pointDiffuseWeight;",
+
+						"#endif",
+
+					"}",
+
+				"#endif",
+
+				// spot lights
+
+				"#if MAX_SPOT_LIGHTS > 0",
+
+					"vec3 spotDiffuse = vec3( 0.0 );",
+					"vec3 spotSpecular = vec3( 0.0 );",
+
+					"for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+						"vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+						"vec3 spotVector = lPosition.xyz + vViewPosition.xyz;",
+
+						"float spotDistance = 1.0;",
+						"if ( spotLightDistance[ i ] > 0.0 )",
+							"spotDistance = 1.0 - min( ( length( spotVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+						"spotVector = normalize( spotVector );",
+
+						"float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );",
+
+						"if ( spotEffect > spotLightAngleCos[ i ] ) {",
+
+							"spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );",
+
+							// diffuse
+
+							"#ifdef WRAP_AROUND",
+
+								"float spotDiffuseWeightFull = max( dot( normal, spotVector ), 0.0 );",
+								"float spotDiffuseWeightHalf = max( 0.5 * dot( normal, spotVector ) + 0.5, 0.0 );",
+
+								"vec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );",
+
+							"#else",
+
+								"float spotDiffuseWeight = max( dot( normal, spotVector ), 0.0 );",
+
+							"#endif",
+
+							"spotDiffuse += spotDistance * spotLightColor[ i ] * uDiffuseColor * spotDiffuseWeight * spotEffect;",
+
+							// specular
+
+							"vec3 spotHalfVector = normalize( spotVector + viewPosition );",
+							"float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );",
+							"float spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, uShininess ), 0.0 );",
+
+							"#ifdef PHYSICALLY_BASED_SHADING",
+
+								// 2.0 => 2.0001 is hack to work around ANGLE bug
+
+								"float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+								"vec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( spotVector, spotHalfVector ), 5.0 );",
+								"spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;",
+
+							"#else",
+
+								"spotSpecular += spotDistance * spotLightColor[ i ] * uSpecularColor * spotSpecularWeight * spotDiffuseWeight * spotEffect;",
+
+							"#endif",
+
+						"}",
+
+					"}",
+
+				"#endif",
+
+				// directional lights
+
+				"#if MAX_DIR_LIGHTS > 0",
+
+					"vec3 dirDiffuse = vec3( 0.0 );",
+					"vec3 dirSpecular = vec3( 0.0 );",
+
+					"for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
+
+						"vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+						"vec3 dirVector = normalize( lDirection.xyz );",
+
+						// diffuse
+
+						"#ifdef WRAP_AROUND",
+
+							"float directionalLightWeightingFull = max( dot( normal, dirVector ), 0.0 );",
+							"float directionalLightWeightingHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );",
+
+							"vec3 dirDiffuseWeight = mix( vec3( directionalLightWeightingFull ), vec3( directionalLightWeightingHalf ), wrapRGB );",
+
+						"#else",
+
+							"float dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );",
+
+						"#endif",
+
+						"dirDiffuse += directionalLightColor[ i ] * uDiffuseColor * dirDiffuseWeight;",
+
+						// specular
+
+						"vec3 dirHalfVector = normalize( dirVector + viewPosition );",
+						"float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
+						"float dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, uShininess ), 0.0 );",
+
+						"#ifdef PHYSICALLY_BASED_SHADING",
+
+							// 2.0 => 2.0001 is hack to work around ANGLE bug
+
+							"float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+							"vec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );",
+							"dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;",
+
+						"#else",
+
+							"dirSpecular += directionalLightColor[ i ] * uSpecularColor * dirSpecularWeight * dirDiffuseWeight;",
+
+						"#endif",
+
+					"}",
+
+				"#endif",
+
+				// hemisphere lights
+
+				"#if MAX_HEMI_LIGHTS > 0",
+
+					"vec3 hemiDiffuse  = vec3( 0.0 );",
+					"vec3 hemiSpecular = vec3( 0.0 );" ,
+
+					"for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {",
+
+						"vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );",
+						"vec3 lVector = normalize( lDirection.xyz );",
+
+						// diffuse
+
+						"float dotProduct = dot( normal, lVector );",
+						"float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
+
+						"vec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );",
+
+						"hemiDiffuse += uDiffuseColor * hemiColor;",
+
+						// specular (sky light)
+
+
+						"vec3 hemiHalfVectorSky = normalize( lVector + viewPosition );",
+						"float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;",
+						"float hemiSpecularWeightSky = specularTex.r * max( pow( hemiDotNormalHalfSky, uShininess ), 0.0 );",
+
+						// specular (ground light)
+
+						"vec3 lVectorGround = -lVector;",
+
+						"vec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );",
+						"float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;",
+						"float hemiSpecularWeightGround = specularTex.r * max( pow( hemiDotNormalHalfGround, uShininess ), 0.0 );",
+
+						"#ifdef PHYSICALLY_BASED_SHADING",
+
+							"float dotProductGround = dot( normal, lVectorGround );",
+
+							// 2.0 => 2.0001 is hack to work around ANGLE bug
+
+							"float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+							"vec3 schlickSky = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );",
+							"vec3 schlickGround = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );",
+							"hemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );",
+
+						"#else",
+
+							"hemiSpecular += uSpecularColor * hemiColor * ( hemiSpecularWeightSky + hemiSpecularWeightGround ) * hemiDiffuseWeight;",
+
+						"#endif",
+
+					"}",
+
+				"#endif",
+
+				// all lights contribution summation
+
+				"vec3 totalDiffuse = vec3( 0.0 );",
+				"vec3 totalSpecular = vec3( 0.0 );",
+
+				"#if MAX_DIR_LIGHTS > 0",
+
+					"totalDiffuse += dirDiffuse;",
+					"totalSpecular += dirSpecular;",
+
+				"#endif",
+
+				"#if MAX_HEMI_LIGHTS > 0",
+
+					"totalDiffuse += hemiDiffuse;",
+					"totalSpecular += hemiSpecular;",
+
+				"#endif",
+
+				"#if MAX_POINT_LIGHTS > 0",
+
+					"totalDiffuse += pointDiffuse;",
+					"totalSpecular += pointSpecular;",
+
+				"#endif",
+
+				"#if MAX_SPOT_LIGHTS > 0",
+
+					"totalDiffuse += spotDiffuse;",
+					"totalSpecular += spotSpecular;",
+
+				"#endif",
+
+				"#ifdef METAL",
+
+					"gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * uAmbientColor + totalSpecular );",
+
+				"#else",
+
+					"gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * uAmbientColor ) + totalSpecular;",
+
+				"#endif",
+
+				"if ( enableReflection ) {",
+
+					"vec3 vReflect;",
+					"vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );",
+
+					"if ( useRefract ) {",
+
+						"vReflect = refract( cameraToVertex, normal, uRefractionRatio );",
+
+					"} else {",
+
+						"vReflect = reflect( cameraToVertex, normal );",
+
+					"}",
+
+					"vec4 cubeColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );",
+
+					"#ifdef GAMMA_INPUT",
+
+						"cubeColor.xyz *= cubeColor.xyz;",
+
+					"#endif",
+
+					"gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularTex.r * uReflectivity );",
+
+				"}",
+
+				THREE.ShaderChunk[ "shadowmap_fragment" ],
+				THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+				THREE.ShaderChunk[ "fog_fragment" ],
+
+			"}"
+
+		].join("\n"),
+
+		vertexShader: [
+
+			"attribute vec4 tangent;",
+
+			"uniform vec2 uOffset;",
+			"uniform vec2 uRepeat;",
+
+			"uniform bool enableDisplacement;",
+
+			"#ifdef VERTEX_TEXTURES",
+
+				"uniform sampler2D tDisplacement;",
+				"uniform float uDisplacementScale;",
+				"uniform float uDisplacementBias;",
+
+			"#endif",
+
+			"varying vec3 vTangent;",
+			"varying vec3 vBinormal;",
+			"varying vec3 vNormal;",
+			"varying vec2 vUv;",
+
+			"varying vec3 vWorldPosition;",
+			"varying vec3 vViewPosition;",
+
+			THREE.ShaderChunk[ "skinning_pars_vertex" ],
+			THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+			"void main() {",
+
+				THREE.ShaderChunk[ "skinbase_vertex" ],
+				THREE.ShaderChunk[ "skinnormal_vertex" ],
+
+				// normal, tangent and binormal vectors
+
+				"#ifdef USE_SKINNING",
+
+					"vNormal = normalize( normalMatrix * skinnedNormal.xyz );",
+
+					"vec4 skinnedTangent = skinMatrix * vec4( tangent.xyz, 0.0 );",
+					"vTangent = normalize( normalMatrix * skinnedTangent.xyz );",
+
+				"#else",
+
+					"vNormal = normalize( normalMatrix * normal );",
+					"vTangent = normalize( normalMatrix * tangent.xyz );",
+
+				"#endif",
+
+				"vBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );",
+
+				"vUv = uv * uRepeat + uOffset;",
+
+				// displacement mapping
+
+				"vec3 displacedPosition;",
+
+				"#ifdef VERTEX_TEXTURES",
+
+					"if ( enableDisplacement ) {",
+
+						"vec3 dv = texture2D( tDisplacement, uv ).xyz;",
+						"float df = uDisplacementScale * dv.x + uDisplacementBias;",
+						"displacedPosition = position + normalize( normal ) * df;",
+
+					"} else {",
+
+						"#ifdef USE_SKINNING",
+
+							"vec4 skinVertex = vec4( position, 1.0 );",
+
+							"vec4 skinned  = boneMatX * skinVertex * skinWeight.x;",
+							"skinned 	  += boneMatY * skinVertex * skinWeight.y;",
+
+							"displacedPosition  = skinned.xyz;",
+
+						"#else",
+
+							"displacedPosition = position;",
+
+						"#endif",
+
+					"}",
+
+				"#else",
+
+					"#ifdef USE_SKINNING",
+
+						"vec4 skinVertex = vec4( position, 1.0 );",
+
+						"vec4 skinned  = boneMatX * skinVertex * skinWeight.x;",
+						"skinned 	  += boneMatY * skinVertex * skinWeight.y;",
+
+						"displacedPosition  = skinned.xyz;",
+
+					"#else",
+
+						"displacedPosition = position;",
+
+					"#endif",
+
+				"#endif",
+
+				//
+
+				"vec4 mvPosition = modelViewMatrix * vec4( displacedPosition, 1.0 );",
+				"vec4 worldPosition = modelMatrix * vec4( displacedPosition, 1.0 );",
+
+				"gl_Position = projectionMatrix * mvPosition;",
+
+				//
+
+				"vWorldPosition = worldPosition.xyz;",
+				"vViewPosition = -mvPosition.xyz;",
+
+				// shadows
+
+				"#ifdef USE_SHADOWMAP",
+
+					"for( int i = 0; i < MAX_SHADOWS; i ++ ) {",
+
+						"vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;",
+
+					"}",
+
+				"#endif",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Cube map shader
+	 ------------------------------------------------------------------------- */
+
+	'cube': {
+
+		uniforms: { "tCube": { type: "t", value: null },
+					"tFlip": { type: "f", value: -1 } },
+
+		vertexShader: [
+
+			"varying vec3 vWorldPosition;",
+
+			"void main() {",
+
+				"vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
+				"vWorldPosition = worldPosition.xyz;",
+
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform samplerCube tCube;",
+			"uniform float tFlip;",
+
+			"varying vec3 vWorldPosition;",
+
+			"void main() {",
+
+				"gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	// Depth encoding into RGBA texture
+	// 	based on SpiderGL shadow map example
+	// 		http://spidergl.org/example.php?id=6
+	// 	originally from
+	//		http://www.gamedev.net/topic/442138-packing-a-float-into-a-a8r8g8b8-texture-shader/page__whichpage__1%25EF%25BF%25BD
+	// 	see also here:
+	//		http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/
+
+	'depthRGBA': {
+
+		uniforms: {},
+
+		vertexShader: [
+
+			THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+			THREE.ShaderChunk[ "skinning_pars_vertex" ],
+
+			"void main() {",
+
+				THREE.ShaderChunk[ "skinbase_vertex" ],
+				THREE.ShaderChunk[ "morphtarget_vertex" ],
+				THREE.ShaderChunk[ "skinning_vertex" ],
+				THREE.ShaderChunk[ "default_vertex" ],
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"vec4 pack_depth( const in float depth ) {",
+
+				"const vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );",
+				"const vec4 bit_mask  = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );",
+				"vec4 res = fract( depth * bit_shift );",
+				"res -= res.xxyz * bit_mask;",
+				"return res;",
+
+			"}",
+
+			"void main() {",
+
+				"gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );",
+
+				//"gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z / gl_FragCoord.w );",
+				//"float z = ( ( gl_FragCoord.z / gl_FragCoord.w ) - 3.0 ) / ( 4000.0 - 3.0 );",
+				//"gl_FragData[ 0 ] = pack_depth( z );",
+				//"gl_FragData[ 0 ] = vec4( z, z, z, 1.0 );",
+
+			"}"
+
+		].join("\n")
+
+	}
+
+};
+/**
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author szimek / https://github.com/szimek/
+ */
+
+THREE.WebGLRenderer = function ( parameters ) {
+
+	console.log( 'THREE.WebGLRenderer', THREE.REVISION );
+
+	parameters = parameters || {};
+
+	var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElement( 'canvas' ),
+
+	_precision = parameters.precision !== undefined ? parameters.precision : 'highp',
+
+	_alpha = parameters.alpha !== undefined ? parameters.alpha : true,
+	_premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true,
+	_antialias = parameters.antialias !== undefined ? parameters.antialias : false,
+	_stencil = parameters.stencil !== undefined ? parameters.stencil : true,
+	_preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false,
+
+	_clearColor = new THREE.Color( 0x000000 ),
+	_clearAlpha = 0;
+
+	if ( parameters.clearColor !== undefined ) {
+
+		console.warn( 'DEPRECATED: clearColor in WebGLRenderer constructor parameters is being removed. Use .setClearColor() instead.' );
+		_clearColor.setHex( parameters.clearColor );
+
+	}
+
+	if ( parameters.clearAlpha !== undefined ) {
+
+		console.warn( 'DEPRECATED: clearAlpha in WebGLRenderer constructor parameters is being removed. Use .setClearColor() instead.' );
+		_clearAlpha = parameters.clearAlpha;
+
+	}
+
+	// public properties
+
+	this.domElement = _canvas;
+	this.context = null;
+	this.devicePixelRatio = parameters.devicePixelRatio !== undefined
+				? parameters.devicePixelRatio
+				: window.devicePixelRatio !== undefined
+					? window.devicePixelRatio
+					: 1;
+
+	// clearing
+
+	this.autoClear = true;
+	this.autoClearColor = true;
+	this.autoClearDepth = true;
+	this.autoClearStencil = true;
+
+	// scene graph
+
+	this.sortObjects = true;
+	this.autoUpdateObjects = true;
+
+	// physically based shading
+
+	this.gammaInput = false;
+	this.gammaOutput = false;
+	this.physicallyBasedShading = false;
+
+	// shadow map
+
+	this.shadowMapEnabled = false;
+	this.shadowMapAutoUpdate = true;
+	this.shadowMapType = THREE.PCFShadowMap;
+	this.shadowMapCullFace = THREE.CullFaceFront;
+	this.shadowMapDebug = false;
+	this.shadowMapCascade = false;
+
+	// morphs
+
+	this.maxMorphTargets = 8;
+	this.maxMorphNormals = 4;
+
+	// flags
+
+	this.autoScaleCubemaps = true;
+
+	// custom render plugins
+
+	this.renderPluginsPre = [];
+	this.renderPluginsPost = [];
+
+	// info
+
+	this.info = {
+
+		memory: {
+
+			programs: 0,
+			geometries: 0,
+			textures: 0
+
+		},
+
+		render: {
+
+			calls: 0,
+			vertices: 0,
+			faces: 0,
+			points: 0
+
+		}
+
+	};
+
+	// internal properties
+
+	var _this = this,
+
+	_programs = [],
+	_programs_counter = 0,
+
+	// internal state cache
+
+	_currentProgram = null,
+	_currentFramebuffer = null,
+	_currentMaterialId = -1,
+	_currentGeometryGroupHash = null,
+	_currentCamera = null,
+	_geometryGroupCounter = 0,
+
+	_usedTextureUnits = 0,
+
+	// GL state cache
+
+	_oldDoubleSided = -1,
+	_oldFlipSided = -1,
+
+	_oldBlending = -1,
+
+	_oldBlendEquation = -1,
+	_oldBlendSrc = -1,
+	_oldBlendDst = -1,
+
+	_oldDepthTest = -1,
+	_oldDepthWrite = -1,
+
+	_oldPolygonOffset = null,
+	_oldPolygonOffsetFactor = null,
+	_oldPolygonOffsetUnits = null,
+
+	_oldLineWidth = null,
+
+	_viewportX = 0,
+	_viewportY = 0,
+	_viewportWidth = 0,
+	_viewportHeight = 0,
+	_currentWidth = 0,
+	_currentHeight = 0,
+
+	_enabledAttributes = {},
+
+	// frustum
+
+	_frustum = new THREE.Frustum(),
+
+	 // camera matrices cache
+
+	_projScreenMatrix = new THREE.Matrix4(),
+	_projScreenMatrixPS = new THREE.Matrix4(),
+
+	_vector3 = new THREE.Vector3(),
+
+	// light arrays cache
+
+	_direction = new THREE.Vector3(),
+
+	_lightsNeedUpdate = true,
+
+	_lights = {
+
+		ambient: [ 0, 0, 0 ],
+		directional: { length: 0, colors: new Array(), positions: new Array() },
+		point: { length: 0, colors: new Array(), positions: new Array(), distances: new Array() },
+		spot: { length: 0, colors: new Array(), positions: new Array(), distances: new Array(), directions: new Array(), anglesCos: new Array(), exponents: new Array() },
+		hemi: { length: 0, skyColors: new Array(), groundColors: new Array(), positions: new Array() }
+
+	};
+
+	// initialize
+
+	var _gl;
+
+	var _glExtensionTextureFloat;
+	var _glExtensionStandardDerivatives;
+	var _glExtensionTextureFilterAnisotropic;
+	var _glExtensionCompressedTextureS3TC;
+
+	initGL();
+
+	setDefaultGLState();
+
+	this.context = _gl;
+
+	// GPU capabilities
+
+	var _maxTextures = _gl.getParameter( _gl.MAX_TEXTURE_IMAGE_UNITS );
+	var _maxVertexTextures = _gl.getParameter( _gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS );
+	var _maxTextureSize = _gl.getParameter( _gl.MAX_TEXTURE_SIZE );
+	var _maxCubemapSize = _gl.getParameter( _gl.MAX_CUBE_MAP_TEXTURE_SIZE );
+
+	var _maxAnisotropy = _glExtensionTextureFilterAnisotropic ? _gl.getParameter( _glExtensionTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT ) : 0;
+
+	var _supportsVertexTextures = ( _maxVertexTextures > 0 );
+	var _supportsBoneTextures = _supportsVertexTextures && _glExtensionTextureFloat;
+
+	var _compressedTextureFormats = _glExtensionCompressedTextureS3TC ? _gl.getParameter( _gl.COMPRESSED_TEXTURE_FORMATS ) : [];
+
+	//
+
+	var _vertexShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.HIGH_FLOAT );
+	var _vertexShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.MEDIUM_FLOAT );
+	var _vertexShaderPrecisionLowpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.LOW_FLOAT );
+
+	var _fragmentShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.HIGH_FLOAT );
+	var _fragmentShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.MEDIUM_FLOAT );
+	var _fragmentShaderPrecisionLowpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.LOW_FLOAT );
+
+	var _vertexShaderPrecisionHighpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.HIGH_INT );
+	var _vertexShaderPrecisionMediumpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.MEDIUM_INT );
+	var _vertexShaderPrecisionLowpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.LOW_INT );
+
+	var _fragmentShaderPrecisionHighpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.HIGH_INT );
+	var _fragmentShaderPrecisionMediumpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.MEDIUM_INT );
+	var _fragmentShaderPrecisionLowpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.LOW_INT );
+
+	// clamp precision to maximum available
+
+	var highpAvailable = _vertexShaderPrecisionHighpFloat.precision > 0 && _fragmentShaderPrecisionHighpFloat.precision > 0;
+	var mediumpAvailable = _vertexShaderPrecisionMediumpFloat.precision > 0 && _fragmentShaderPrecisionMediumpFloat.precision > 0;
+
+	if ( _precision === "highp" && ! highpAvailable ) {
+
+		if ( mediumpAvailable ) {
+
+			_precision = "mediump";
+			console.warn( "WebGLRenderer: highp not supported, using mediump" );
+
+		} else {
+
+			_precision = "lowp";
+			console.warn( "WebGLRenderer: highp and mediump not supported, using lowp" );
+
+		}
+
+	}
+
+	if ( _precision === "mediump" && ! mediumpAvailable ) {
+
+		_precision = "lowp";
+		console.warn( "WebGLRenderer: mediump not supported, using lowp" );
+
+	}
+
+	// API
+
+	this.getContext = function () {
+
+		return _gl;
+
+	};
+
+	this.supportsVertexTextures = function () {
+
+		return _supportsVertexTextures;
+
+	};
+
+	this.supportsFloatTextures = function () {
+
+		return _glExtensionTextureFloat;
+
+	};
+
+	this.supportsStandardDerivatives = function () {
+
+		return _glExtensionStandardDerivatives;
+
+	};
+
+	this.supportsCompressedTextureS3TC = function () {
+
+		return _glExtensionCompressedTextureS3TC;
+
+	};
+
+	this.getMaxAnisotropy  = function () {
+
+		return _maxAnisotropy;
+
+	};
+
+	this.getPrecision = function () {
+
+		return _precision;
+
+	};
+
+	this.setSize = function ( width, height, updateStyle ) {
+
+		_canvas.width = width * this.devicePixelRatio;
+		_canvas.height = height * this.devicePixelRatio;
+
+		if ( this.devicePixelRatio !== 1 && updateStyle !== false ) {
+
+			_canvas.style.width = width + 'px';
+			_canvas.style.height = height + 'px';
+
+		}
+
+		this.setViewport( 0, 0, _canvas.width, _canvas.height );
+
+	};
+
+	this.setViewport = function ( x, y, width, height ) {
+
+		_viewportX = x !== undefined ? x : 0;
+		_viewportY = y !== undefined ? y : 0;
+
+		_viewportWidth = width !== undefined ? width : _canvas.width;
+		_viewportHeight = height !== undefined ? height : _canvas.height;
+
+		_gl.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight );
+
+	};
+
+	this.setScissor = function ( x, y, width, height ) {
+
+		_gl.scissor( x, y, width, height );
+
+	};
+
+	this.enableScissorTest = function ( enable ) {
+
+		enable ? _gl.enable( _gl.SCISSOR_TEST ) : _gl.disable( _gl.SCISSOR_TEST );
+
+	};
+
+	// Clearing
+
+	this.setClearColor = function ( color, alpha ) {
+
+		_clearColor.set( color );
+		_clearAlpha = alpha !== undefined ? alpha : 1;
+
+		_gl.clearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha );
+
+	};
+
+	this.setClearColorHex = function ( hex, alpha ) {
+
+		console.warn( 'DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.' );
+		this.setClearColor( hex, alpha );
+
+	};
+
+	this.getClearColor = function () {
+
+		return _clearColor;
+
+	};
+
+	this.getClearAlpha = function () {
+
+		return _clearAlpha;
+
+	};
+
+	this.clear = function ( color, depth, stencil ) {
+
+		var bits = 0;
+
+		if ( color === undefined || color ) bits |= _gl.COLOR_BUFFER_BIT;
+		if ( depth === undefined || depth ) bits |= _gl.DEPTH_BUFFER_BIT;
+		if ( stencil === undefined || stencil ) bits |= _gl.STENCIL_BUFFER_BIT;
+
+		_gl.clear( bits );
+
+	};
+
+	this.clearTarget = function ( renderTarget, color, depth, stencil ) {
+
+		this.setRenderTarget( renderTarget );
+		this.clear( color, depth, stencil );
+
+	};
+
+	// Plugins
+
+	this.addPostPlugin = function ( plugin ) {
+
+		plugin.init( this );
+		this.renderPluginsPost.push( plugin );
+
+	};
+
+	this.addPrePlugin = function ( plugin ) {
+
+		plugin.init( this );
+		this.renderPluginsPre.push( plugin );
+
+	};
+
+	// Rendering
+
+	this.updateShadowMap = function ( scene, camera ) {
+
+		_currentProgram = null;
+		_oldBlending = -1;
+		_oldDepthTest = -1;
+		_oldDepthWrite = -1;
+		_currentGeometryGroupHash = -1;
+		_currentMaterialId = -1;
+		_lightsNeedUpdate = true;
+		_oldDoubleSided = -1;
+		_oldFlipSided = -1;
+
+		this.shadowMapPlugin.update( scene, camera );
+
+	};
+
+	// Internal functions
+
+	// Buffer allocation
+
+	function createParticleBuffers ( geometry ) {
+
+		geometry.__webglVertexBuffer = _gl.createBuffer();
+		geometry.__webglColorBuffer = _gl.createBuffer();
+
+		_this.info.memory.geometries ++;
+
+	};
+
+	function createLineBuffers ( geometry ) {
+
+		geometry.__webglVertexBuffer = _gl.createBuffer();
+		geometry.__webglColorBuffer = _gl.createBuffer();
+		geometry.__webglLineDistanceBuffer = _gl.createBuffer();
+
+		_this.info.memory.geometries ++;
+
+	};
+
+	function createRibbonBuffers ( geometry ) {
+
+		geometry.__webglVertexBuffer = _gl.createBuffer();
+		geometry.__webglColorBuffer = _gl.createBuffer();
+		geometry.__webglNormalBuffer = _gl.createBuffer();
+
+		_this.info.memory.geometries ++;
+
+	};
+
+	function createMeshBuffers ( geometryGroup ) {
+
+		geometryGroup.__webglVertexBuffer = _gl.createBuffer();
+		geometryGroup.__webglNormalBuffer = _gl.createBuffer();
+		geometryGroup.__webglTangentBuffer = _gl.createBuffer();
+		geometryGroup.__webglColorBuffer = _gl.createBuffer();
+		geometryGroup.__webglUVBuffer = _gl.createBuffer();
+		geometryGroup.__webglUV2Buffer = _gl.createBuffer();
+
+		geometryGroup.__webglSkinIndicesBuffer = _gl.createBuffer();
+		geometryGroup.__webglSkinWeightsBuffer = _gl.createBuffer();
+
+		geometryGroup.__webglFaceBuffer = _gl.createBuffer();
+		geometryGroup.__webglLineBuffer = _gl.createBuffer();
+
+		var m, ml;
+
+		if ( geometryGroup.numMorphTargets ) {
+
+			geometryGroup.__webglMorphTargetsBuffers = [];
+
+			for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) {
+
+				geometryGroup.__webglMorphTargetsBuffers.push( _gl.createBuffer() );
+
+			}
+
+		}
+
+		if ( geometryGroup.numMorphNormals ) {
+
+			geometryGroup.__webglMorphNormalsBuffers = [];
+
+			for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) {
+
+				geometryGroup.__webglMorphNormalsBuffers.push( _gl.createBuffer() );
+
+			}
+
+		}
+
+		_this.info.memory.geometries ++;
+
+	};
+
+	// Events
+
+	var onGeometryDispose = function ( event ) {
+
+		var geometry = event.target;
+
+		geometry.removeEventListener( 'dispose', onGeometryDispose );
+
+		deallocateGeometry( geometry );
+
+		_this.info.memory.geometries --;
+
+	};
+
+	var onTextureDispose = function ( event ) {
+
+		var texture = event.target;
+
+		texture.removeEventListener( 'dispose', onTextureDispose );
+
+		deallocateTexture( texture );
+
+		_this.info.memory.textures --;
+
+
+	};
+
+	var onRenderTargetDispose = function ( event ) {
+
+		var renderTarget = event.target;
+
+		renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );
+
+		deallocateRenderTarget( renderTarget );
+
+		_this.info.memory.textures --;
+
+	};
+
+	var onMaterialDispose = function ( event ) {
+
+		var material = event.target;
+
+		material.removeEventListener( 'dispose', onMaterialDispose );
+
+		deallocateMaterial( material );
+
+	};
+
+	// Buffer deallocation
+
+	var deallocateGeometry = function ( geometry ) {
+
+		geometry.__webglInit = undefined;
+
+		if ( geometry.__webglVertexBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglVertexBuffer );
+		if ( geometry.__webglNormalBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglNormalBuffer );
+		if ( geometry.__webglTangentBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglTangentBuffer );
+		if ( geometry.__webglColorBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglColorBuffer );
+		if ( geometry.__webglUVBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglUVBuffer );
+		if ( geometry.__webglUV2Buffer !== undefined ) _gl.deleteBuffer( geometry.__webglUV2Buffer );
+
+		if ( geometry.__webglSkinIndicesBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglSkinIndicesBuffer );
+		if ( geometry.__webglSkinWeightsBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglSkinWeightsBuffer );
+
+		if ( geometry.__webglFaceBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglFaceBuffer );
+		if ( geometry.__webglLineBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglLineBuffer );
+
+		if ( geometry.__webglLineDistanceBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglLineDistanceBuffer );
+
+		// geometry groups
+
+		if ( geometry.geometryGroups !== undefined ) {
+
+			for ( var g in geometry.geometryGroups ) {
+
+				var geometryGroup = geometry.geometryGroups[ g ];
+
+				if ( geometryGroup.numMorphTargets !== undefined ) {
+
+					for ( var m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) {
+
+						_gl.deleteBuffer( geometryGroup.__webglMorphTargetsBuffers[ m ] );
+
+					}
+
+				}
+
+				if ( geometryGroup.numMorphNormals !== undefined ) {
+
+					for ( var m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) {
+
+						_gl.deleteBuffer( geometryGroup.__webglMorphNormalsBuffers[ m ] );
+
+					}
+
+				}
+
+				deleteCustomAttributesBuffers( geometryGroup );
+
+			}
+
+		}
+
+		deleteCustomAttributesBuffers( geometry );
+
+	};
+
+	var deallocateTexture = function ( texture ) {
+
+		if ( texture.image && texture.image.__webglTextureCube ) {
+
+			// cube texture
+
+			_gl.deleteTexture( texture.image.__webglTextureCube );
+
+		} else {
+
+			// 2D texture
+
+			if ( ! texture.__webglInit ) return;
+
+			texture.__webglInit = false;
+			_gl.deleteTexture( texture.__webglTexture );
+
+		}
+
+	};
+
+	var deallocateRenderTarget = function ( renderTarget ) {
+
+		if ( !renderTarget || ! renderTarget.__webglTexture ) return;
+
+		_gl.deleteTexture( renderTarget.__webglTexture );
+
+		if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) {
+
+			for ( var i = 0; i < 6; i ++ ) {
+
+				_gl.deleteFramebuffer( renderTarget.__webglFramebuffer[ i ] );
+				_gl.deleteRenderbuffer( renderTarget.__webglRenderbuffer[ i ] );
+
+			}
+
+		} else {
+
+			_gl.deleteFramebuffer( renderTarget.__webglFramebuffer );
+			_gl.deleteRenderbuffer( renderTarget.__webglRenderbuffer );
+
+		}
+
+	};
+
+	var deallocateMaterial = function ( material ) {
+
+		var program = material.program;
+
+		if ( program === undefined ) return;
+
+		material.program = undefined;
+
+		// only deallocate GL program if this was the last use of shared program
+		// assumed there is only single copy of any program in the _programs list
+		// (that's how it's constructed)
+
+		var i, il, programInfo;
+		var deleteProgram = false;
+
+		for ( i = 0, il = _programs.length; i < il; i ++ ) {
+
+			programInfo = _programs[ i ];
+
+			if ( programInfo.program === program ) {
+
+				programInfo.usedTimes --;
+
+				if ( programInfo.usedTimes === 0 ) {
+
+					deleteProgram = true;
+
+				}
+
+				break;
+
+			}
+
+		}
+
+		if ( deleteProgram === true ) {
+
+			// avoid using array.splice, this is costlier than creating new array from scratch
+
+			var newPrograms = [];
+
+			for ( i = 0, il = _programs.length; i < il; i ++ ) {
+
+				programInfo = _programs[ i ];
+
+				if ( programInfo.program !== program ) {
+
+					newPrograms.push( programInfo );
+
+				}
+
+			}
+
+			_programs = newPrograms;
+
+			_gl.deleteProgram( program );
+
+			_this.info.memory.programs --;
+
+		}
+
+	};
+
+	//
+
+	/*
+	function deleteParticleBuffers ( geometry ) {
+
+		_gl.deleteBuffer( geometry.__webglVertexBuffer );
+		_gl.deleteBuffer( geometry.__webglColorBuffer );
+
+		deleteCustomAttributesBuffers( geometry );
+
+		_this.info.memory.geometries --;
+
+	};
+
+	function deleteLineBuffers ( geometry ) {
+
+		_gl.deleteBuffer( geometry.__webglVertexBuffer );
+		_gl.deleteBuffer( geometry.__webglColorBuffer );
+		_gl.deleteBuffer( geometry.__webglLineDistanceBuffer );
+
+		deleteCustomAttributesBuffers( geometry );
+
+		_this.info.memory.geometries --;
+
+	};
+
+	function deleteRibbonBuffers ( geometry ) {
+
+		_gl.deleteBuffer( geometry.__webglVertexBuffer );
+		_gl.deleteBuffer( geometry.__webglColorBuffer );
+		_gl.deleteBuffer( geometry.__webglNormalBuffer );
+
+		deleteCustomAttributesBuffers( geometry );
+
+		_this.info.memory.geometries --;
+
+	};
+
+	function deleteMeshBuffers ( geometryGroup ) {
+
+		_gl.deleteBuffer( geometryGroup.__webglVertexBuffer );
+		_gl.deleteBuffer( geometryGroup.__webglNormalBuffer );
+		_gl.deleteBuffer( geometryGroup.__webglTangentBuffer );
+		_gl.deleteBuffer( geometryGroup.__webglColorBuffer );
+		_gl.deleteBuffer( geometryGroup.__webglUVBuffer );
+		_gl.deleteBuffer( geometryGroup.__webglUV2Buffer );
+
+		_gl.deleteBuffer( geometryGroup.__webglSkinIndicesBuffer );
+		_gl.deleteBuffer( geometryGroup.__webglSkinWeightsBuffer );
+
+		_gl.deleteBuffer( geometryGroup.__webglFaceBuffer );
+		_gl.deleteBuffer( geometryGroup.__webglLineBuffer );
+
+		var m, ml;
+
+		if ( geometryGroup.numMorphTargets ) {
+
+			for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) {
+
+				_gl.deleteBuffer( geometryGroup.__webglMorphTargetsBuffers[ m ] );
+
+			}
+
+		}
+
+		if ( geometryGroup.numMorphNormals ) {
+
+			for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) {
+
+				_gl.deleteBuffer( geometryGroup.__webglMorphNormalsBuffers[ m ] );
+
+			}
+
+		}
+
+		deleteCustomAttributesBuffers( geometryGroup );
+
+		_this.info.memory.geometries --;
+
+	};
+	*/
+
+	function deleteCustomAttributesBuffers( geometry ) {
+
+		if ( geometry.__webglCustomAttributesList ) {
+
+			for ( var id in geometry.__webglCustomAttributesList ) {
+
+				_gl.deleteBuffer( geometry.__webglCustomAttributesList[ id ].buffer );
+
+			}
+
+		}
+
+	};
+
+	// Buffer initialization
+
+	function initCustomAttributes ( geometry, object ) {
+
+		var nvertices = geometry.vertices.length;
+
+		var material = object.material;
+
+		if ( material.attributes ) {
+
+			if ( geometry.__webglCustomAttributesList === undefined ) {
+
+				geometry.__webglCustomAttributesList = [];
+
+			}
+
+			for ( var a in material.attributes ) {
+
+				var attribute = material.attributes[ a ];
+
+				if ( !attribute.__webglInitialized || attribute.createUniqueBuffers ) {
+
+					attribute.__webglInitialized = true;
+
+					var size = 1;		// "f" and "i"
+
+					if ( attribute.type === "v2" ) size = 2;
+					else if ( attribute.type === "v3" ) size = 3;
+					else if ( attribute.type === "v4" ) size = 4;
+					else if ( attribute.type === "c"  ) size = 3;
+
+					attribute.size = size;
+
+					attribute.array = new Float32Array( nvertices * size );
+
+					attribute.buffer = _gl.createBuffer();
+					attribute.buffer.belongsToAttribute = a;
+
+					attribute.needsUpdate = true;
+
+				}
+
+				geometry.__webglCustomAttributesList.push( attribute );
+
+			}
+
+		}
+
+	};
+
+	function initParticleBuffers ( geometry, object ) {
+
+		var nvertices = geometry.vertices.length;
+
+		geometry.__vertexArray = new Float32Array( nvertices * 3 );
+		geometry.__colorArray = new Float32Array( nvertices * 3 );
+
+		geometry.__sortArray = [];
+
+		geometry.__webglParticleCount = nvertices;
+
+		initCustomAttributes ( geometry, object );
+
+	};
+
+	function initLineBuffers ( geometry, object ) {
+
+		var nvertices = geometry.vertices.length;
+
+		geometry.__vertexArray = new Float32Array( nvertices * 3 );
+		geometry.__colorArray = new Float32Array( nvertices * 3 );
+		geometry.__lineDistanceArray = new Float32Array( nvertices * 1 );
+
+		geometry.__webglLineCount = nvertices;
+
+		initCustomAttributes ( geometry, object );
+
+	};
+
+	function initRibbonBuffers ( geometry, object ) {
+
+		var nvertices = geometry.vertices.length;
+
+		geometry.__vertexArray = new Float32Array( nvertices * 3 );
+		geometry.__colorArray = new Float32Array( nvertices * 3 );
+		geometry.__normalArray = new Float32Array( nvertices * 3 );
+
+		geometry.__webglVertexCount = nvertices;
+
+		initCustomAttributes ( geometry, object );
+
+	};
+
+	function initMeshBuffers ( geometryGroup, object ) {
+
+		var geometry = object.geometry,
+			faces3 = geometryGroup.faces3,
+			faces4 = geometryGroup.faces4,
+
+			nvertices = faces3.length * 3 + faces4.length * 4,
+			ntris     = faces3.length * 1 + faces4.length * 2,
+			nlines    = faces3.length * 3 + faces4.length * 4,
+
+			material = getBufferMaterial( object, geometryGroup ),
+
+			uvType = bufferGuessUVType( material ),
+			normalType = bufferGuessNormalType( material ),
+			vertexColorType = bufferGuessVertexColorType( material );
+
+		// console.log( "uvType", uvType, "normalType", normalType, "vertexColorType", vertexColorType, object, geometryGroup, material );
+
+		geometryGroup.__vertexArray = new Float32Array( nvertices * 3 );
+
+		if ( normalType ) {
+
+			geometryGroup.__normalArray = new Float32Array( nvertices * 3 );
+
+		}
+
+		if ( geometry.hasTangents ) {
+
+			geometryGroup.__tangentArray = new Float32Array( nvertices * 4 );
+
+		}
+
+		if ( vertexColorType ) {
+
+			geometryGroup.__colorArray = new Float32Array( nvertices * 3 );
+
+		}
+
+		if ( uvType ) {
+
+			if ( geometry.faceUvs.length > 0 || geometry.faceVertexUvs.length > 0 ) {
+
+				geometryGroup.__uvArray = new Float32Array( nvertices * 2 );
+
+			}
+
+			if ( geometry.faceUvs.length > 1 || geometry.faceVertexUvs.length > 1 ) {
+
+				geometryGroup.__uv2Array = new Float32Array( nvertices * 2 );
+
+			}
+
+		}
+
+		if ( object.geometry.skinWeights.length && object.geometry.skinIndices.length ) {
+
+			geometryGroup.__skinIndexArray = new Float32Array( nvertices * 4 );
+			geometryGroup.__skinWeightArray = new Float32Array( nvertices * 4 );
+
+		}
+
+		geometryGroup.__faceArray = new Uint16Array( ntris * 3 );
+		geometryGroup.__lineArray = new Uint16Array( nlines * 2 );
+
+		var m, ml;
+
+		if ( geometryGroup.numMorphTargets ) {
+
+			geometryGroup.__morphTargetsArrays = [];
+
+			for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) {
+
+				geometryGroup.__morphTargetsArrays.push( new Float32Array( nvertices * 3 ) );
+
+			}
+
+		}
+
+		if ( geometryGroup.numMorphNormals ) {
+
+			geometryGroup.__morphNormalsArrays = [];
+
+			for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) {
+
+				geometryGroup.__morphNormalsArrays.push( new Float32Array( nvertices * 3 ) );
+
+			}
+
+		}
+
+		geometryGroup.__webglFaceCount = ntris * 3;
+		geometryGroup.__webglLineCount = nlines * 2;
+
+
+		// custom attributes
+
+		if ( material.attributes ) {
+
+			if ( geometryGroup.__webglCustomAttributesList === undefined ) {
+
+				geometryGroup.__webglCustomAttributesList = [];
+
+			}
+
+			for ( var a in material.attributes ) {
+
+				// Do a shallow copy of the attribute object so different geometryGroup chunks use different
+				// attribute buffers which are correctly indexed in the setMeshBuffers function
+
+				var originalAttribute = material.attributes[ a ];
+
+				var attribute = {};
+
+				for ( var property in originalAttribute ) {
+
+					attribute[ property ] = originalAttribute[ property ];
+
+				}
+
+				if ( !attribute.__webglInitialized || attribute.createUniqueBuffers ) {
+
+					attribute.__webglInitialized = true;
+
+					var size = 1;		// "f" and "i"
+
+					if( attribute.type === "v2" ) size = 2;
+					else if( attribute.type === "v3" ) size = 3;
+					else if( attribute.type === "v4" ) size = 4;
+					else if( attribute.type === "c"  ) size = 3;
+
+					attribute.size = size;
+
+					attribute.array = new Float32Array( nvertices * size );
+
+					attribute.buffer = _gl.createBuffer();
+					attribute.buffer.belongsToAttribute = a;
+
+					originalAttribute.needsUpdate = true;
+					attribute.__original = originalAttribute;
+
+				}
+
+				geometryGroup.__webglCustomAttributesList.push( attribute );
+
+			}
+
+		}
+
+		geometryGroup.__inittedArrays = true;
+
+	};
+
+	function getBufferMaterial( object, geometryGroup ) {
+
+		return object.material instanceof THREE.MeshFaceMaterial
+			? object.material.materials[ geometryGroup.materialIndex ]
+			: object.material;
+
+	};
+
+	function materialNeedsSmoothNormals ( material ) {
+
+		return material && material.shading !== undefined && material.shading === THREE.SmoothShading;
+
+	};
+
+	function bufferGuessNormalType ( material ) {
+
+		// only MeshBasicMaterial and MeshDepthMaterial don't need normals
+
+		if ( ( material instanceof THREE.MeshBasicMaterial && !material.envMap ) || material instanceof THREE.MeshDepthMaterial ) {
+
+			return false;
+
+		}
+
+		if ( materialNeedsSmoothNormals( material ) ) {
+
+			return THREE.SmoothShading;
+
+		} else {
+
+			return THREE.FlatShading;
+
+		}
+
+	};
+
+	function bufferGuessVertexColorType( material ) {
+
+		if ( material.vertexColors ) {
+
+			return material.vertexColors;
+
+		}
+
+		return false;
+
+	};
+
+	function bufferGuessUVType( material ) {
+
+		// material must use some texture to require uvs
+
+		if ( material.map ||
+		     material.lightMap ||
+		     material.bumpMap ||
+		     material.normalMap ||
+		     material.specularMap ||
+		     material instanceof THREE.ShaderMaterial ) {
+
+			return true;
+
+		}
+
+		return false;
+
+	};
+
+	//
+
+	function initDirectBuffers( geometry ) {
+
+		var a, attribute, type;
+
+		for ( a in geometry.attributes ) {
+
+			if ( a === "index" ) {
+
+				type = _gl.ELEMENT_ARRAY_BUFFER;
+
+			} else {
+
+				type = _gl.ARRAY_BUFFER;
+
+			}
+
+			attribute = geometry.attributes[ a ];
+
+			attribute.buffer = _gl.createBuffer();
+
+			_gl.bindBuffer( type, attribute.buffer );
+			_gl.bufferData( type, attribute.array, _gl.STATIC_DRAW );
+
+		}
+
+	};
+
+	// Buffer setting
+
+	function setParticleBuffers ( geometry, hint, object ) {
+
+		var v, c, vertex, offset, index, color,
+
+		vertices = geometry.vertices,
+		vl = vertices.length,
+
+		colors = geometry.colors,
+		cl = colors.length,
+
+		vertexArray = geometry.__vertexArray,
+		colorArray = geometry.__colorArray,
+
+		sortArray = geometry.__sortArray,
+
+		dirtyVertices = geometry.verticesNeedUpdate,
+		dirtyElements = geometry.elementsNeedUpdate,
+		dirtyColors = geometry.colorsNeedUpdate,
+
+		customAttributes = geometry.__webglCustomAttributesList,
+		i, il,
+		a, ca, cal, value,
+		customAttribute;
+
+		if ( object.sortParticles ) {
+
+			_projScreenMatrixPS.copy( _projScreenMatrix );
+			_projScreenMatrixPS.multiply( object.matrixWorld );
+
+			for ( v = 0; v < vl; v ++ ) {
+
+				vertex = vertices[ v ];
+
+				_vector3.copy( vertex );
+				_vector3.applyProjection( _projScreenMatrixPS );
+
+				sortArray[ v ] = [ _vector3.z, v ];
+
+			}
+
+			sortArray.sort( numericalSort );
+
+			for ( v = 0; v < vl; v ++ ) {
+
+				vertex = vertices[ sortArray[v][1] ];
+
+				offset = v * 3;
+
+				vertexArray[ offset ]     = vertex.x;
+				vertexArray[ offset + 1 ] = vertex.y;
+				vertexArray[ offset + 2 ] = vertex.z;
+
+			}
+
+			for ( c = 0; c < cl; c ++ ) {
+
+				offset = c * 3;
+
+				color = colors[ sortArray[c][1] ];
+
+				colorArray[ offset ]     = color.r;
+				colorArray[ offset + 1 ] = color.g;
+				colorArray[ offset + 2 ] = color.b;
+
+			}
+
+			if ( customAttributes ) {
+
+				for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+					customAttribute = customAttributes[ i ];
+
+					if ( ! ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) ) continue;
+
+					offset = 0;
+
+					cal = customAttribute.value.length;
+
+					if ( customAttribute.size === 1 ) {
+
+						for ( ca = 0; ca < cal; ca ++ ) {
+
+							index = sortArray[ ca ][ 1 ];
+
+							customAttribute.array[ ca ] = customAttribute.value[ index ];
+
+						}
+
+					} else if ( customAttribute.size === 2 ) {
+
+						for ( ca = 0; ca < cal; ca ++ ) {
+
+							index = sortArray[ ca ][ 1 ];
+
+							value = customAttribute.value[ index ];
+
+							customAttribute.array[ offset ] 	= value.x;
+							customAttribute.array[ offset + 1 ] = value.y;
+
+							offset += 2;
+
+						}
+
+					} else if ( customAttribute.size === 3 ) {
+
+						if ( customAttribute.type === "c" ) {
+
+							for ( ca = 0; ca < cal; ca ++ ) {
+
+								index = sortArray[ ca ][ 1 ];
+
+								value = customAttribute.value[ index ];
+
+								customAttribute.array[ offset ]     = value.r;
+								customAttribute.array[ offset + 1 ] = value.g;
+								customAttribute.array[ offset + 2 ] = value.b;
+
+								offset += 3;
+
+							}
+
+						} else {
+
+							for ( ca = 0; ca < cal; ca ++ ) {
+
+								index = sortArray[ ca ][ 1 ];
+
+								value = customAttribute.value[ index ];
+
+								customAttribute.array[ offset ] 	= value.x;
+								customAttribute.array[ offset + 1 ] = value.y;
+								customAttribute.array[ offset + 2 ] = value.z;
+
+								offset += 3;
+
+							}
+
+						}
+
+					} else if ( customAttribute.size === 4 ) {
+
+						for ( ca = 0; ca < cal; ca ++ ) {
+
+							index = sortArray[ ca ][ 1 ];
+
+							value = customAttribute.value[ index ];
+
+							customAttribute.array[ offset ]      = value.x;
+							customAttribute.array[ offset + 1  ] = value.y;
+							customAttribute.array[ offset + 2  ] = value.z;
+							customAttribute.array[ offset + 3  ] = value.w;
+
+							offset += 4;
+
+						}
+
+					}
+
+				}
+
+			}
+
+		} else {
+
+			if ( dirtyVertices ) {
+
+				for ( v = 0; v < vl; v ++ ) {
+
+					vertex = vertices[ v ];
+
+					offset = v * 3;
+
+					vertexArray[ offset ]     = vertex.x;
+					vertexArray[ offset + 1 ] = vertex.y;
+					vertexArray[ offset + 2 ] = vertex.z;
+
+				}
+
+			}
+
+			if ( dirtyColors ) {
+
+				for ( c = 0; c < cl; c ++ ) {
+
+					color = colors[ c ];
+
+					offset = c * 3;
+
+					colorArray[ offset ]     = color.r;
+					colorArray[ offset + 1 ] = color.g;
+					colorArray[ offset + 2 ] = color.b;
+
+				}
+
+			}
+
+			if ( customAttributes ) {
+
+				for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+					customAttribute = customAttributes[ i ];
+
+					if ( customAttribute.needsUpdate &&
+						 ( customAttribute.boundTo === undefined ||
+						   customAttribute.boundTo === "vertices") ) {
+
+						cal = customAttribute.value.length;
+
+						offset = 0;
+
+						if ( customAttribute.size === 1 ) {
+
+							for ( ca = 0; ca < cal; ca ++ ) {
+
+								customAttribute.array[ ca ] = customAttribute.value[ ca ];
+
+							}
+
+						} else if ( customAttribute.size === 2 ) {
+
+							for ( ca = 0; ca < cal; ca ++ ) {
+
+								value = customAttribute.value[ ca ];
+
+								customAttribute.array[ offset ] 	= value.x;
+								customAttribute.array[ offset + 1 ] = value.y;
+
+								offset += 2;
+
+							}
+
+						} else if ( customAttribute.size === 3 ) {
+
+							if ( customAttribute.type === "c" ) {
+
+								for ( ca = 0; ca < cal; ca ++ ) {
+
+									value = customAttribute.value[ ca ];
+
+									customAttribute.array[ offset ] 	= value.r;
+									customAttribute.array[ offset + 1 ] = value.g;
+									customAttribute.array[ offset + 2 ] = value.b;
+
+									offset += 3;
+
+								}
+
+							} else {
+
+								for ( ca = 0; ca < cal; ca ++ ) {
+
+									value = customAttribute.value[ ca ];
+
+									customAttribute.array[ offset ] 	= value.x;
+									customAttribute.array[ offset + 1 ] = value.y;
+									customAttribute.array[ offset + 2 ] = value.z;
+
+									offset += 3;
+
+								}
+
+							}
+
+						} else if ( customAttribute.size === 4 ) {
+
+							for ( ca = 0; ca < cal; ca ++ ) {
+
+								value = customAttribute.value[ ca ];
+
+								customAttribute.array[ offset ]      = value.x;
+								customAttribute.array[ offset + 1  ] = value.y;
+								customAttribute.array[ offset + 2  ] = value.z;
+								customAttribute.array[ offset + 3  ] = value.w;
+
+								offset += 4;
+
+							}
+
+						}
+
+					}
+
+				}
+
+			}
+
+		}
+
+		if ( dirtyVertices || object.sortParticles ) {
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint );
+
+		}
+
+		if ( dirtyColors || object.sortParticles ) {
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint );
+
+		}
+
+		if ( customAttributes ) {
+
+			for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+				customAttribute = customAttributes[ i ];
+
+				if ( customAttribute.needsUpdate || object.sortParticles ) {
+
+					_gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer );
+					_gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint );
+
+				}
+
+			}
+
+		}
+
+
+	};
+
+	function setLineBuffers ( geometry, hint ) {
+
+		var v, c, d, vertex, offset, color,
+
+		vertices = geometry.vertices,
+		colors = geometry.colors,
+		lineDistances = geometry.lineDistances,
+
+		vl = vertices.length,
+		cl = colors.length,
+		dl = lineDistances.length,
+
+		vertexArray = geometry.__vertexArray,
+		colorArray = geometry.__colorArray,
+		lineDistanceArray = geometry.__lineDistanceArray,
+
+		dirtyVertices = geometry.verticesNeedUpdate,
+		dirtyColors = geometry.colorsNeedUpdate,
+		dirtyLineDistances = geometry.lineDistancesNeedUpdate,
+
+		customAttributes = geometry.__webglCustomAttributesList,
+
+		i, il,
+		a, ca, cal, value,
+		customAttribute;
+
+		if ( dirtyVertices ) {
+
+			for ( v = 0; v < vl; v ++ ) {
+
+				vertex = vertices[ v ];
+
+				offset = v * 3;
+
+				vertexArray[ offset ]     = vertex.x;
+				vertexArray[ offset + 1 ] = vertex.y;
+				vertexArray[ offset + 2 ] = vertex.z;
+
+			}
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint );
+
+		}
+
+		if ( dirtyColors ) {
+
+			for ( c = 0; c < cl; c ++ ) {
+
+				color = colors[ c ];
+
+				offset = c * 3;
+
+				colorArray[ offset ]     = color.r;
+				colorArray[ offset + 1 ] = color.g;
+				colorArray[ offset + 2 ] = color.b;
+
+			}
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint );
+
+		}
+
+		if ( dirtyLineDistances ) {
+
+			for ( d = 0; d < dl; d ++ ) {
+
+				lineDistanceArray[ d ] = lineDistances[ d ];
+
+			}
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglLineDistanceBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, lineDistanceArray, hint );
+
+		}
+
+		if ( customAttributes ) {
+
+			for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+				customAttribute = customAttributes[ i ];
+
+				if ( customAttribute.needsUpdate &&
+					 ( customAttribute.boundTo === undefined ||
+					   customAttribute.boundTo === "vertices" ) ) {
+
+					offset = 0;
+
+					cal = customAttribute.value.length;
+
+					if ( customAttribute.size === 1 ) {
+
+						for ( ca = 0; ca < cal; ca ++ ) {
+
+							customAttribute.array[ ca ] = customAttribute.value[ ca ];
+
+						}
+
+					} else if ( customAttribute.size === 2 ) {
+
+						for ( ca = 0; ca < cal; ca ++ ) {
+
+							value = customAttribute.value[ ca ];
+
+							customAttribute.array[ offset ] 	= value.x;
+							customAttribute.array[ offset + 1 ] = value.y;
+
+							offset += 2;
+
+						}
+
+					} else if ( customAttribute.size === 3 ) {
+
+						if ( customAttribute.type === "c" ) {
+
+							for ( ca = 0; ca < cal; ca ++ ) {
+
+								value = customAttribute.value[ ca ];
+
+								customAttribute.array[ offset ] 	= value.r;
+								customAttribute.array[ offset + 1 ] = value.g;
+								customAttribute.array[ offset + 2 ] = value.b;
+
+								offset += 3;
+
+							}
+
+						} else {
+
+							for ( ca = 0; ca < cal; ca ++ ) {
+
+								value = customAttribute.value[ ca ];
+
+								customAttribute.array[ offset ] 	= value.x;
+								customAttribute.array[ offset + 1 ] = value.y;
+								customAttribute.array[ offset + 2 ] = value.z;
+
+								offset += 3;
+
+							}
+
+						}
+
+					} else if ( customAttribute.size === 4 ) {
+
+						for ( ca = 0; ca < cal; ca ++ ) {
+
+							value = customAttribute.value[ ca ];
+
+							customAttribute.array[ offset ] 	 = value.x;
+							customAttribute.array[ offset + 1  ] = value.y;
+							customAttribute.array[ offset + 2  ] = value.z;
+							customAttribute.array[ offset + 3  ] = value.w;
+
+							offset += 4;
+
+						}
+
+					}
+
+					_gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer );
+					_gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint );
+
+				}
+
+			}
+
+		}
+
+	};
+
+	function setRibbonBuffers ( geometry, hint ) {
+
+		var v, c, n, vertex, offset, color, normal,
+
+		i, il, ca, cal, customAttribute, value,
+
+		vertices = geometry.vertices,
+		colors = geometry.colors,
+		normals = geometry.normals,
+
+		vl = vertices.length,
+		cl = colors.length,
+		nl = normals.length,
+
+		vertexArray = geometry.__vertexArray,
+		colorArray = geometry.__colorArray,
+		normalArray = geometry.__normalArray,
+
+		dirtyVertices = geometry.verticesNeedUpdate,
+		dirtyColors = geometry.colorsNeedUpdate,
+		dirtyNormals = geometry.normalsNeedUpdate,
+
+		customAttributes = geometry.__webglCustomAttributesList;
+
+		if ( dirtyVertices ) {
+
+			for ( v = 0; v < vl; v ++ ) {
+
+				vertex = vertices[ v ];
+
+				offset = v * 3;
+
+				vertexArray[ offset ]     = vertex.x;
+				vertexArray[ offset + 1 ] = vertex.y;
+				vertexArray[ offset + 2 ] = vertex.z;
+
+			}
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint );
+
+		}
+
+		if ( dirtyColors ) {
+
+			for ( c = 0; c < cl; c ++ ) {
+
+				color = colors[ c ];
+
+				offset = c * 3;
+
+				colorArray[ offset ]     = color.r;
+				colorArray[ offset + 1 ] = color.g;
+				colorArray[ offset + 2 ] = color.b;
+
+			}
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint );
+
+		}
+
+		if ( dirtyNormals ) {
+
+			for ( n = 0; n < nl; n ++ ) {
+
+				normal = normals[ n ];
+
+				offset = n * 3;
+
+				normalArray[ offset ]     = normal.x;
+				normalArray[ offset + 1 ] = normal.y;
+				normalArray[ offset + 2 ] = normal.z;
+
+			}
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglNormalBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, normalArray, hint );
+
+		}
+
+		if ( customAttributes ) {
+
+			for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+				customAttribute = customAttributes[ i ];
+
+				if ( customAttribute.needsUpdate &&
+					 ( customAttribute.boundTo === undefined ||
+					   customAttribute.boundTo === "vertices" ) ) {
+
+					offset = 0;
+
+					cal = customAttribute.value.length;
+
+					if ( customAttribute.size === 1 ) {
+
+						for ( ca = 0; ca < cal; ca ++ ) {
+
+							customAttribute.array[ ca ] = customAttribute.value[ ca ];
+
+						}
+
+					} else if ( customAttribute.size === 2 ) {
+
+						for ( ca = 0; ca < cal; ca ++ ) {
+
+							value = customAttribute.value[ ca ];
+
+							customAttribute.array[ offset ] 	= value.x;
+							customAttribute.array[ offset + 1 ] = value.y;
+
+							offset += 2;
+
+						}
+
+					} else if ( customAttribute.size === 3 ) {
+
+						if ( customAttribute.type === "c" ) {
+
+							for ( ca = 0; ca < cal; ca ++ ) {
+
+								value = customAttribute.value[ ca ];
+
+								customAttribute.array[ offset ] 	= value.r;
+								customAttribute.array[ offset + 1 ] = value.g;
+								customAttribute.array[ offset + 2 ] = value.b;
+
+								offset += 3;
+
+							}
+
+						} else {
+
+							for ( ca = 0; ca < cal; ca ++ ) {
+
+								value = customAttribute.value[ ca ];
+
+								customAttribute.array[ offset ] 	= value.x;
+								customAttribute.array[ offset + 1 ] = value.y;
+								customAttribute.array[ offset + 2 ] = value.z;
+
+								offset += 3;
+
+							}
+
+						}
+
+					} else if ( customAttribute.size === 4 ) {
+
+						for ( ca = 0; ca < cal; ca ++ ) {
+
+							value = customAttribute.value[ ca ];
+
+							customAttribute.array[ offset ] 	 = value.x;
+							customAttribute.array[ offset + 1  ] = value.y;
+							customAttribute.array[ offset + 2  ] = value.z;
+							customAttribute.array[ offset + 3  ] = value.w;
+
+							offset += 4;
+
+						}
+
+					}
+
+					_gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer );
+					_gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint );
+
+				}
+
+			}
+
+		}
+
+	};
+
+	function setMeshBuffers( geometryGroup, object, hint, dispose, material ) {
+
+		if ( ! geometryGroup.__inittedArrays ) {
+
+			return;
+
+		}
+
+		var normalType = bufferGuessNormalType( material ),
+		vertexColorType = bufferGuessVertexColorType( material ),
+		uvType = bufferGuessUVType( material ),
+
+		needsSmoothNormals = ( normalType === THREE.SmoothShading );
+
+		var f, fl, fi, face,
+		vertexNormals, faceNormal, normal,
+		vertexColors, faceColor,
+		vertexTangents,
+		uv, uv2, v1, v2, v3, v4, t1, t2, t3, t4, n1, n2, n3, n4,
+		c1, c2, c3, c4,
+		sw1, sw2, sw3, sw4,
+		si1, si2, si3, si4,
+		sa1, sa2, sa3, sa4,
+		sb1, sb2, sb3, sb4,
+		m, ml, i, il,
+		vn, uvi, uv2i,
+		vk, vkl, vka,
+		nka, chf, faceVertexNormals,
+		a,
+
+		vertexIndex = 0,
+
+		offset = 0,
+		offset_uv = 0,
+		offset_uv2 = 0,
+		offset_face = 0,
+		offset_normal = 0,
+		offset_tangent = 0,
+		offset_line = 0,
+		offset_color = 0,
+		offset_skin = 0,
+		offset_morphTarget = 0,
+		offset_custom = 0,
+		offset_customSrc = 0,
+
+		value,
+
+		vertexArray = geometryGroup.__vertexArray,
+		uvArray = geometryGroup.__uvArray,
+		uv2Array = geometryGroup.__uv2Array,
+		normalArray = geometryGroup.__normalArray,
+		tangentArray = geometryGroup.__tangentArray,
+		colorArray = geometryGroup.__colorArray,
+
+		skinIndexArray = geometryGroup.__skinIndexArray,
+		skinWeightArray = geometryGroup.__skinWeightArray,
+
+		morphTargetsArrays = geometryGroup.__morphTargetsArrays,
+		morphNormalsArrays = geometryGroup.__morphNormalsArrays,
+
+		customAttributes = geometryGroup.__webglCustomAttributesList,
+		customAttribute,
+
+		faceArray = geometryGroup.__faceArray,
+		lineArray = geometryGroup.__lineArray,
+
+		geometry = object.geometry, // this is shared for all chunks
+
+		dirtyVertices = geometry.verticesNeedUpdate,
+		dirtyElements = geometry.elementsNeedUpdate,
+		dirtyUvs = geometry.uvsNeedUpdate,
+		dirtyNormals = geometry.normalsNeedUpdate,
+		dirtyTangents = geometry.tangentsNeedUpdate,
+		dirtyColors = geometry.colorsNeedUpdate,
+		dirtyMorphTargets = geometry.morphTargetsNeedUpdate,
+
+		vertices = geometry.vertices,
+		chunk_faces3 = geometryGroup.faces3,
+		chunk_faces4 = geometryGroup.faces4,
+		obj_faces = geometry.faces,
+
+		obj_uvs  = geometry.faceVertexUvs[ 0 ],
+		obj_uvs2 = geometry.faceVertexUvs[ 1 ],
+
+		obj_colors = geometry.colors,
+
+		obj_skinIndices = geometry.skinIndices,
+		obj_skinWeights = geometry.skinWeights,
+
+		morphTargets = geometry.morphTargets,
+		morphNormals = geometry.morphNormals;
+
+		if ( dirtyVertices ) {
+
+			for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+				face = obj_faces[ chunk_faces3[ f ] ];
+
+				v1 = vertices[ face.a ];
+				v2 = vertices[ face.b ];
+				v3 = vertices[ face.c ];
+
+				vertexArray[ offset ]     = v1.x;
+				vertexArray[ offset + 1 ] = v1.y;
+				vertexArray[ offset + 2 ] = v1.z;
+
+				vertexArray[ offset + 3 ] = v2.x;
+				vertexArray[ offset + 4 ] = v2.y;
+				vertexArray[ offset + 5 ] = v2.z;
+
+				vertexArray[ offset + 6 ] = v3.x;
+				vertexArray[ offset + 7 ] = v3.y;
+				vertexArray[ offset + 8 ] = v3.z;
+
+				offset += 9;
+
+			}
+
+			for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+				face = obj_faces[ chunk_faces4[ f ] ];
+
+				v1 = vertices[ face.a ];
+				v2 = vertices[ face.b ];
+				v3 = vertices[ face.c ];
+				v4 = vertices[ face.d ];
+
+				vertexArray[ offset ]     = v1.x;
+				vertexArray[ offset + 1 ] = v1.y;
+				vertexArray[ offset + 2 ] = v1.z;
+
+				vertexArray[ offset + 3 ] = v2.x;
+				vertexArray[ offset + 4 ] = v2.y;
+				vertexArray[ offset + 5 ] = v2.z;
+
+				vertexArray[ offset + 6 ] = v3.x;
+				vertexArray[ offset + 7 ] = v3.y;
+				vertexArray[ offset + 8 ] = v3.z;
+
+				vertexArray[ offset + 9 ]  = v4.x;
+				vertexArray[ offset + 10 ] = v4.y;
+				vertexArray[ offset + 11 ] = v4.z;
+
+				offset += 12;
+
+			}
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint );
+
+		}
+
+		if ( dirtyMorphTargets ) {
+
+			for ( vk = 0, vkl = morphTargets.length; vk < vkl; vk ++ ) {
+
+				offset_morphTarget = 0;
+
+				for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+					chf = chunk_faces3[ f ];
+					face = obj_faces[ chf ];
+
+					// morph positions
+
+					v1 = morphTargets[ vk ].vertices[ face.a ];
+					v2 = morphTargets[ vk ].vertices[ face.b ];
+					v3 = morphTargets[ vk ].vertices[ face.c ];
+
+					vka = morphTargetsArrays[ vk ];
+
+					vka[ offset_morphTarget ] 	  = v1.x;
+					vka[ offset_morphTarget + 1 ] = v1.y;
+					vka[ offset_morphTarget + 2 ] = v1.z;
+
+					vka[ offset_morphTarget + 3 ] = v2.x;
+					vka[ offset_morphTarget + 4 ] = v2.y;
+					vka[ offset_morphTarget + 5 ] = v2.z;
+
+					vka[ offset_morphTarget + 6 ] = v3.x;
+					vka[ offset_morphTarget + 7 ] = v3.y;
+					vka[ offset_morphTarget + 8 ] = v3.z;
+
+					// morph normals
+
+					if ( material.morphNormals ) {
+
+						if ( needsSmoothNormals ) {
+
+							faceVertexNormals = morphNormals[ vk ].vertexNormals[ chf ];
+
+							n1 = faceVertexNormals.a;
+							n2 = faceVertexNormals.b;
+							n3 = faceVertexNormals.c;
+
+						} else {
+
+							n1 = morphNormals[ vk ].faceNormals[ chf ];
+							n2 = n1;
+							n3 = n1;
+
+						}
+
+						nka = morphNormalsArrays[ vk ];
+
+						nka[ offset_morphTarget ] 	  = n1.x;
+						nka[ offset_morphTarget + 1 ] = n1.y;
+						nka[ offset_morphTarget + 2 ] = n1.z;
+
+						nka[ offset_morphTarget + 3 ] = n2.x;
+						nka[ offset_morphTarget + 4 ] = n2.y;
+						nka[ offset_morphTarget + 5 ] = n2.z;
+
+						nka[ offset_morphTarget + 6 ] = n3.x;
+						nka[ offset_morphTarget + 7 ] = n3.y;
+						nka[ offset_morphTarget + 8 ] = n3.z;
+
+					}
+
+					//
+
+					offset_morphTarget += 9;
+
+				}
+
+				for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+					chf = chunk_faces4[ f ];
+					face = obj_faces[ chf ];
+
+					// morph positions
+
+					v1 = morphTargets[ vk ].vertices[ face.a ];
+					v2 = morphTargets[ vk ].vertices[ face.b ];
+					v3 = morphTargets[ vk ].vertices[ face.c ];
+					v4 = morphTargets[ vk ].vertices[ face.d ];
+
+					vka = morphTargetsArrays[ vk ];
+
+					vka[ offset_morphTarget ] 	  = v1.x;
+					vka[ offset_morphTarget + 1 ] = v1.y;
+					vka[ offset_morphTarget + 2 ] = v1.z;
+
+					vka[ offset_morphTarget + 3 ] = v2.x;
+					vka[ offset_morphTarget + 4 ] = v2.y;
+					vka[ offset_morphTarget + 5 ] = v2.z;
+
+					vka[ offset_morphTarget + 6 ] = v3.x;
+					vka[ offset_morphTarget + 7 ] = v3.y;
+					vka[ offset_morphTarget + 8 ] = v3.z;
+
+					vka[ offset_morphTarget + 9 ]  = v4.x;
+					vka[ offset_morphTarget + 10 ] = v4.y;
+					vka[ offset_morphTarget + 11 ] = v4.z;
+
+					// morph normals
+
+					if ( material.morphNormals ) {
+
+						if ( needsSmoothNormals ) {
+
+							faceVertexNormals = morphNormals[ vk ].vertexNormals[ chf ];
+
+							n1 = faceVertexNormals.a;
+							n2 = faceVertexNormals.b;
+							n3 = faceVertexNormals.c;
+							n4 = faceVertexNormals.d;
+
+						} else {
+
+							n1 = morphNormals[ vk ].faceNormals[ chf ];
+							n2 = n1;
+							n3 = n1;
+							n4 = n1;
+
+						}
+
+						nka = morphNormalsArrays[ vk ];
+
+						nka[ offset_morphTarget ] 	  = n1.x;
+						nka[ offset_morphTarget + 1 ] = n1.y;
+						nka[ offset_morphTarget + 2 ] = n1.z;
+
+						nka[ offset_morphTarget + 3 ] = n2.x;
+						nka[ offset_morphTarget + 4 ] = n2.y;
+						nka[ offset_morphTarget + 5 ] = n2.z;
+
+						nka[ offset_morphTarget + 6 ] = n3.x;
+						nka[ offset_morphTarget + 7 ] = n3.y;
+						nka[ offset_morphTarget + 8 ] = n3.z;
+
+						nka[ offset_morphTarget + 9 ]  = n4.x;
+						nka[ offset_morphTarget + 10 ] = n4.y;
+						nka[ offset_morphTarget + 11 ] = n4.z;
+
+					}
+
+					//
+
+					offset_morphTarget += 12;
+
+				}
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ vk ] );
+				_gl.bufferData( _gl.ARRAY_BUFFER, morphTargetsArrays[ vk ], hint );
+
+				if ( material.morphNormals ) {
+
+					_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ vk ] );
+					_gl.bufferData( _gl.ARRAY_BUFFER, morphNormalsArrays[ vk ], hint );
+
+				}
+
+			}
+
+		}
+
+		if ( obj_skinWeights.length ) {
+
+			for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+				face = obj_faces[ chunk_faces3[ f ]	];
+
+				// weights
+
+				sw1 = obj_skinWeights[ face.a ];
+				sw2 = obj_skinWeights[ face.b ];
+				sw3 = obj_skinWeights[ face.c ];
+
+				skinWeightArray[ offset_skin ]     = sw1.x;
+				skinWeightArray[ offset_skin + 1 ] = sw1.y;
+				skinWeightArray[ offset_skin + 2 ] = sw1.z;
+				skinWeightArray[ offset_skin + 3 ] = sw1.w;
+
+				skinWeightArray[ offset_skin + 4 ] = sw2.x;
+				skinWeightArray[ offset_skin + 5 ] = sw2.y;
+				skinWeightArray[ offset_skin + 6 ] = sw2.z;
+				skinWeightArray[ offset_skin + 7 ] = sw2.w;
+
+				skinWeightArray[ offset_skin + 8 ]  = sw3.x;
+				skinWeightArray[ offset_skin + 9 ]  = sw3.y;
+				skinWeightArray[ offset_skin + 10 ] = sw3.z;
+				skinWeightArray[ offset_skin + 11 ] = sw3.w;
+
+				// indices
+
+				si1 = obj_skinIndices[ face.a ];
+				si2 = obj_skinIndices[ face.b ];
+				si3 = obj_skinIndices[ face.c ];
+
+				skinIndexArray[ offset_skin ]     = si1.x;
+				skinIndexArray[ offset_skin + 1 ] = si1.y;
+				skinIndexArray[ offset_skin + 2 ] = si1.z;
+				skinIndexArray[ offset_skin + 3 ] = si1.w;
+
+				skinIndexArray[ offset_skin + 4 ] = si2.x;
+				skinIndexArray[ offset_skin + 5 ] = si2.y;
+				skinIndexArray[ offset_skin + 6 ] = si2.z;
+				skinIndexArray[ offset_skin + 7 ] = si2.w;
+
+				skinIndexArray[ offset_skin + 8 ]  = si3.x;
+				skinIndexArray[ offset_skin + 9 ]  = si3.y;
+				skinIndexArray[ offset_skin + 10 ] = si3.z;
+				skinIndexArray[ offset_skin + 11 ] = si3.w;
+
+				offset_skin += 12;
+
+			}
+
+			for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+				face = obj_faces[ chunk_faces4[ f ] ];
+
+				// weights
+
+				sw1 = obj_skinWeights[ face.a ];
+				sw2 = obj_skinWeights[ face.b ];
+				sw3 = obj_skinWeights[ face.c ];
+				sw4 = obj_skinWeights[ face.d ];
+
+				skinWeightArray[ offset_skin ]     = sw1.x;
+				skinWeightArray[ offset_skin + 1 ] = sw1.y;
+				skinWeightArray[ offset_skin + 2 ] = sw1.z;
+				skinWeightArray[ offset_skin + 3 ] = sw1.w;
+
+				skinWeightArray[ offset_skin + 4 ] = sw2.x;
+				skinWeightArray[ offset_skin + 5 ] = sw2.y;
+				skinWeightArray[ offset_skin + 6 ] = sw2.z;
+				skinWeightArray[ offset_skin + 7 ] = sw2.w;
+
+				skinWeightArray[ offset_skin + 8 ]  = sw3.x;
+				skinWeightArray[ offset_skin + 9 ]  = sw3.y;
+				skinWeightArray[ offset_skin + 10 ] = sw3.z;
+				skinWeightArray[ offset_skin + 11 ] = sw3.w;
+
+				skinWeightArray[ offset_skin + 12 ] = sw4.x;
+				skinWeightArray[ offset_skin + 13 ] = sw4.y;
+				skinWeightArray[ offset_skin + 14 ] = sw4.z;
+				skinWeightArray[ offset_skin + 15 ] = sw4.w;
+
+				// indices
+
+				si1 = obj_skinIndices[ face.a ];
+				si2 = obj_skinIndices[ face.b ];
+				si3 = obj_skinIndices[ face.c ];
+				si4 = obj_skinIndices[ face.d ];
+
+				skinIndexArray[ offset_skin ]     = si1.x;
+				skinIndexArray[ offset_skin + 1 ] = si1.y;
+				skinIndexArray[ offset_skin + 2 ] = si1.z;
+				skinIndexArray[ offset_skin + 3 ] = si1.w;
+
+				skinIndexArray[ offset_skin + 4 ] = si2.x;
+				skinIndexArray[ offset_skin + 5 ] = si2.y;
+				skinIndexArray[ offset_skin + 6 ] = si2.z;
+				skinIndexArray[ offset_skin + 7 ] = si2.w;
+
+				skinIndexArray[ offset_skin + 8 ]  = si3.x;
+				skinIndexArray[ offset_skin + 9 ]  = si3.y;
+				skinIndexArray[ offset_skin + 10 ] = si3.z;
+				skinIndexArray[ offset_skin + 11 ] = si3.w;
+
+				skinIndexArray[ offset_skin + 12 ] = si4.x;
+				skinIndexArray[ offset_skin + 13 ] = si4.y;
+				skinIndexArray[ offset_skin + 14 ] = si4.z;
+				skinIndexArray[ offset_skin + 15 ] = si4.w;
+
+				offset_skin += 16;
+
+			}
+
+			if ( offset_skin > 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinIndicesBuffer );
+				_gl.bufferData( _gl.ARRAY_BUFFER, skinIndexArray, hint );
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinWeightsBuffer );
+				_gl.bufferData( _gl.ARRAY_BUFFER, skinWeightArray, hint );
+
+			}
+
+		}
+
+		if ( dirtyColors && vertexColorType ) {
+
+			for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+				face = obj_faces[ chunk_faces3[ f ]	];
+
+				vertexColors = face.vertexColors;
+				faceColor = face.color;
+
+				if ( vertexColors.length === 3 && vertexColorType === THREE.VertexColors ) {
+
+					c1 = vertexColors[ 0 ];
+					c2 = vertexColors[ 1 ];
+					c3 = vertexColors[ 2 ];
+
+				} else {
+
+					c1 = faceColor;
+					c2 = faceColor;
+					c3 = faceColor;
+
+				}
+
+				colorArray[ offset_color ]     = c1.r;
+				colorArray[ offset_color + 1 ] = c1.g;
+				colorArray[ offset_color + 2 ] = c1.b;
+
+				colorArray[ offset_color + 3 ] = c2.r;
+				colorArray[ offset_color + 4 ] = c2.g;
+				colorArray[ offset_color + 5 ] = c2.b;
+
+				colorArray[ offset_color + 6 ] = c3.r;
+				colorArray[ offset_color + 7 ] = c3.g;
+				colorArray[ offset_color + 8 ] = c3.b;
+
+				offset_color += 9;
+
+			}
+
+			for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+				face = obj_faces[ chunk_faces4[ f ] ];
+
+				vertexColors = face.vertexColors;
+				faceColor = face.color;
+
+				if ( vertexColors.length === 4 && vertexColorType === THREE.VertexColors ) {
+
+					c1 = vertexColors[ 0 ];
+					c2 = vertexColors[ 1 ];
+					c3 = vertexColors[ 2 ];
+					c4 = vertexColors[ 3 ];
+
+				} else {
+
+					c1 = faceColor;
+					c2 = faceColor;
+					c3 = faceColor;
+					c4 = faceColor;
+
+				}
+
+				colorArray[ offset_color ]     = c1.r;
+				colorArray[ offset_color + 1 ] = c1.g;
+				colorArray[ offset_color + 2 ] = c1.b;
+
+				colorArray[ offset_color + 3 ] = c2.r;
+				colorArray[ offset_color + 4 ] = c2.g;
+				colorArray[ offset_color + 5 ] = c2.b;
+
+				colorArray[ offset_color + 6 ] = c3.r;
+				colorArray[ offset_color + 7 ] = c3.g;
+				colorArray[ offset_color + 8 ] = c3.b;
+
+				colorArray[ offset_color + 9 ]  = c4.r;
+				colorArray[ offset_color + 10 ] = c4.g;
+				colorArray[ offset_color + 11 ] = c4.b;
+
+				offset_color += 12;
+
+			}
+
+			if ( offset_color > 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer );
+				_gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint );
+
+			}
+
+		}
+
+		if ( dirtyTangents && geometry.hasTangents ) {
+
+			for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+				face = obj_faces[ chunk_faces3[ f ]	];
+
+				vertexTangents = face.vertexTangents;
+
+				t1 = vertexTangents[ 0 ];
+				t2 = vertexTangents[ 1 ];
+				t3 = vertexTangents[ 2 ];
+
+				tangentArray[ offset_tangent ]     = t1.x;
+				tangentArray[ offset_tangent + 1 ] = t1.y;
+				tangentArray[ offset_tangent + 2 ] = t1.z;
+				tangentArray[ offset_tangent + 3 ] = t1.w;
+
+				tangentArray[ offset_tangent + 4 ] = t2.x;
+				tangentArray[ offset_tangent + 5 ] = t2.y;
+				tangentArray[ offset_tangent + 6 ] = t2.z;
+				tangentArray[ offset_tangent + 7 ] = t2.w;
+
+				tangentArray[ offset_tangent + 8 ]  = t3.x;
+				tangentArray[ offset_tangent + 9 ]  = t3.y;
+				tangentArray[ offset_tangent + 10 ] = t3.z;
+				tangentArray[ offset_tangent + 11 ] = t3.w;
+
+				offset_tangent += 12;
+
+			}
+
+			for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+				face = obj_faces[ chunk_faces4[ f ] ];
+
+				vertexTangents = face.vertexTangents;
+
+				t1 = vertexTangents[ 0 ];
+				t2 = vertexTangents[ 1 ];
+				t3 = vertexTangents[ 2 ];
+				t4 = vertexTangents[ 3 ];
+
+				tangentArray[ offset_tangent ]     = t1.x;
+				tangentArray[ offset_tangent + 1 ] = t1.y;
+				tangentArray[ offset_tangent + 2 ] = t1.z;
+				tangentArray[ offset_tangent + 3 ] = t1.w;
+
+				tangentArray[ offset_tangent + 4 ] = t2.x;
+				tangentArray[ offset_tangent + 5 ] = t2.y;
+				tangentArray[ offset_tangent + 6 ] = t2.z;
+				tangentArray[ offset_tangent + 7 ] = t2.w;
+
+				tangentArray[ offset_tangent + 8 ]  = t3.x;
+				tangentArray[ offset_tangent + 9 ]  = t3.y;
+				tangentArray[ offset_tangent + 10 ] = t3.z;
+				tangentArray[ offset_tangent + 11 ] = t3.w;
+
+				tangentArray[ offset_tangent + 12 ] = t4.x;
+				tangentArray[ offset_tangent + 13 ] = t4.y;
+				tangentArray[ offset_tangent + 14 ] = t4.z;
+				tangentArray[ offset_tangent + 15 ] = t4.w;
+
+				offset_tangent += 16;
+
+			}
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglTangentBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, tangentArray, hint );
+
+		}
+
+		if ( dirtyNormals && normalType ) {
+
+			for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+				face = obj_faces[ chunk_faces3[ f ]	];
+
+				vertexNormals = face.vertexNormals;
+				faceNormal = face.normal;
+
+				if ( vertexNormals.length === 3 && needsSmoothNormals ) {
+
+					for ( i = 0; i < 3; i ++ ) {
+
+						vn = vertexNormals[ i ];
+
+						normalArray[ offset_normal ]     = vn.x;
+						normalArray[ offset_normal + 1 ] = vn.y;
+						normalArray[ offset_normal + 2 ] = vn.z;
+
+						offset_normal += 3;
+
+					}
+
+				} else {
+
+					for ( i = 0; i < 3; i ++ ) {
+
+						normalArray[ offset_normal ]     = faceNormal.x;
+						normalArray[ offset_normal + 1 ] = faceNormal.y;
+						normalArray[ offset_normal + 2 ] = faceNormal.z;
+
+						offset_normal += 3;
+
+					}
+
+				}
+
+			}
+
+			for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+				face = obj_faces[ chunk_faces4[ f ] ];
+
+				vertexNormals = face.vertexNormals;
+				faceNormal = face.normal;
+
+				if ( vertexNormals.length === 4 && needsSmoothNormals ) {
+
+					for ( i = 0; i < 4; i ++ ) {
+
+						vn = vertexNormals[ i ];
+
+						normalArray[ offset_normal ]     = vn.x;
+						normalArray[ offset_normal + 1 ] = vn.y;
+						normalArray[ offset_normal + 2 ] = vn.z;
+
+						offset_normal += 3;
+
+					}
+
+				} else {
+
+					for ( i = 0; i < 4; i ++ ) {
+
+						normalArray[ offset_normal ]     = faceNormal.x;
+						normalArray[ offset_normal + 1 ] = faceNormal.y;
+						normalArray[ offset_normal + 2 ] = faceNormal.z;
+
+						offset_normal += 3;
+
+					}
+
+				}
+
+			}
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, normalArray, hint );
+
+		}
+
+		if ( dirtyUvs && obj_uvs && uvType ) {
+
+			for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+				fi = chunk_faces3[ f ];
+
+				uv = obj_uvs[ fi ];
+
+				if ( uv === undefined ) continue;
+
+				for ( i = 0; i < 3; i ++ ) {
+
+					uvi = uv[ i ];
+
+					uvArray[ offset_uv ]     = uvi.x;
+					uvArray[ offset_uv + 1 ] = uvi.y;
+
+					offset_uv += 2;
+
+				}
+
+			}
+
+			for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+				fi = chunk_faces4[ f ];
+
+				uv = obj_uvs[ fi ];
+
+				if ( uv === undefined ) continue;
+
+				for ( i = 0; i < 4; i ++ ) {
+
+					uvi = uv[ i ];
+
+					uvArray[ offset_uv ]     = uvi.x;
+					uvArray[ offset_uv + 1 ] = uvi.y;
+
+					offset_uv += 2;
+
+				}
+
+			}
+
+			if ( offset_uv > 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUVBuffer );
+				_gl.bufferData( _gl.ARRAY_BUFFER, uvArray, hint );
+
+			}
+
+		}
+
+		if ( dirtyUvs && obj_uvs2 && uvType ) {
+
+			for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+				fi = chunk_faces3[ f ];
+
+				uv2 = obj_uvs2[ fi ];
+
+				if ( uv2 === undefined ) continue;
+
+				for ( i = 0; i < 3; i ++ ) {
+
+					uv2i = uv2[ i ];
+
+					uv2Array[ offset_uv2 ]     = uv2i.x;
+					uv2Array[ offset_uv2 + 1 ] = uv2i.y;
+
+					offset_uv2 += 2;
+
+				}
+
+			}
+
+			for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+				fi = chunk_faces4[ f ];
+
+				uv2 = obj_uvs2[ fi ];
+
+				if ( uv2 === undefined ) continue;
+
+				for ( i = 0; i < 4; i ++ ) {
+
+					uv2i = uv2[ i ];
+
+					uv2Array[ offset_uv2 ]     = uv2i.x;
+					uv2Array[ offset_uv2 + 1 ] = uv2i.y;
+
+					offset_uv2 += 2;
+
+				}
+
+			}
+
+			if ( offset_uv2 > 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUV2Buffer );
+				_gl.bufferData( _gl.ARRAY_BUFFER, uv2Array, hint );
+
+			}
+
+		}
+
+		if ( dirtyElements ) {
+
+			for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+				faceArray[ offset_face ] 	 = vertexIndex;
+				faceArray[ offset_face + 1 ] = vertexIndex + 1;
+				faceArray[ offset_face + 2 ] = vertexIndex + 2;
+
+				offset_face += 3;
+
+				lineArray[ offset_line ]     = vertexIndex;
+				lineArray[ offset_line + 1 ] = vertexIndex + 1;
+
+				lineArray[ offset_line + 2 ] = vertexIndex;
+				lineArray[ offset_line + 3 ] = vertexIndex + 2;
+
+				lineArray[ offset_line + 4 ] = vertexIndex + 1;
+				lineArray[ offset_line + 5 ] = vertexIndex + 2;
+
+				offset_line += 6;
+
+				vertexIndex += 3;
+
+			}
+
+			for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+				faceArray[ offset_face ]     = vertexIndex;
+				faceArray[ offset_face + 1 ] = vertexIndex + 1;
+				faceArray[ offset_face + 2 ] = vertexIndex + 3;
+
+				faceArray[ offset_face + 3 ] = vertexIndex + 1;
+				faceArray[ offset_face + 4 ] = vertexIndex + 2;
+				faceArray[ offset_face + 5 ] = vertexIndex + 3;
+
+				offset_face += 6;
+
+				lineArray[ offset_line ]     = vertexIndex;
+				lineArray[ offset_line + 1 ] = vertexIndex + 1;
+
+				lineArray[ offset_line + 2 ] = vertexIndex;
+				lineArray[ offset_line + 3 ] = vertexIndex + 3;
+
+				lineArray[ offset_line + 4 ] = vertexIndex + 1;
+				lineArray[ offset_line + 5 ] = vertexIndex + 2;
+
+				lineArray[ offset_line + 6 ] = vertexIndex + 2;
+				lineArray[ offset_line + 7 ] = vertexIndex + 3;
+
+				offset_line += 8;
+
+				vertexIndex += 4;
+
+			}
+
+			_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer );
+			_gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, faceArray, hint );
+
+			_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer );
+			_gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, lineArray, hint );
+
+		}
+
+		if ( customAttributes ) {
+
+			for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+				customAttribute = customAttributes[ i ];
+
+				if ( ! customAttribute.__original.needsUpdate ) continue;
+
+				offset_custom = 0;
+				offset_customSrc = 0;
+
+				if ( customAttribute.size === 1 ) {
+
+					if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+						for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+							face = obj_faces[ chunk_faces3[ f ]	];
+
+							customAttribute.array[ offset_custom ] 	   = customAttribute.value[ face.a ];
+							customAttribute.array[ offset_custom + 1 ] = customAttribute.value[ face.b ];
+							customAttribute.array[ offset_custom + 2 ] = customAttribute.value[ face.c ];
+
+							offset_custom += 3;
+
+						}
+
+						for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+							face = obj_faces[ chunk_faces4[ f ] ];
+
+							customAttribute.array[ offset_custom ] 	   = customAttribute.value[ face.a ];
+							customAttribute.array[ offset_custom + 1 ] = customAttribute.value[ face.b ];
+							customAttribute.array[ offset_custom + 2 ] = customAttribute.value[ face.c ];
+							customAttribute.array[ offset_custom + 3 ] = customAttribute.value[ face.d ];
+
+							offset_custom += 4;
+
+						}
+
+					} else if ( customAttribute.boundTo === "faces" ) {
+
+						for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces3[ f ] ];
+
+							customAttribute.array[ offset_custom ] 	   = value;
+							customAttribute.array[ offset_custom + 1 ] = value;
+							customAttribute.array[ offset_custom + 2 ] = value;
+
+							offset_custom += 3;
+
+						}
+
+						for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces4[ f ] ];
+
+							customAttribute.array[ offset_custom ] 	   = value;
+							customAttribute.array[ offset_custom + 1 ] = value;
+							customAttribute.array[ offset_custom + 2 ] = value;
+							customAttribute.array[ offset_custom + 3 ] = value;
+
+							offset_custom += 4;
+
+						}
+
+					}
+
+				} else if ( customAttribute.size === 2 ) {
+
+					if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+						for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+							face = obj_faces[ chunk_faces3[ f ]	];
+
+							v1 = customAttribute.value[ face.a ];
+							v2 = customAttribute.value[ face.b ];
+							v3 = customAttribute.value[ face.c ];
+
+							customAttribute.array[ offset_custom ] 	   = v1.x;
+							customAttribute.array[ offset_custom + 1 ] = v1.y;
+
+							customAttribute.array[ offset_custom + 2 ] = v2.x;
+							customAttribute.array[ offset_custom + 3 ] = v2.y;
+
+							customAttribute.array[ offset_custom + 4 ] = v3.x;
+							customAttribute.array[ offset_custom + 5 ] = v3.y;
+
+							offset_custom += 6;
+
+						}
+
+						for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+							face = obj_faces[ chunk_faces4[ f ] ];
+
+							v1 = customAttribute.value[ face.a ];
+							v2 = customAttribute.value[ face.b ];
+							v3 = customAttribute.value[ face.c ];
+							v4 = customAttribute.value[ face.d ];
+
+							customAttribute.array[ offset_custom ] 	   = v1.x;
+							customAttribute.array[ offset_custom + 1 ] = v1.y;
+
+							customAttribute.array[ offset_custom + 2 ] = v2.x;
+							customAttribute.array[ offset_custom + 3 ] = v2.y;
+
+							customAttribute.array[ offset_custom + 4 ] = v3.x;
+							customAttribute.array[ offset_custom + 5 ] = v3.y;
+
+							customAttribute.array[ offset_custom + 6 ] = v4.x;
+							customAttribute.array[ offset_custom + 7 ] = v4.y;
+
+							offset_custom += 8;
+
+						}
+
+					} else if ( customAttribute.boundTo === "faces" ) {
+
+						for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces3[ f ] ];
+
+							v1 = value;
+							v2 = value;
+							v3 = value;
+
+							customAttribute.array[ offset_custom ] 	   = v1.x;
+							customAttribute.array[ offset_custom + 1 ] = v1.y;
+
+							customAttribute.array[ offset_custom + 2 ] = v2.x;
+							customAttribute.array[ offset_custom + 3 ] = v2.y;
+
+							customAttribute.array[ offset_custom + 4 ] = v3.x;
+							customAttribute.array[ offset_custom + 5 ] = v3.y;
+
+							offset_custom += 6;
+
+						}
+
+						for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces4[ f ] ];
+
+							v1 = value;
+							v2 = value;
+							v3 = value;
+							v4 = value;
+
+							customAttribute.array[ offset_custom ] 	   = v1.x;
+							customAttribute.array[ offset_custom + 1 ] = v1.y;
+
+							customAttribute.array[ offset_custom + 2 ] = v2.x;
+							customAttribute.array[ offset_custom + 3 ] = v2.y;
+
+							customAttribute.array[ offset_custom + 4 ] = v3.x;
+							customAttribute.array[ offset_custom + 5 ] = v3.y;
+
+							customAttribute.array[ offset_custom + 6 ] = v4.x;
+							customAttribute.array[ offset_custom + 7 ] = v4.y;
+
+							offset_custom += 8;
+
+						}
+
+					}
+
+				} else if ( customAttribute.size === 3 ) {
+
+					var pp;
+
+					if ( customAttribute.type === "c" ) {
+
+						pp = [ "r", "g", "b" ];
+
+					} else {
+
+						pp = [ "x", "y", "z" ];
+
+					}
+
+					if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+						for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+							face = obj_faces[ chunk_faces3[ f ]	];
+
+							v1 = customAttribute.value[ face.a ];
+							v2 = customAttribute.value[ face.b ];
+							v3 = customAttribute.value[ face.c ];
+
+							customAttribute.array[ offset_custom ] 	   = v1[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ];
+
+							offset_custom += 9;
+
+						}
+
+						for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+							face = obj_faces[ chunk_faces4[ f ] ];
+
+							v1 = customAttribute.value[ face.a ];
+							v2 = customAttribute.value[ face.b ];
+							v3 = customAttribute.value[ face.c ];
+							v4 = customAttribute.value[ face.d ];
+
+							customAttribute.array[ offset_custom  ] 	= v1[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 1  ] = v1[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 2  ] = v1[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 3  ] = v2[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 4  ] = v2[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 5  ] = v2[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 6  ] = v3[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 7  ] = v3[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 8  ] = v3[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 9  ] = v4[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 10 ] = v4[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 11 ] = v4[ pp[ 2 ] ];
+
+							offset_custom += 12;
+
+						}
+
+					} else if ( customAttribute.boundTo === "faces" ) {
+
+						for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces3[ f ] ];
+
+							v1 = value;
+							v2 = value;
+							v3 = value;
+
+							customAttribute.array[ offset_custom ] 	   = v1[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ];
+
+							offset_custom += 9;
+
+						}
+
+						for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces4[ f ] ];
+
+							v1 = value;
+							v2 = value;
+							v3 = value;
+							v4 = value;
+
+							customAttribute.array[ offset_custom  ] 	= v1[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 1  ] = v1[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 2  ] = v1[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 3  ] = v2[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 4  ] = v2[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 5  ] = v2[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 6  ] = v3[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 7  ] = v3[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 8  ] = v3[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 9  ] = v4[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 10 ] = v4[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 11 ] = v4[ pp[ 2 ] ];
+
+							offset_custom += 12;
+
+						}
+
+					} else if ( customAttribute.boundTo === "faceVertices" ) {
+
+						for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces3[ f ] ];
+
+							v1 = value[ 0 ];
+							v2 = value[ 1 ];
+							v3 = value[ 2 ];
+
+							customAttribute.array[ offset_custom ] 	   = v1[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ];
+
+							offset_custom += 9;
+
+						}
+
+						for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces4[ f ] ];
+
+							v1 = value[ 0 ];
+							v2 = value[ 1 ];
+							v3 = value[ 2 ];
+							v4 = value[ 3 ];
+
+							customAttribute.array[ offset_custom  ] 	= v1[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 1  ] = v1[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 2  ] = v1[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 3  ] = v2[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 4  ] = v2[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 5  ] = v2[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 6  ] = v3[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 7  ] = v3[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 8  ] = v3[ pp[ 2 ] ];
+
+							customAttribute.array[ offset_custom + 9  ] = v4[ pp[ 0 ] ];
+							customAttribute.array[ offset_custom + 10 ] = v4[ pp[ 1 ] ];
+							customAttribute.array[ offset_custom + 11 ] = v4[ pp[ 2 ] ];
+
+							offset_custom += 12;
+
+						}
+
+					}
+
+				} else if ( customAttribute.size === 4 ) {
+
+					if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+						for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+							face = obj_faces[ chunk_faces3[ f ]	];
+
+							v1 = customAttribute.value[ face.a ];
+							v2 = customAttribute.value[ face.b ];
+							v3 = customAttribute.value[ face.c ];
+
+							customAttribute.array[ offset_custom  ] 	= v1.x;
+							customAttribute.array[ offset_custom + 1  ] = v1.y;
+							customAttribute.array[ offset_custom + 2  ] = v1.z;
+							customAttribute.array[ offset_custom + 3  ] = v1.w;
+
+							customAttribute.array[ offset_custom + 4  ] = v2.x;
+							customAttribute.array[ offset_custom + 5  ] = v2.y;
+							customAttribute.array[ offset_custom + 6  ] = v2.z;
+							customAttribute.array[ offset_custom + 7  ] = v2.w;
+
+							customAttribute.array[ offset_custom + 8  ] = v3.x;
+							customAttribute.array[ offset_custom + 9  ] = v3.y;
+							customAttribute.array[ offset_custom + 10 ] = v3.z;
+							customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+							offset_custom += 12;
+
+						}
+
+						for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+							face = obj_faces[ chunk_faces4[ f ] ];
+
+							v1 = customAttribute.value[ face.a ];
+							v2 = customAttribute.value[ face.b ];
+							v3 = customAttribute.value[ face.c ];
+							v4 = customAttribute.value[ face.d ];
+
+							customAttribute.array[ offset_custom  ] 	= v1.x;
+							customAttribute.array[ offset_custom + 1  ] = v1.y;
+							customAttribute.array[ offset_custom + 2  ] = v1.z;
+							customAttribute.array[ offset_custom + 3  ] = v1.w;
+
+							customAttribute.array[ offset_custom + 4  ] = v2.x;
+							customAttribute.array[ offset_custom + 5  ] = v2.y;
+							customAttribute.array[ offset_custom + 6  ] = v2.z;
+							customAttribute.array[ offset_custom + 7  ] = v2.w;
+
+							customAttribute.array[ offset_custom + 8  ] = v3.x;
+							customAttribute.array[ offset_custom + 9  ] = v3.y;
+							customAttribute.array[ offset_custom + 10 ] = v3.z;
+							customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+							customAttribute.array[ offset_custom + 12 ] = v4.x;
+							customAttribute.array[ offset_custom + 13 ] = v4.y;
+							customAttribute.array[ offset_custom + 14 ] = v4.z;
+							customAttribute.array[ offset_custom + 15 ] = v4.w;
+
+							offset_custom += 16;
+
+						}
+
+					} else if ( customAttribute.boundTo === "faces" ) {
+
+						for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces3[ f ] ];
+
+							v1 = value;
+							v2 = value;
+							v3 = value;
+
+							customAttribute.array[ offset_custom  ] 	= v1.x;
+							customAttribute.array[ offset_custom + 1  ] = v1.y;
+							customAttribute.array[ offset_custom + 2  ] = v1.z;
+							customAttribute.array[ offset_custom + 3  ] = v1.w;
+
+							customAttribute.array[ offset_custom + 4  ] = v2.x;
+							customAttribute.array[ offset_custom + 5  ] = v2.y;
+							customAttribute.array[ offset_custom + 6  ] = v2.z;
+							customAttribute.array[ offset_custom + 7  ] = v2.w;
+
+							customAttribute.array[ offset_custom + 8  ] = v3.x;
+							customAttribute.array[ offset_custom + 9  ] = v3.y;
+							customAttribute.array[ offset_custom + 10 ] = v3.z;
+							customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+							offset_custom += 12;
+
+						}
+
+						for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces4[ f ] ];
+
+							v1 = value;
+							v2 = value;
+							v3 = value;
+							v4 = value;
+
+							customAttribute.array[ offset_custom  ] 	= v1.x;
+							customAttribute.array[ offset_custom + 1  ] = v1.y;
+							customAttribute.array[ offset_custom + 2  ] = v1.z;
+							customAttribute.array[ offset_custom + 3  ] = v1.w;
+
+							customAttribute.array[ offset_custom + 4  ] = v2.x;
+							customAttribute.array[ offset_custom + 5  ] = v2.y;
+							customAttribute.array[ offset_custom + 6  ] = v2.z;
+							customAttribute.array[ offset_custom + 7  ] = v2.w;
+
+							customAttribute.array[ offset_custom + 8  ] = v3.x;
+							customAttribute.array[ offset_custom + 9  ] = v3.y;
+							customAttribute.array[ offset_custom + 10 ] = v3.z;
+							customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+							customAttribute.array[ offset_custom + 12 ] = v4.x;
+							customAttribute.array[ offset_custom + 13 ] = v4.y;
+							customAttribute.array[ offset_custom + 14 ] = v4.z;
+							customAttribute.array[ offset_custom + 15 ] = v4.w;
+
+							offset_custom += 16;
+
+						}
+
+					} else if ( customAttribute.boundTo === "faceVertices" ) {
+
+						for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces3[ f ] ];
+
+							v1 = value[ 0 ];
+							v2 = value[ 1 ];
+							v3 = value[ 2 ];
+
+							customAttribute.array[ offset_custom  ] 	= v1.x;
+							customAttribute.array[ offset_custom + 1  ] = v1.y;
+							customAttribute.array[ offset_custom + 2  ] = v1.z;
+							customAttribute.array[ offset_custom + 3  ] = v1.w;
+
+							customAttribute.array[ offset_custom + 4  ] = v2.x;
+							customAttribute.array[ offset_custom + 5  ] = v2.y;
+							customAttribute.array[ offset_custom + 6  ] = v2.z;
+							customAttribute.array[ offset_custom + 7  ] = v2.w;
+
+							customAttribute.array[ offset_custom + 8  ] = v3.x;
+							customAttribute.array[ offset_custom + 9  ] = v3.y;
+							customAttribute.array[ offset_custom + 10 ] = v3.z;
+							customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+							offset_custom += 12;
+
+						}
+
+						for ( f = 0, fl = chunk_faces4.length; f < fl; f ++ ) {
+
+							value = customAttribute.value[ chunk_faces4[ f ] ];
+
+							v1 = value[ 0 ];
+							v2 = value[ 1 ];
+							v3 = value[ 2 ];
+							v4 = value[ 3 ];
+
+							customAttribute.array[ offset_custom  ] 	= v1.x;
+							customAttribute.array[ offset_custom + 1  ] = v1.y;
+							customAttribute.array[ offset_custom + 2  ] = v1.z;
+							customAttribute.array[ offset_custom + 3  ] = v1.w;
+
+							customAttribute.array[ offset_custom + 4  ] = v2.x;
+							customAttribute.array[ offset_custom + 5  ] = v2.y;
+							customAttribute.array[ offset_custom + 6  ] = v2.z;
+							customAttribute.array[ offset_custom + 7  ] = v2.w;
+
+							customAttribute.array[ offset_custom + 8  ] = v3.x;
+							customAttribute.array[ offset_custom + 9  ] = v3.y;
+							customAttribute.array[ offset_custom + 10 ] = v3.z;
+							customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+							customAttribute.array[ offset_custom + 12 ] = v4.x;
+							customAttribute.array[ offset_custom + 13 ] = v4.y;
+							customAttribute.array[ offset_custom + 14 ] = v4.z;
+							customAttribute.array[ offset_custom + 15 ] = v4.w;
+
+							offset_custom += 16;
+
+						}
+
+					}
+
+				}
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer );
+				_gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint );
+
+			}
+
+		}
+
+		if ( dispose ) {
+
+			delete geometryGroup.__inittedArrays;
+			delete geometryGroup.__colorArray;
+			delete geometryGroup.__normalArray;
+			delete geometryGroup.__tangentArray;
+			delete geometryGroup.__uvArray;
+			delete geometryGroup.__uv2Array;
+			delete geometryGroup.__faceArray;
+			delete geometryGroup.__vertexArray;
+			delete geometryGroup.__lineArray;
+			delete geometryGroup.__skinIndexArray;
+			delete geometryGroup.__skinWeightArray;
+
+		}
+
+	};
+
+	function setDirectBuffers ( geometry, hint, dispose ) {
+
+		var attributes = geometry.attributes;
+
+		var attributeName, attributeItem;
+
+		for ( attributeName in attributes ) {
+
+			attributeItem = attributes[ attributeName ];
+
+			if ( attributeItem.needsUpdate ) {
+
+				if ( attributeName === 'index' ) {
+
+					_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attributeItem.buffer );
+					_gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, attributeItem.array, hint );
+
+				} else {
+
+					_gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+					_gl.bufferData( _gl.ARRAY_BUFFER, attributeItem.array, hint );
+
+				}
+
+				attributeItem.needsUpdate = false;
+
+			}
+
+			if ( dispose && ! attributeItem.dynamic ) {
+
+				delete attributeItem.array;
+
+			}
+
+		}
+
+	};
+
+	// Buffer rendering
+
+	this.renderBufferImmediate = function ( object, program, material ) {
+
+		if ( object.hasPositions && ! object.__webglVertexBuffer ) object.__webglVertexBuffer = _gl.createBuffer();
+		if ( object.hasNormals && ! object.__webglNormalBuffer ) object.__webglNormalBuffer = _gl.createBuffer();
+		if ( object.hasUvs && ! object.__webglUvBuffer ) object.__webglUvBuffer = _gl.createBuffer();
+		if ( object.hasColors && ! object.__webglColorBuffer ) object.__webglColorBuffer = _gl.createBuffer();
+
+		if ( object.hasPositions ) {
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglVertexBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW );
+			_gl.enableVertexAttribArray( program.attributes.position );
+			_gl.vertexAttribPointer( program.attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+		}
+
+		if ( object.hasNormals ) {
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglNormalBuffer );
+
+			if ( material.shading === THREE.FlatShading ) {
+
+				var nx, ny, nz,
+					nax, nbx, ncx, nay, nby, ncy, naz, nbz, ncz,
+					normalArray,
+					i, il = object.count * 3;
+
+				for( i = 0; i < il; i += 9 ) {
+
+					normalArray = object.normalArray;
+
+					nax  = normalArray[ i ];
+					nay  = normalArray[ i + 1 ];
+					naz  = normalArray[ i + 2 ];
+
+					nbx  = normalArray[ i + 3 ];
+					nby  = normalArray[ i + 4 ];
+					nbz  = normalArray[ i + 5 ];
+
+					ncx  = normalArray[ i + 6 ];
+					ncy  = normalArray[ i + 7 ];
+					ncz  = normalArray[ i + 8 ];
+
+					nx = ( nax + nbx + ncx ) / 3;
+					ny = ( nay + nby + ncy ) / 3;
+					nz = ( naz + nbz + ncz ) / 3;
+
+					normalArray[ i ] 	 = nx;
+					normalArray[ i + 1 ] = ny;
+					normalArray[ i + 2 ] = nz;
+
+					normalArray[ i + 3 ] = nx;
+					normalArray[ i + 4 ] = ny;
+					normalArray[ i + 5 ] = nz;
+
+					normalArray[ i + 6 ] = nx;
+					normalArray[ i + 7 ] = ny;
+					normalArray[ i + 8 ] = nz;
+
+				}
+
+			}
+
+			_gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW );
+			_gl.enableVertexAttribArray( program.attributes.normal );
+			_gl.vertexAttribPointer( program.attributes.normal, 3, _gl.FLOAT, false, 0, 0 );
+
+		}
+
+		if ( object.hasUvs && material.map ) {
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglUvBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW );
+			_gl.enableVertexAttribArray( program.attributes.uv );
+			_gl.vertexAttribPointer( program.attributes.uv, 2, _gl.FLOAT, false, 0, 0 );
+
+		}
+
+		if ( object.hasColors && material.vertexColors !== THREE.NoColors ) {
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglColorBuffer );
+			_gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW );
+			_gl.enableVertexAttribArray( program.attributes.color );
+			_gl.vertexAttribPointer( program.attributes.color, 3, _gl.FLOAT, false, 0, 0 );
+
+		}
+
+		_gl.drawArrays( _gl.TRIANGLES, 0, object.count );
+
+		object.count = 0;
+
+	};
+
+	this.renderBufferDirect = function ( camera, lights, fog, material, geometry, object ) {
+
+		if ( material.visible === false ) return;
+
+		var program, programAttributes, linewidth, primitives, a, attribute, geometryAttributes;
+		var attributeItem, attributeName, attributePointer, attributeSize;
+
+		program = setProgram( camera, lights, fog, material, object );
+
+		programAttributes = program.attributes;
+		geometryAttributes = geometry.attributes;
+
+		var updateBuffers = false,
+			wireframeBit = material.wireframe ? 1 : 0,
+			geometryHash = ( geometry.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit;
+
+		if ( geometryHash !== _currentGeometryGroupHash ) {
+
+			_currentGeometryGroupHash = geometryHash;
+			updateBuffers = true;
+
+		}
+
+		if ( updateBuffers ) {
+
+			disableAttributes();
+
+		}
+
+		// render mesh
+
+		if ( object instanceof THREE.Mesh ) {
+
+			var index = geometryAttributes[ "index" ];
+
+			// indexed triangles
+
+			if ( index ) {
+
+				var offsets = geometry.offsets;
+
+				// if there is more than 1 chunk
+				// must set attribute pointers to use new offsets for each chunk
+				// even if geometry and materials didn't change
+
+				if ( offsets.length > 1 ) updateBuffers = true;
+
+				for ( var i = 0, il = offsets.length; i < il; i ++ ) {
+
+					var startIndex = offsets[ i ].index;
+
+					if ( updateBuffers ) {
+
+						for ( attributeName in geometryAttributes ) {
+
+							if ( attributeName === 'index' ) continue;
+
+							attributePointer = programAttributes[ attributeName ];
+							attributeItem = geometryAttributes[ attributeName ];
+							attributeSize = attributeItem.itemSize;
+
+							if ( attributePointer >= 0 ) {
+
+								_gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+								enableAttribute( attributePointer );
+								_gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, startIndex * attributeSize * 4 ); // 4 bytes per Float32
+
+							}
+
+						}
+
+						// indices
+
+						_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer );
+
+					}
+
+					// render indexed triangles
+
+					_gl.drawElements( _gl.TRIANGLES, offsets[ i ].count, _gl.UNSIGNED_SHORT, offsets[ i ].start * 2 ); // 2 bytes per Uint16
+
+					_this.info.render.calls ++;
+					_this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared
+					_this.info.render.faces += offsets[ i ].count / 3;
+
+				}
+
+			// non-indexed triangles
+
+			} else {
+
+				if ( updateBuffers ) {
+
+					for ( attributeName in geometryAttributes ) {
+
+						if ( attributeName === 'index') continue;
+
+						attributePointer = programAttributes[ attributeName ];
+						attributeItem = geometryAttributes[ attributeName ];
+						attributeSize = attributeItem.itemSize;
+
+						if ( attributePointer >= 0 ) {
+
+							_gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+							enableAttribute( attributePointer );
+							_gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 );
+
+						}
+
+					}
+
+				}
+
+				var position = geometry.attributes[ "position" ];
+
+				// render non-indexed triangles
+
+				_gl.drawArrays( _gl.TRIANGLES, 0, position.numItems / 3 );
+
+				_this.info.render.calls ++;
+				_this.info.render.vertices += position.numItems / 3;
+				_this.info.render.faces += position.numItems / 3 / 3;
+
+			}
+
+		// render particles
+
+		} else if ( object instanceof THREE.ParticleSystem ) {
+
+			if ( updateBuffers ) {
+
+				for ( attributeName in geometryAttributes ) {
+
+					attributePointer = programAttributes[ attributeName ];
+					attributeItem = geometryAttributes[ attributeName ];
+					attributeSize = attributeItem.itemSize;
+
+					if ( attributePointer >= 0 ) {
+
+						_gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+						enableAttribute( attributePointer );
+						_gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 );
+
+					}
+
+				}
+
+				var position = geometryAttributes[ "position" ];
+
+				// render particles
+
+				_gl.drawArrays( _gl.POINTS, 0, position.numItems / 3 );
+
+				_this.info.render.calls ++;
+				_this.info.render.points += position.numItems / 3;
+
+			}
+
+		} else if ( object instanceof THREE.Line ) {
+
+			if ( updateBuffers ) {
+
+				for ( attributeName in geometryAttributes ) {
+
+					attributePointer = programAttributes[ attributeName ];
+					attributeItem = geometryAttributes[ attributeName ];
+					attributeSize = attributeItem.itemSize;
+
+					if ( attributePointer >= 0 ) {
+
+						_gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+						enableAttribute( attributePointer );
+						_gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 );
+
+					}
+
+				}
+
+				// render lines
+
+				setLineWidth( material.linewidth );
+
+				var position = geometryAttributes[ "position" ];
+
+				_gl.drawArrays( _gl.LINE_STRIP, 0, position.numItems / 3 );
+
+				_this.info.render.calls ++;
+				_this.info.render.points += position.numItems;
+
+			}
+
+    	}
+
+	};
+
+	this.renderBuffer = function ( camera, lights, fog, material, geometryGroup, object ) {
+
+		if ( material.visible === false ) return;
+
+		var program, attributes, linewidth, primitives, a, attribute, i, il;
+
+		program = setProgram( camera, lights, fog, material, object );
+
+		attributes = program.attributes;
+
+		var updateBuffers = false,
+			wireframeBit = material.wireframe ? 1 : 0,
+			geometryGroupHash = ( geometryGroup.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit;
+
+		if ( geometryGroupHash !== _currentGeometryGroupHash ) {
+
+			_currentGeometryGroupHash = geometryGroupHash;
+			updateBuffers = true;
+
+		}
+
+		if ( updateBuffers ) {
+
+			disableAttributes();
+
+		}
+
+		// vertices
+
+		if ( !material.morphTargets && attributes.position >= 0 ) {
+
+			if ( updateBuffers ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer );
+				enableAttribute( attributes.position );
+				_gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+			}
+
+		} else {
+
+			if ( object.morphTargetBase ) {
+
+				setupMorphTargets( material, geometryGroup, object );
+
+			}
+
+		}
+
+
+		if ( updateBuffers ) {
+
+			// custom attributes
+
+			// Use the per-geometryGroup custom attribute arrays which are setup in initMeshBuffers
+
+			if ( geometryGroup.__webglCustomAttributesList ) {
+
+				for ( i = 0, il = geometryGroup.__webglCustomAttributesList.length; i < il; i ++ ) {
+
+					attribute = geometryGroup.__webglCustomAttributesList[ i ];
+
+					if ( attributes[ attribute.buffer.belongsToAttribute ] >= 0 ) {
+
+						_gl.bindBuffer( _gl.ARRAY_BUFFER, attribute.buffer );
+						enableAttribute( attributes[ attribute.buffer.belongsToAttribute ] );
+						_gl.vertexAttribPointer( attributes[ attribute.buffer.belongsToAttribute ], attribute.size, _gl.FLOAT, false, 0, 0 );
+
+					}
+
+				}
+
+			}
+
+
+			// colors
+
+			if ( attributes.color >= 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer );
+				enableAttribute( attributes.color );
+				_gl.vertexAttribPointer( attributes.color, 3, _gl.FLOAT, false, 0, 0 );
+
+			}
+
+			// normals
+
+			if ( attributes.normal >= 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer );
+				enableAttribute( attributes.normal );
+				_gl.vertexAttribPointer( attributes.normal, 3, _gl.FLOAT, false, 0, 0 );
+
+			}
+
+			// tangents
+
+			if ( attributes.tangent >= 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglTangentBuffer );
+				enableAttribute( attributes.tangent );
+				_gl.vertexAttribPointer( attributes.tangent, 4, _gl.FLOAT, false, 0, 0 );
+
+			}
+
+			// uvs
+
+			if ( attributes.uv >= 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUVBuffer );
+				enableAttribute( attributes.uv );
+				_gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 0, 0 );
+
+			}
+
+			if ( attributes.uv2 >= 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUV2Buffer );
+				enableAttribute( attributes.uv2 );
+				_gl.vertexAttribPointer( attributes.uv2, 2, _gl.FLOAT, false, 0, 0 );
+
+			}
+
+			if ( material.skinning &&
+				 attributes.skinIndex >= 0 && attributes.skinWeight >= 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinIndicesBuffer );
+				enableAttribute( attributes.skinIndex );
+				_gl.vertexAttribPointer( attributes.skinIndex, 4, _gl.FLOAT, false, 0, 0 );
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinWeightsBuffer );
+				enableAttribute( attributes.skinWeight );
+				_gl.vertexAttribPointer( attributes.skinWeight, 4, _gl.FLOAT, false, 0, 0 );
+
+			}
+
+			// line distances
+
+			if ( attributes.lineDistance >= 0 ) {
+
+				_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglLineDistanceBuffer );
+				enableAttribute( attributes.lineDistance );
+				_gl.vertexAttribPointer( attributes.lineDistance, 1, _gl.FLOAT, false, 0, 0 );
+
+			}
+
+		}
+
+		// render mesh
+
+		if ( object instanceof THREE.Mesh ) {
+
+			// wireframe
+
+			if ( material.wireframe ) {
+
+				setLineWidth( material.wireframeLinewidth );
+
+				if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer );
+				_gl.drawElements( _gl.LINES, geometryGroup.__webglLineCount, _gl.UNSIGNED_SHORT, 0 );
+
+			// triangles
+
+			} else {
+
+				if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer );
+				_gl.drawElements( _gl.TRIANGLES, geometryGroup.__webglFaceCount, _gl.UNSIGNED_SHORT, 0 );
+
+			}
+
+			_this.info.render.calls ++;
+			_this.info.render.vertices += geometryGroup.__webglFaceCount;
+			_this.info.render.faces += geometryGroup.__webglFaceCount / 3;
+
+		// render lines
+
+		} else if ( object instanceof THREE.Line ) {
+
+			primitives = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES;
+
+			setLineWidth( material.linewidth );
+
+			_gl.drawArrays( primitives, 0, geometryGroup.__webglLineCount );
+
+			_this.info.render.calls ++;
+
+		// render particles
+
+		} else if ( object instanceof THREE.ParticleSystem ) {
+
+			_gl.drawArrays( _gl.POINTS, 0, geometryGroup.__webglParticleCount );
+
+			_this.info.render.calls ++;
+			_this.info.render.points += geometryGroup.__webglParticleCount;
+
+		// render ribbon
+
+		} else if ( object instanceof THREE.Ribbon ) {
+
+			_gl.drawArrays( _gl.TRIANGLE_STRIP, 0, geometryGroup.__webglVertexCount );
+
+			_this.info.render.calls ++;
+
+		}
+
+	};
+
+	function enableAttribute( attribute ) {
+
+		if ( ! _enabledAttributes[ attribute ] ) {
+
+			_gl.enableVertexAttribArray( attribute );
+			_enabledAttributes[ attribute ] = true;
+
+		}
+
+	};
+
+	function disableAttributes() {
+
+		for ( var attribute in _enabledAttributes ) {
+
+			if ( _enabledAttributes[ attribute ] ) {
+
+				_gl.disableVertexAttribArray( attribute );
+				_enabledAttributes[ attribute ] = false;
+
+			}
+
+		}
+
+	};
+
+	function setupMorphTargets ( material, geometryGroup, object ) {
+
+		// set base
+
+		var attributes = material.program.attributes;
+
+		if ( object.morphTargetBase !== -1 && attributes.position >= 0 ) {
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ object.morphTargetBase ] );
+			enableAttribute( attributes.position );
+			_gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+		} else if ( attributes.position >= 0 ) {
+
+			_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer );
+			enableAttribute( attributes.position );
+			_gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+		}
+
+		if ( object.morphTargetForcedOrder.length ) {
+
+			// set forced order
+
+			var m = 0;
+			var order = object.morphTargetForcedOrder;
+			var influences = object.morphTargetInfluences;
+
+			while ( m < material.numSupportedMorphTargets && m < order.length ) {
+
+				if ( attributes[ "morphTarget" + m ] >= 0 ) {
+
+					_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ order[ m ] ] );
+					enableAttribute( attributes[ "morphTarget" + m ] );
+					_gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+				}
+
+				if ( attributes[ "morphNormal" + m ] >= 0 && material.morphNormals ) {
+
+					_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ order[ m ] ] );
+					enableAttribute( attributes[ "morphNormal" + m ] );
+					_gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+				}
+
+				object.__webglMorphTargetInfluences[ m ] = influences[ order[ m ] ];
+
+				m ++;
+			}
+
+		} else {
+
+			// find the most influencing
+
+			var influence, activeInfluenceIndices = [];
+			var influences = object.morphTargetInfluences;
+			var i, il = influences.length;
+
+			for ( i = 0; i < il; i ++ ) {
+
+				influence = influences[ i ];
+
+				if ( influence > 0 ) {
+
+					activeInfluenceIndices.push( [ influence, i ] );
+
+				}
+
+			}
+
+			if ( activeInfluenceIndices.length > material.numSupportedMorphTargets ) {
+
+				activeInfluenceIndices.sort( numericalSort );
+				activeInfluenceIndices.length = material.numSupportedMorphTargets;
+
+			} else if ( activeInfluenceIndices.length > material.numSupportedMorphNormals ) {
+
+				activeInfluenceIndices.sort( numericalSort );
+
+			} else if ( activeInfluenceIndices.length === 0 ) {
+
+				activeInfluenceIndices.push( [ 0, 0 ] );
+
+			};
+
+			var influenceIndex, m = 0;
+
+			while ( m < material.numSupportedMorphTargets ) {
+
+				if ( activeInfluenceIndices[ m ] ) {
+
+					influenceIndex = activeInfluenceIndices[ m ][ 1 ];
+
+					if ( attributes[ "morphTarget" + m ] >= 0 ) {
+
+						_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ influenceIndex ] );
+						enableAttribute( attributes[ "morphTarget" + m ] );
+						_gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+					}
+
+					if ( attributes[ "morphNormal" + m ] >= 0 && material.morphNormals ) {
+
+						_gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ influenceIndex ] );
+						enableAttribute( attributes[ "morphNormal" + m ] );
+						_gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+
+					}
+
+					object.__webglMorphTargetInfluences[ m ] = influences[ influenceIndex ];
+
+				} else {
+
+					/*
+					_gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+					if ( material.morphNormals ) {
+
+						_gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+					}
+					*/
+
+					object.__webglMorphTargetInfluences[ m ] = 0;
+
+				}
+
+				m ++;
+
+			}
+
+		}
+
+		// load updated influences uniform
+
+		if ( material.program.uniforms.morphTargetInfluences !== null ) {
+
+			_gl.uniform1fv( material.program.uniforms.morphTargetInfluences, object.__webglMorphTargetInfluences );
+
+		}
+
+	};
+
+	// Sorting
+
+	function painterSortStable ( a, b ) {
+
+		if ( a.z !== b.z ) {
+
+			return b.z - a.z;
+
+		} else {
+
+			return a.id - b.id;
+
+		}
+
+	};
+
+	function numericalSort ( a, b ) {
+
+		return b[ 0 ] - a[ 0 ];
+
+	};
+
+
+	// Rendering
+
+	this.render = function ( scene, camera, renderTarget, forceClear ) {
+
+		if ( camera instanceof THREE.Camera === false ) {
+
+			console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );
+			return;
+
+		}
+
+		var i, il,
+
+		webglObject, object,
+		renderList,
+
+		lights = scene.__lights,
+		fog = scene.fog;
+
+		// reset caching for this frame
+
+		_currentMaterialId = -1;
+		_lightsNeedUpdate = true;
+
+		// update scene graph
+
+		if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+		// update camera matrices and frustum
+
+		if ( camera.parent === undefined ) camera.updateMatrixWorld();
+
+		camera.matrixWorldInverse.getInverse( camera.matrixWorld );
+
+		_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+		_frustum.setFromMatrix( _projScreenMatrix );
+
+		// update WebGL objects
+
+		if ( this.autoUpdateObjects ) this.initWebGLObjects( scene );
+
+		// custom render plugins (pre pass)
+
+		renderPlugins( this.renderPluginsPre, scene, camera );
+
+		//
+
+		_this.info.render.calls = 0;
+		_this.info.render.vertices = 0;
+		_this.info.render.faces = 0;
+		_this.info.render.points = 0;
+
+		this.setRenderTarget( renderTarget );
+
+		if ( this.autoClear || forceClear ) {
+
+			this.clear( this.autoClearColor, this.autoClearDepth, this.autoClearStencil );
+
+		}
+
+		// set matrices for regular objects (frustum culled)
+
+		renderList = scene.__webglObjects;
+
+		for ( i = 0, il = renderList.length; i < il; i ++ ) {
+
+			webglObject = renderList[ i ];
+			object = webglObject.object;
+
+			webglObject.id = i;
+			webglObject.render = false;
+
+			if ( object.visible ) {
+
+				if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) {
+
+					setupMatrices( object, camera );
+
+					unrollBufferMaterial( webglObject );
+
+					webglObject.render = true;
+
+					if ( this.sortObjects === true ) {
+
+						if ( object.renderDepth !== null ) {
+
+							webglObject.z = object.renderDepth;
+
+						} else {
+
+							_vector3.getPositionFromMatrix( object.matrixWorld );
+							_vector3.applyProjection( _projScreenMatrix );
+
+							webglObject.z = _vector3.z;
+
+						}
+
+					}
+
+				}
+
+			}
+
+		}
+
+		if ( this.sortObjects ) {
+
+			renderList.sort( painterSortStable );
+
+		}
+
+		// set matrices for immediate objects
+
+		renderList = scene.__webglObjectsImmediate;
+
+		for ( i = 0, il = renderList.length; i < il; i ++ ) {
+
+			webglObject = renderList[ i ];
+			object = webglObject.object;
+
+			if ( object.visible ) {
+
+				setupMatrices( object, camera );
+
+				unrollImmediateBufferMaterial( webglObject );
+
+			}
+
+		}
+
+		if ( scene.overrideMaterial ) {
+
+			var material = scene.overrideMaterial;
+
+			this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+			this.setDepthTest( material.depthTest );
+			this.setDepthWrite( material.depthWrite );
+			setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+			renderObjects( scene.__webglObjects, false, "", camera, lights, fog, true, material );
+			renderObjectsImmediate( scene.__webglObjectsImmediate, "", camera, lights, fog, false, material );
+
+		} else {
+
+			var material = null;
+
+			// opaque pass (front-to-back order)
+
+			this.setBlending( THREE.NoBlending );
+
+			renderObjects( scene.__webglObjects, true, "opaque", camera, lights, fog, false, material );
+			renderObjectsImmediate( scene.__webglObjectsImmediate, "opaque", camera, lights, fog, false, material );
+
+			// transparent pass (back-to-front order)
+
+			renderObjects( scene.__webglObjects, false, "transparent", camera, lights, fog, true, material );
+			renderObjectsImmediate( scene.__webglObjectsImmediate, "transparent", camera, lights, fog, true, material );
+
+		}
+
+		// custom render plugins (post pass)
+
+		renderPlugins( this.renderPluginsPost, scene, camera );
+
+
+		// Generate mipmap if we're using any kind of mipmap filtering
+
+		if ( renderTarget && renderTarget.generateMipmaps && renderTarget.minFilter !== THREE.NearestFilter && renderTarget.minFilter !== THREE.LinearFilter ) {
+
+			updateRenderTargetMipmap( renderTarget );
+
+		}
+
+		// Ensure depth buffer writing is enabled so it can be cleared on next render
+
+		this.setDepthTest( true );
+		this.setDepthWrite( true );
+
+		// _gl.finish();
+
+	};
+
+	function renderPlugins( plugins, scene, camera ) {
+
+		if ( ! plugins.length ) return;
+
+		for ( var i = 0, il = plugins.length; i < il; i ++ ) {
+
+			// reset state for plugin (to start from clean slate)
+
+			_currentProgram = null;
+			_currentCamera = null;
+
+			_oldBlending = -1;
+			_oldDepthTest = -1;
+			_oldDepthWrite = -1;
+			_oldDoubleSided = -1;
+			_oldFlipSided = -1;
+			_currentGeometryGroupHash = -1;
+			_currentMaterialId = -1;
+
+			_lightsNeedUpdate = true;
+
+			plugins[ i ].render( scene, camera, _currentWidth, _currentHeight );
+
+			// reset state after plugin (anything could have changed)
+
+			_currentProgram = null;
+			_currentCamera = null;
+
+			_oldBlending = -1;
+			_oldDepthTest = -1;
+			_oldDepthWrite = -1;
+			_oldDoubleSided = -1;
+			_oldFlipSided = -1;
+			_currentGeometryGroupHash = -1;
+			_currentMaterialId = -1;
+
+			_lightsNeedUpdate = true;
+
+		}
+
+	};
+
+	function renderObjects ( renderList, reverse, materialType, camera, lights, fog, useBlending, overrideMaterial ) {
+
+		var webglObject, object, buffer, material, start, end, delta;
+
+		if ( reverse ) {
+
+			start = renderList.length - 1;
+			end = -1;
+			delta = -1;
+
+		} else {
+
+			start = 0;
+			end = renderList.length;
+			delta = 1;
+		}
+
+		for ( var i = start; i !== end; i += delta ) {
+
+			webglObject = renderList[ i ];
+
+			if ( webglObject.render ) {
+
+				object = webglObject.object;
+				buffer = webglObject.buffer;
+
+				if ( overrideMaterial ) {
+
+					material = overrideMaterial;
+
+				} else {
+
+					material = webglObject[ materialType ];
+
+					if ( ! material ) continue;
+
+					if ( useBlending ) _this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+
+					_this.setDepthTest( material.depthTest );
+					_this.setDepthWrite( material.depthWrite );
+					setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+				}
+
+				_this.setMaterialFaces( material );
+
+				if ( buffer instanceof THREE.BufferGeometry ) {
+
+					_this.renderBufferDirect( camera, lights, fog, material, buffer, object );
+
+				} else {
+
+					_this.renderBuffer( camera, lights, fog, material, buffer, object );
+
+				}
+
+			}
+
+		}
+
+	};
+
+	function renderObjectsImmediate ( renderList, materialType, camera, lights, fog, useBlending, overrideMaterial ) {
+
+		var webglObject, object, material, program;
+
+		for ( var i = 0, il = renderList.length; i < il; i ++ ) {
+
+			webglObject = renderList[ i ];
+			object = webglObject.object;
+
+			if ( object.visible ) {
+
+				if ( overrideMaterial ) {
+
+					material = overrideMaterial;
+
+				} else {
+
+					material = webglObject[ materialType ];
+
+					if ( ! material ) continue;
+
+					if ( useBlending ) _this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+
+					_this.setDepthTest( material.depthTest );
+					_this.setDepthWrite( material.depthWrite );
+					setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+				}
+
+				_this.renderImmediateObject( camera, lights, fog, material, object );
+
+			}
+
+		}
+
+	};
+
+	this.renderImmediateObject = function ( camera, lights, fog, material, object ) {
+
+		var program = setProgram( camera, lights, fog, material, object );
+
+		_currentGeometryGroupHash = -1;
+
+		_this.setMaterialFaces( material );
+
+		if ( object.immediateRenderCallback ) {
+
+			object.immediateRenderCallback( program, _gl, _frustum );
+
+		} else {
+
+			object.render( function( object ) { _this.renderBufferImmediate( object, program, material ); } );
+
+		}
+
+	};
+
+	function unrollImmediateBufferMaterial ( globject ) {
+
+		var object = globject.object,
+			material = object.material;
+
+		if ( material.transparent ) {
+
+			globject.transparent = material;
+			globject.opaque = null;
+
+		} else {
+
+			globject.opaque = material;
+			globject.transparent = null;
+
+		}
+
+	};
+
+	function unrollBufferMaterial ( globject ) {
+
+		var object = globject.object,
+			buffer = globject.buffer,
+			material, materialIndex, meshMaterial;
+
+		meshMaterial = object.material;
+
+		if ( meshMaterial instanceof THREE.MeshFaceMaterial ) {
+
+			materialIndex = buffer.materialIndex;
+
+			material = meshMaterial.materials[ materialIndex ];
+
+			if ( material.transparent ) {
+
+				globject.transparent = material;
+				globject.opaque = null;
+
+			} else {
+
+				globject.opaque = material;
+				globject.transparent = null;
+
+			}
+
+		} else {
+
+			material = meshMaterial;
+
+			if ( material ) {
+
+				if ( material.transparent ) {
+
+					globject.transparent = material;
+					globject.opaque = null;
+
+				} else {
+
+					globject.opaque = material;
+					globject.transparent = null;
+
+				}
+
+			}
+
+		}
+
+	};
+
+	// Geometry splitting
+
+	function sortFacesByMaterial ( geometry, material ) {
+
+		var f, fl, face, materialIndex, vertices,
+			groupHash, hash_map = {};
+
+		var numMorphTargets = geometry.morphTargets.length;
+		var numMorphNormals = geometry.morphNormals.length;
+
+		var usesFaceMaterial = material instanceof THREE.MeshFaceMaterial;
+
+		geometry.geometryGroups = {};
+
+		for ( f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
+
+			face = geometry.faces[ f ];
+			materialIndex = usesFaceMaterial ? face.materialIndex : 0;
+
+			if ( hash_map[ materialIndex ] === undefined ) {
+
+				hash_map[ materialIndex ] = { 'hash': materialIndex, 'counter': 0 };
+
+			}
+
+			groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter;
+
+			if ( geometry.geometryGroups[ groupHash ] === undefined ) {
+
+				geometry.geometryGroups[ groupHash ] = { 'faces3': [], 'faces4': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals };
+
+			}
+
+			vertices = face instanceof THREE.Face3 ? 3 : 4;
+
+			if ( geometry.geometryGroups[ groupHash ].vertices + vertices > 65535 ) {
+
+				hash_map[ materialIndex ].counter += 1;
+				groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter;
+
+				if ( geometry.geometryGroups[ groupHash ] === undefined ) {
+
+					geometry.geometryGroups[ groupHash ] = { 'faces3': [], 'faces4': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals };
+
+				}
+
+			}
+
+			if ( face instanceof THREE.Face3 ) {
+
+				geometry.geometryGroups[ groupHash ].faces3.push( f );
+
+			} else {
+
+				geometry.geometryGroups[ groupHash ].faces4.push( f );
+
+			}
+
+			geometry.geometryGroups[ groupHash ].vertices += vertices;
+
+		}
+
+		geometry.geometryGroupsList = [];
+
+		for ( var g in geometry.geometryGroups ) {
+
+			geometry.geometryGroups[ g ].id = _geometryGroupCounter ++;
+
+			geometry.geometryGroupsList.push( geometry.geometryGroups[ g ] );
+
+		}
+
+	};
+
+	// Objects refresh
+
+	this.initWebGLObjects = function ( scene ) {
+
+		if ( !scene.__webglObjects ) {
+
+			scene.__webglObjects = [];
+			scene.__webglObjectsImmediate = [];
+			scene.__webglSprites = [];
+			scene.__webglFlares = [];
+
+		}
+
+		while ( scene.__objectsAdded.length ) {
+
+			addObject( scene.__objectsAdded[ 0 ], scene );
+			scene.__objectsAdded.splice( 0, 1 );
+
+		}
+
+		while ( scene.__objectsRemoved.length ) {
+
+			removeObject( scene.__objectsRemoved[ 0 ], scene );
+			scene.__objectsRemoved.splice( 0, 1 );
+
+		}
+
+		// update must be called after objects adding / removal
+
+		for ( var o = 0, ol = scene.__webglObjects.length; o < ol; o ++ ) {
+
+			var object = scene.__webglObjects[ o ].object;
+
+			// TODO: Remove this hack (WebGLRenderer refactoring)
+
+			if ( object.__webglInit === undefined ) {
+
+				if ( object.__webglActive !== undefined ) {
+
+					removeObject( object, scene );
+
+				}
+
+				addObject( object, scene );
+
+			}
+
+			updateObject( object );
+
+		}
+
+	};
+
+	// Objects adding
+
+	function addObject( object, scene ) {
+
+		var g, geometry, material, geometryGroup;
+
+		if ( object.__webglInit === undefined ) {
+
+			object.__webglInit = true;
+
+			object._modelViewMatrix = new THREE.Matrix4();
+			object._normalMatrix = new THREE.Matrix3();
+
+			if ( object.geometry !== undefined && object.geometry.__webglInit === undefined ) {
+
+				object.geometry.__webglInit = true;
+				object.geometry.addEventListener( 'dispose', onGeometryDispose );
+
+			}
+
+			geometry = object.geometry;
+
+			if ( geometry === undefined ) {
+
+				// fail silently for now
+
+			} else if ( geometry instanceof THREE.BufferGeometry ) {
+
+				initDirectBuffers( geometry );
+
+			} else if ( object instanceof THREE.Mesh ) {
+
+				material = object.material;
+
+				if ( geometry.geometryGroups === undefined ) {
+
+					sortFacesByMaterial( geometry, material );
+
+				}
+
+				// create separate VBOs per geometry chunk
+
+				for ( g in geometry.geometryGroups ) {
+
+					geometryGroup = geometry.geometryGroups[ g ];
+
+					// initialise VBO on the first access
+
+					if ( ! geometryGroup.__webglVertexBuffer ) {
+
+						createMeshBuffers( geometryGroup );
+						initMeshBuffers( geometryGroup, object );
+
+						geometry.verticesNeedUpdate = true;
+						geometry.morphTargetsNeedUpdate = true;
+						geometry.elementsNeedUpdate = true;
+						geometry.uvsNeedUpdate = true;
+						geometry.normalsNeedUpdate = true;
+						geometry.tangentsNeedUpdate = true;
+						geometry.colorsNeedUpdate = true;
+
+					}
+
+				}
+
+			} else if ( object instanceof THREE.Ribbon ) {
+
+				if ( ! geometry.__webglVertexBuffer ) {
+
+					createRibbonBuffers( geometry );
+					initRibbonBuffers( geometry, object );
+
+					geometry.verticesNeedUpdate = true;
+					geometry.colorsNeedUpdate = true;
+					geometry.normalsNeedUpdate = true;
+
+				}
+
+			} else if ( object instanceof THREE.Line ) {
+
+				if ( ! geometry.__webglVertexBuffer ) {
+
+					createLineBuffers( geometry );
+					initLineBuffers( geometry, object );
+
+					geometry.verticesNeedUpdate = true;
+					geometry.colorsNeedUpdate = true;
+					geometry.lineDistancesNeedUpdate = true;
+
+				}
+
+			} else if ( object instanceof THREE.ParticleSystem ) {
+
+				if ( ! geometry.__webglVertexBuffer ) {
+
+					createParticleBuffers( geometry );
+					initParticleBuffers( geometry, object );
+
+					geometry.verticesNeedUpdate = true;
+					geometry.colorsNeedUpdate = true;
+
+				}
+
+			}
+
+		}
+
+		if ( object.__webglActive === undefined ) {
+
+			if ( object instanceof THREE.Mesh ) {
+
+				geometry = object.geometry;
+
+				if ( geometry instanceof THREE.BufferGeometry ) {
+
+					addBuffer( scene.__webglObjects, geometry, object );
+
+				} else if ( geometry instanceof THREE.Geometry ) {
+
+					for ( g in geometry.geometryGroups ) {
+
+						geometryGroup = geometry.geometryGroups[ g ];
+
+						addBuffer( scene.__webglObjects, geometryGroup, object );
+
+					}
+
+				}
+
+			} else if ( object instanceof THREE.Ribbon ||
+						object instanceof THREE.Line ||
+						object instanceof THREE.ParticleSystem ) {
+
+				geometry = object.geometry;
+				addBuffer( scene.__webglObjects, geometry, object );
+
+			} else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) {
+
+				addBufferImmediate( scene.__webglObjectsImmediate, object );
+
+			} else if ( object instanceof THREE.Sprite ) {
+
+				scene.__webglSprites.push( object );
+
+			} else if ( object instanceof THREE.LensFlare ) {
+
+				scene.__webglFlares.push( object );
+
+			}
+
+			object.__webglActive = true;
+
+		}
+
+	};
+
+	function addBuffer( objlist, buffer, object ) {
+
+		objlist.push(
+			{
+				buffer: buffer,
+				object: object,
+				opaque: null,
+				transparent: null
+			}
+		);
+
+	};
+
+	function addBufferImmediate( objlist, object ) {
+
+		objlist.push(
+			{
+				object: object,
+				opaque: null,
+				transparent: null
+			}
+		);
+
+	};
+
+	// Objects updates
+
+	function updateObject( object ) {
+
+		var geometry = object.geometry,
+			geometryGroup, customAttributesDirty, material;
+
+		if ( geometry instanceof THREE.BufferGeometry ) {
+
+			setDirectBuffers( geometry, _gl.DYNAMIC_DRAW, !geometry.dynamic );
+
+		} else if ( object instanceof THREE.Mesh ) {
+
+			// check all geometry groups
+
+			for( var i = 0, il = geometry.geometryGroupsList.length; i < il; i ++ ) {
+
+				geometryGroup = geometry.geometryGroupsList[ i ];
+
+				material = getBufferMaterial( object, geometryGroup );
+
+				if ( geometry.buffersNeedUpdate ) {
+
+					initMeshBuffers( geometryGroup, object );
+
+				}
+
+				customAttributesDirty = material.attributes && areCustomAttributesDirty( material );
+
+				if ( geometry.verticesNeedUpdate || geometry.morphTargetsNeedUpdate || geometry.elementsNeedUpdate ||
+					 geometry.uvsNeedUpdate || geometry.normalsNeedUpdate ||
+					 geometry.colorsNeedUpdate || geometry.tangentsNeedUpdate || customAttributesDirty ) {
+
+					setMeshBuffers( geometryGroup, object, _gl.DYNAMIC_DRAW, !geometry.dynamic, material );
+
+				}
+
+			}
+
+			geometry.verticesNeedUpdate = false;
+			geometry.morphTargetsNeedUpdate = false;
+			geometry.elementsNeedUpdate = false;
+			geometry.uvsNeedUpdate = false;
+			geometry.normalsNeedUpdate = false;
+			geometry.colorsNeedUpdate = false;
+			geometry.tangentsNeedUpdate = false;
+
+			geometry.buffersNeedUpdate = false;
+
+			material.attributes && clearCustomAttributes( material );
+
+		} else if ( object instanceof THREE.Ribbon ) {
+
+			material = getBufferMaterial( object, geometry );
+
+			customAttributesDirty = material.attributes && areCustomAttributesDirty( material );
+
+			if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || geometry.normalsNeedUpdate || customAttributesDirty ) {
+
+				setRibbonBuffers( geometry, _gl.DYNAMIC_DRAW );
+
+			}
+
+			geometry.verticesNeedUpdate = false;
+			geometry.colorsNeedUpdate = false;
+			geometry.normalsNeedUpdate = false;
+
+			material.attributes && clearCustomAttributes( material );
+
+		} else if ( object instanceof THREE.Line ) {
+
+			material = getBufferMaterial( object, geometry );
+
+			customAttributesDirty = material.attributes && areCustomAttributesDirty( material );
+
+			if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || geometry.lineDistancesNeedUpdate || customAttributesDirty ) {
+
+				setLineBuffers( geometry, _gl.DYNAMIC_DRAW );
+
+			}
+
+			geometry.verticesNeedUpdate = false;
+			geometry.colorsNeedUpdate = false;
+			geometry.lineDistancesNeedUpdate = false;
+
+			material.attributes && clearCustomAttributes( material );
+
+
+		} else if ( object instanceof THREE.ParticleSystem ) {
+
+			material = getBufferMaterial( object, geometry );
+
+			customAttributesDirty = material.attributes && areCustomAttributesDirty( material );
+
+			if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || object.sortParticles || customAttributesDirty ) {
+
+				setParticleBuffers( geometry, _gl.DYNAMIC_DRAW, object );
+
+			}
+
+			geometry.verticesNeedUpdate = false;
+			geometry.colorsNeedUpdate = false;
+
+			material.attributes && clearCustomAttributes( material );
+
+		}
+
+	};
+
+	// Objects updates - custom attributes check
+
+	function areCustomAttributesDirty( material ) {
+
+		for ( var a in material.attributes ) {
+
+			if ( material.attributes[ a ].needsUpdate ) return true;
+
+		}
+
+		return false;
+
+	};
+
+	function clearCustomAttributes( material ) {
+
+		for ( var a in material.attributes ) {
+
+			material.attributes[ a ].needsUpdate = false;
+
+		}
+
+	};
+
+	// Objects removal
+
+	function removeObject( object, scene ) {
+
+		if ( object instanceof THREE.Mesh  ||
+			 object instanceof THREE.ParticleSystem ||
+			 object instanceof THREE.Ribbon ||
+			 object instanceof THREE.Line ) {
+
+			removeInstances( scene.__webglObjects, object );
+
+		} else if ( object instanceof THREE.Sprite ) {
+
+			removeInstancesDirect( scene.__webglSprites, object );
+
+		} else if ( object instanceof THREE.LensFlare ) {
+
+			removeInstancesDirect( scene.__webglFlares, object );
+
+		} else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) {
+
+			removeInstances( scene.__webglObjectsImmediate, object );
+
+		}
+
+		delete object.__webglActive;
+
+	};
+
+	function removeInstances( objlist, object ) {
+
+		for ( var o = objlist.length - 1; o >= 0; o -- ) {
+
+			if ( objlist[ o ].object === object ) {
+
+				objlist.splice( o, 1 );
+
+			}
+
+		}
+
+	};
+
+	function removeInstancesDirect( objlist, object ) {
+
+		for ( var o = objlist.length - 1; o >= 0; o -- ) {
+
+			if ( objlist[ o ] === object ) {
+
+				objlist.splice( o, 1 );
+
+			}
+
+		}
+
+	};
+
+	// Materials
+
+	this.initMaterial = function ( material, lights, fog, object ) {
+
+		material.addEventListener( 'dispose', onMaterialDispose );
+
+		var u, a, identifiers, i, parameters, maxLightCount, maxBones, maxShadows, shaderID;
+
+		if ( material instanceof THREE.MeshDepthMaterial ) {
+
+			shaderID = 'depth';
+
+		} else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+			shaderID = 'normal';
+
+		} else if ( material instanceof THREE.MeshBasicMaterial ) {
+
+			shaderID = 'basic';
+
+		} else if ( material instanceof THREE.MeshLambertMaterial ) {
+
+			shaderID = 'lambert';
+
+		} else if ( material instanceof THREE.MeshPhongMaterial ) {
+
+			shaderID = 'phong';
+
+		} else if ( material instanceof THREE.LineBasicMaterial ) {
+
+			shaderID = 'basic';
+
+		} else if ( material instanceof THREE.LineDashedMaterial ) {
+
+			shaderID = 'dashed';
+
+		} else if ( material instanceof THREE.ParticleBasicMaterial ) {
+
+			shaderID = 'particle_basic';
+
+		}
+
+		if ( shaderID ) {
+
+			setMaterialShaders( material, THREE.ShaderLib[ shaderID ] );
+
+		}
+
+		// heuristics to create shader parameters according to lights in the scene
+		// (not to blow over maxLights budget)
+
+		maxLightCount = allocateLights( lights );
+
+		maxShadows = allocateShadows( lights );
+
+		maxBones = allocateBones( object );
+
+		parameters = {
+
+			map: !!material.map,
+			envMap: !!material.envMap,
+			lightMap: !!material.lightMap,
+			bumpMap: !!material.bumpMap,
+			normalMap: !!material.normalMap,
+			specularMap: !!material.specularMap,
+
+			vertexColors: material.vertexColors,
+
+			fog: fog,
+			useFog: material.fog,
+			fogExp: fog instanceof THREE.FogExp2,
+
+			sizeAttenuation: material.sizeAttenuation,
+
+			skinning: material.skinning,
+			maxBones: maxBones,
+			useVertexTexture: _supportsBoneTextures && object && object.useVertexTexture,
+			boneTextureWidth: object && object.boneTextureWidth,
+			boneTextureHeight: object && object.boneTextureHeight,
+
+			morphTargets: material.morphTargets,
+			morphNormals: material.morphNormals,
+			maxMorphTargets: this.maxMorphTargets,
+			maxMorphNormals: this.maxMorphNormals,
+
+			maxDirLights: maxLightCount.directional,
+			maxPointLights: maxLightCount.point,
+			maxSpotLights: maxLightCount.spot,
+			maxHemiLights: maxLightCount.hemi,
+
+			maxShadows: maxShadows,
+			shadowMapEnabled: this.shadowMapEnabled && object.receiveShadow,
+			shadowMapType: this.shadowMapType,
+			shadowMapDebug: this.shadowMapDebug,
+			shadowMapCascade: this.shadowMapCascade,
+
+			alphaTest: material.alphaTest,
+			metal: material.metal,
+			perPixel: material.perPixel,
+			wrapAround: material.wrapAround,
+			doubleSided: material.side === THREE.DoubleSide,
+			flipSided: material.side === THREE.BackSide
+
+		};
+
+		material.program = buildProgram( shaderID, material.fragmentShader, material.vertexShader, material.uniforms, material.attributes, material.defines, parameters );
+
+		var attributes = material.program.attributes;
+
+		if ( material.morphTargets ) {
+
+			material.numSupportedMorphTargets = 0;
+
+			var id, base = "morphTarget";
+
+			for ( i = 0; i < this.maxMorphTargets; i ++ ) {
+
+				id = base + i;
+
+				if ( attributes[ id ] >= 0 ) {
+
+					material.numSupportedMorphTargets ++;
+
+				}
+
+			}
+
+		}
+
+		if ( material.morphNormals ) {
+
+			material.numSupportedMorphNormals = 0;
+
+			var id, base = "morphNormal";
+
+			for ( i = 0; i < this.maxMorphNormals; i ++ ) {
+
+				id = base + i;
+
+				if ( attributes[ id ] >= 0 ) {
+
+					material.numSupportedMorphNormals ++;
+
+				}
+
+			}
+
+		}
+
+		material.uniformsList = [];
+
+		for ( u in material.uniforms ) {
+
+			material.uniformsList.push( [ material.uniforms[ u ], u ] );
+
+		}
+
+	};
+
+	function setMaterialShaders( material, shaders ) {
+
+		material.uniforms = THREE.UniformsUtils.clone( shaders.uniforms );
+		material.vertexShader = shaders.vertexShader;
+		material.fragmentShader = shaders.fragmentShader;
+
+	};
+
+	function setProgram( camera, lights, fog, material, object ) {
+
+		_usedTextureUnits = 0;
+
+		if ( material.needsUpdate ) {
+
+			if ( material.program ) deallocateMaterial( material );
+
+			_this.initMaterial( material, lights, fog, object );
+			material.needsUpdate = false;
+
+		}
+
+		if ( material.morphTargets ) {
+
+			if ( ! object.__webglMorphTargetInfluences ) {
+
+				object.__webglMorphTargetInfluences = new Float32Array( _this.maxMorphTargets );
+
+			}
+
+		}
+
+		var refreshMaterial = false;
+
+		var program = material.program,
+			p_uniforms = program.uniforms,
+			m_uniforms = material.uniforms;
+
+		if ( program !== _currentProgram ) {
+
+			_gl.useProgram( program );
+			_currentProgram = program;
+
+			refreshMaterial = true;
+
+		}
+
+		if ( material.id !== _currentMaterialId ) {
+
+			_currentMaterialId = material.id;
+			refreshMaterial = true;
+
+		}
+
+		if ( refreshMaterial || camera !== _currentCamera ) {
+
+			_gl.uniformMatrix4fv( p_uniforms.projectionMatrix, false, camera.projectionMatrix.elements );
+
+			if ( camera !== _currentCamera ) _currentCamera = camera;
+
+		}
+
+		// skinning uniforms must be set even if material didn't change
+		// auto-setting of texture unit for bone texture must go before other textures
+		// not sure why, but otherwise weird things happen
+
+		if ( material.skinning ) {
+
+			if ( _supportsBoneTextures && object.useVertexTexture ) {
+
+				if ( p_uniforms.boneTexture !== null ) {
+
+					var textureUnit = getTextureUnit();
+
+					_gl.uniform1i( p_uniforms.boneTexture, textureUnit );
+					_this.setTexture( object.boneTexture, textureUnit );
+
+				}
+
+			} else {
+
+				if ( p_uniforms.boneGlobalMatrices !== null ) {
+
+					_gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.boneMatrices );
+
+				}
+
+			}
+
+		}
+
+		if ( refreshMaterial ) {
+
+			// refresh uniforms common to several materials
+
+			if ( fog && material.fog ) {
+
+				refreshUniformsFog( m_uniforms, fog );
+
+			}
+
+			if ( material instanceof THREE.MeshPhongMaterial ||
+				 material instanceof THREE.MeshLambertMaterial ||
+				 material.lights ) {
+
+				if ( _lightsNeedUpdate ) {
+
+					setupLights( program, lights );
+					_lightsNeedUpdate = false;
+
+				}
+
+				refreshUniformsLights( m_uniforms, _lights );
+
+			}
+
+			if ( material instanceof THREE.MeshBasicMaterial ||
+				 material instanceof THREE.MeshLambertMaterial ||
+				 material instanceof THREE.MeshPhongMaterial ) {
+
+				refreshUniformsCommon( m_uniforms, material );
+
+			}
+
+			// refresh single material specific uniforms
+
+			if ( material instanceof THREE.LineBasicMaterial ) {
+
+				refreshUniformsLine( m_uniforms, material );
+
+			} else if ( material instanceof THREE.LineDashedMaterial ) {
+
+				refreshUniformsLine( m_uniforms, material );
+				refreshUniformsDash( m_uniforms, material );
+
+			} else if ( material instanceof THREE.ParticleBasicMaterial ) {
+
+				refreshUniformsParticle( m_uniforms, material );
+
+			} else if ( material instanceof THREE.MeshPhongMaterial ) {
+
+				refreshUniformsPhong( m_uniforms, material );
+
+			} else if ( material instanceof THREE.MeshLambertMaterial ) {
+
+				refreshUniformsLambert( m_uniforms, material );
+
+			} else if ( material instanceof THREE.MeshDepthMaterial ) {
+
+				m_uniforms.mNear.value = camera.near;
+				m_uniforms.mFar.value = camera.far;
+				m_uniforms.opacity.value = material.opacity;
+
+			} else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+				m_uniforms.opacity.value = material.opacity;
+
+			}
+
+			if ( object.receiveShadow && ! material._shadowPass ) {
+
+				refreshUniformsShadow( m_uniforms, lights );
+
+			}
+
+			// load common uniforms
+
+			loadUniformsGeneric( program, material.uniformsList );
+
+			// load material specific uniforms
+			// (shader material also gets them for the sake of genericity)
+
+			if ( material instanceof THREE.ShaderMaterial ||
+				 material instanceof THREE.MeshPhongMaterial ||
+				 material.envMap ) {
+
+				if ( p_uniforms.cameraPosition !== null ) {
+
+					_vector3.getPositionFromMatrix( camera.matrixWorld );
+					_gl.uniform3f( p_uniforms.cameraPosition, _vector3.x, _vector3.y, _vector3.z );
+
+				}
+
+			}
+
+			if ( material instanceof THREE.MeshPhongMaterial ||
+				 material instanceof THREE.MeshLambertMaterial ||
+				 material instanceof THREE.ShaderMaterial ||
+				 material.skinning ) {
+
+				if ( p_uniforms.viewMatrix !== null ) {
+
+					_gl.uniformMatrix4fv( p_uniforms.viewMatrix, false, camera.matrixWorldInverse.elements );
+
+				}
+
+			}
+
+		}
+
+		loadUniformsMatrices( p_uniforms, object );
+
+		if ( p_uniforms.modelMatrix !== null ) {
+
+			_gl.uniformMatrix4fv( p_uniforms.modelMatrix, false, object.matrixWorld.elements );
+
+		}
+
+		return program;
+
+	};
+
+	// Uniforms (refresh uniforms objects)
+
+	function refreshUniformsCommon ( uniforms, material ) {
+
+		uniforms.opacity.value = material.opacity;
+
+		if ( _this.gammaInput ) {
+
+			uniforms.diffuse.value.copyGammaToLinear( material.color );
+
+		} else {
+
+			uniforms.diffuse.value = material.color;
+
+		}
+
+		uniforms.map.value = material.map;
+		uniforms.lightMap.value = material.lightMap;
+		uniforms.specularMap.value = material.specularMap;
+
+		if ( material.bumpMap ) {
+
+			uniforms.bumpMap.value = material.bumpMap;
+			uniforms.bumpScale.value = material.bumpScale;
+
+		}
+
+		if ( material.normalMap ) {
+
+			uniforms.normalMap.value = material.normalMap;
+			uniforms.normalScale.value.copy( material.normalScale );
+
+		}
+
+		// uv repeat and offset setting priorities
+		//	1. color map
+		//	2. specular map
+		//	3. normal map
+		//	4. bump map
+
+		var uvScaleMap;
+
+		if ( material.map ) {
+
+			uvScaleMap = material.map;
+
+		} else if ( material.specularMap ) {
+
+			uvScaleMap = material.specularMap;
+
+		} else if ( material.normalMap ) {
+
+			uvScaleMap = material.normalMap;
+
+		} else if ( material.bumpMap ) {
+
+			uvScaleMap = material.bumpMap;
+
+		}
+
+		if ( uvScaleMap !== undefined ) {
+
+			var offset = uvScaleMap.offset;
+			var repeat = uvScaleMap.repeat;
+
+			uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y );
+
+		}
+
+		uniforms.envMap.value = material.envMap;
+		uniforms.flipEnvMap.value = ( material.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : -1;
+
+		if ( _this.gammaInput ) {
+
+			//uniforms.reflectivity.value = material.reflectivity * material.reflectivity;
+			uniforms.reflectivity.value = material.reflectivity;
+
+		} else {
+
+			uniforms.reflectivity.value = material.reflectivity;
+
+		}
+
+		uniforms.refractionRatio.value = material.refractionRatio;
+		uniforms.combine.value = material.combine;
+		uniforms.useRefract.value = material.envMap && material.envMap.mapping instanceof THREE.CubeRefractionMapping;
+
+	};
+
+	function refreshUniformsLine ( uniforms, material ) {
+
+		uniforms.diffuse.value = material.color;
+		uniforms.opacity.value = material.opacity;
+
+	};
+
+	function refreshUniformsDash ( uniforms, material ) {
+
+		uniforms.dashSize.value = material.dashSize;
+		uniforms.totalSize.value = material.dashSize + material.gapSize;
+		uniforms.scale.value = material.scale;
+
+	};
+
+	function refreshUniformsParticle ( uniforms, material ) {
+
+		uniforms.psColor.value = material.color;
+		uniforms.opacity.value = material.opacity;
+		uniforms.size.value = material.size;
+		uniforms.scale.value = _canvas.height / 2.0; // TODO: Cache this.
+
+		uniforms.map.value = material.map;
+
+	};
+
+	function refreshUniformsFog ( uniforms, fog ) {
+
+		uniforms.fogColor.value = fog.color;
+
+		if ( fog instanceof THREE.Fog ) {
+
+			uniforms.fogNear.value = fog.near;
+			uniforms.fogFar.value = fog.far;
+
+		} else if ( fog instanceof THREE.FogExp2 ) {
+
+			uniforms.fogDensity.value = fog.density;
+
+		}
+
+	};
+
+	function refreshUniformsPhong ( uniforms, material ) {
+
+		uniforms.shininess.value = material.shininess;
+
+		if ( _this.gammaInput ) {
+
+			uniforms.ambient.value.copyGammaToLinear( material.ambient );
+			uniforms.emissive.value.copyGammaToLinear( material.emissive );
+			uniforms.specular.value.copyGammaToLinear( material.specular );
+
+		} else {
+
+			uniforms.ambient.value = material.ambient;
+			uniforms.emissive.value = material.emissive;
+			uniforms.specular.value = material.specular;
+
+		}
+
+		if ( material.wrapAround ) {
+
+			uniforms.wrapRGB.value.copy( material.wrapRGB );
+
+		}
+
+	};
+
+	function refreshUniformsLambert ( uniforms, material ) {
+
+		if ( _this.gammaInput ) {
+
+			uniforms.ambient.value.copyGammaToLinear( material.ambient );
+			uniforms.emissive.value.copyGammaToLinear( material.emissive );
+
+		} else {
+
+			uniforms.ambient.value = material.ambient;
+			uniforms.emissive.value = material.emissive;
+
+		}
+
+		if ( material.wrapAround ) {
+
+			uniforms.wrapRGB.value.copy( material.wrapRGB );
+
+		}
+
+	};
+
+	function refreshUniformsLights ( uniforms, lights ) {
+
+		uniforms.ambientLightColor.value = lights.ambient;
+
+		uniforms.directionalLightColor.value = lights.directional.colors;
+		uniforms.directionalLightDirection.value = lights.directional.positions;
+
+		uniforms.pointLightColor.value = lights.point.colors;
+		uniforms.pointLightPosition.value = lights.point.positions;
+		uniforms.pointLightDistance.value = lights.point.distances;
+
+		uniforms.spotLightColor.value = lights.spot.colors;
+		uniforms.spotLightPosition.value = lights.spot.positions;
+		uniforms.spotLightDistance.value = lights.spot.distances;
+		uniforms.spotLightDirection.value = lights.spot.directions;
+		uniforms.spotLightAngleCos.value = lights.spot.anglesCos;
+		uniforms.spotLightExponent.value = lights.spot.exponents;
+
+		uniforms.hemisphereLightSkyColor.value = lights.hemi.skyColors;
+		uniforms.hemisphereLightGroundColor.value = lights.hemi.groundColors;
+		uniforms.hemisphereLightDirection.value = lights.hemi.positions;
+
+	};
+
+	function refreshUniformsShadow ( uniforms, lights ) {
+
+		if ( uniforms.shadowMatrix ) {
+
+			var j = 0;
+
+			for ( var i = 0, il = lights.length; i < il; i ++ ) {
+
+				var light = lights[ i ];
+
+				if ( ! light.castShadow ) continue;
+
+				if ( light instanceof THREE.SpotLight || ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) ) {
+
+					uniforms.shadowMap.value[ j ] = light.shadowMap;
+					uniforms.shadowMapSize.value[ j ] = light.shadowMapSize;
+
+					uniforms.shadowMatrix.value[ j ] = light.shadowMatrix;
+
+					uniforms.shadowDarkness.value[ j ] = light.shadowDarkness;
+					uniforms.shadowBias.value[ j ] = light.shadowBias;
+
+					j ++;
+
+				}
+
+			}
+
+		}
+
+	};
+
+	// Uniforms (load to GPU)
+
+	function loadUniformsMatrices ( uniforms, object ) {
+
+		_gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, object._modelViewMatrix.elements );
+
+		if ( uniforms.normalMatrix ) {
+
+			_gl.uniformMatrix3fv( uniforms.normalMatrix, false, object._normalMatrix.elements );
+
+		}
+
+	};
+
+	function getTextureUnit() {
+
+		var textureUnit = _usedTextureUnits;
+
+		if ( textureUnit >= _maxTextures ) {
+
+			console.warn( "WebGLRenderer: trying to use " + textureUnit + " texture units while this GPU supports only " + _maxTextures );
+
+		}
+
+		_usedTextureUnits += 1;
+
+		return textureUnit;
+
+	};
+
+	function loadUniformsGeneric ( program, uniforms ) {
+
+		var uniform, value, type, location, texture, textureUnit, i, il, j, jl, offset;
+
+		for ( j = 0, jl = uniforms.length; j < jl; j ++ ) {
+
+			location = program.uniforms[ uniforms[ j ][ 1 ] ];
+			if ( !location ) continue;
+
+			uniform = uniforms[ j ][ 0 ];
+
+			type = uniform.type;
+			value = uniform.value;
+
+			if ( type === "i" ) { // single integer
+
+				_gl.uniform1i( location, value );
+
+			} else if ( type === "f" ) { // single float
+
+				_gl.uniform1f( location, value );
+
+			} else if ( type === "v2" ) { // single THREE.Vector2
+
+				_gl.uniform2f( location, value.x, value.y );
+
+			} else if ( type === "v3" ) { // single THREE.Vector3
+
+				_gl.uniform3f( location, value.x, value.y, value.z );
+
+			} else if ( type === "v4" ) { // single THREE.Vector4
+
+				_gl.uniform4f( location, value.x, value.y, value.z, value.w );
+
+			} else if ( type === "c" ) { // single THREE.Color
+
+				_gl.uniform3f( location, value.r, value.g, value.b );
+
+			} else if ( type === "iv1" ) { // flat array of integers (JS or typed array)
+
+				_gl.uniform1iv( location, value );
+
+			} else if ( type === "iv" ) { // flat array of integers with 3 x N size (JS or typed array)
+
+				_gl.uniform3iv( location, value );
+
+			} else if ( type === "fv1" ) { // flat array of floats (JS or typed array)
+
+				_gl.uniform1fv( location, value );
+
+			} else if ( type === "fv" ) { // flat array of floats with 3 x N size (JS or typed array)
+
+				_gl.uniform3fv( location, value );
+
+			} else if ( type === "v2v" ) { // array of THREE.Vector2
+
+				if ( uniform._array === undefined ) {
+
+					uniform._array = new Float32Array( 2 * value.length );
+
+				}
+
+				for ( i = 0, il = value.length; i < il; i ++ ) {
+
+					offset = i * 2;
+
+					uniform._array[ offset ] 	 = value[ i ].x;
+					uniform._array[ offset + 1 ] = value[ i ].y;
+
+				}
+
+				_gl.uniform2fv( location, uniform._array );
+
+			} else if ( type === "v3v" ) { // array of THREE.Vector3
+
+				if ( uniform._array === undefined ) {
+
+					uniform._array = new Float32Array( 3 * value.length );
+
+				}
+
+				for ( i = 0, il = value.length; i < il; i ++ ) {
+
+					offset = i * 3;
+
+					uniform._array[ offset ] 	 = value[ i ].x;
+					uniform._array[ offset + 1 ] = value[ i ].y;
+					uniform._array[ offset + 2 ] = value[ i ].z;
+
+				}
+
+				_gl.uniform3fv( location, uniform._array );
+
+			} else if ( type === "v4v" ) { // array of THREE.Vector4
+
+				if ( uniform._array === undefined ) {
+
+					uniform._array = new Float32Array( 4 * value.length );
+
+				}
+
+				for ( i = 0, il = value.length; i < il; i ++ ) {
+
+					offset = i * 4;
+
+					uniform._array[ offset ] 	 = value[ i ].x;
+					uniform._array[ offset + 1 ] = value[ i ].y;
+					uniform._array[ offset + 2 ] = value[ i ].z;
+					uniform._array[ offset + 3 ] = value[ i ].w;
+
+				}
+
+				_gl.uniform4fv( location, uniform._array );
+
+			} else if ( type === "m4") { // single THREE.Matrix4
+
+				if ( uniform._array === undefined ) {
+
+					uniform._array = new Float32Array( 16 );
+
+				}
+
+				value.flattenToArray( uniform._array );
+				_gl.uniformMatrix4fv( location, false, uniform._array );
+
+			} else if ( type === "m4v" ) { // array of THREE.Matrix4
+
+				if ( uniform._array === undefined ) {
+
+					uniform._array = new Float32Array( 16 * value.length );
+
+				}
+
+				for ( i = 0, il = value.length; i < il; i ++ ) {
+
+					value[ i ].flattenToArrayOffset( uniform._array, i * 16 );
+
+				}
+
+				_gl.uniformMatrix4fv( location, false, uniform._array );
+
+			} else if ( type === "t" ) { // single THREE.Texture (2d or cube)
+
+				texture = value;
+				textureUnit = getTextureUnit();
+
+				_gl.uniform1i( location, textureUnit );
+
+				if ( !texture ) continue;
+
+				if ( texture.image instanceof Array && texture.image.length === 6 ) {
+
+					setCubeTexture( texture, textureUnit );
+
+				} else if ( texture instanceof THREE.WebGLRenderTargetCube ) {
+
+					setCubeTextureDynamic( texture, textureUnit );
+
+				} else {
+
+					_this.setTexture( texture, textureUnit );
+
+				}
+
+			} else if ( type === "tv" ) { // array of THREE.Texture (2d)
+
+				if ( uniform._array === undefined ) {
+
+					uniform._array = [];
+
+				}
+
+				for( i = 0, il = uniform.value.length; i < il; i ++ ) {
+
+					uniform._array[ i ] = getTextureUnit();
+
+				}
+
+				_gl.uniform1iv( location, uniform._array );
+
+				for( i = 0, il = uniform.value.length; i < il; i ++ ) {
+
+					texture = uniform.value[ i ];
+					textureUnit = uniform._array[ i ];
+
+					if ( !texture ) continue;
+
+					_this.setTexture( texture, textureUnit );
+
+				}
+
+			}
+
+		}
+
+	};
+
+	function setupMatrices ( object, camera ) {
+
+		object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+		object._normalMatrix.getNormalMatrix( object._modelViewMatrix );
+
+	};
+
+	//
+
+	function setColorGamma( array, offset, color, intensitySq ) {
+
+		array[ offset ]     = color.r * color.r * intensitySq;
+		array[ offset + 1 ] = color.g * color.g * intensitySq;
+		array[ offset + 2 ] = color.b * color.b * intensitySq;
+
+	};
+
+	function setColorLinear( array, offset, color, intensity ) {
+
+		array[ offset ]     = color.r * intensity;
+		array[ offset + 1 ] = color.g * intensity;
+		array[ offset + 2 ] = color.b * intensity;
+
+	};
+
+	function setupLights ( program, lights ) {
+
+		var l, ll, light, n,
+		r = 0, g = 0, b = 0,
+		color, skyColor, groundColor,
+		intensity,  intensitySq,
+		position,
+		distance,
+
+		zlights = _lights,
+
+		dirColors = zlights.directional.colors,
+		dirPositions = zlights.directional.positions,
+
+		pointColors = zlights.point.colors,
+		pointPositions = zlights.point.positions,
+		pointDistances = zlights.point.distances,
+
+		spotColors = zlights.spot.colors,
+		spotPositions = zlights.spot.positions,
+		spotDistances = zlights.spot.distances,
+		spotDirections = zlights.spot.directions,
+		spotAnglesCos = zlights.spot.anglesCos,
+		spotExponents = zlights.spot.exponents,
+
+		hemiSkyColors = zlights.hemi.skyColors,
+		hemiGroundColors = zlights.hemi.groundColors,
+		hemiPositions = zlights.hemi.positions,
+
+		dirLength = 0,
+		pointLength = 0,
+		spotLength = 0,
+		hemiLength = 0,
+
+		dirCount = 0,
+		pointCount = 0,
+		spotCount = 0,
+		hemiCount = 0,
+
+		dirOffset = 0,
+		pointOffset = 0,
+		spotOffset = 0,
+		hemiOffset = 0;
+
+		for ( l = 0, ll = lights.length; l < ll; l ++ ) {
+
+			light = lights[ l ];
+
+			if ( light.onlyShadow ) continue;
+
+			color = light.color;
+			intensity = light.intensity;
+			distance = light.distance;
+
+			if ( light instanceof THREE.AmbientLight ) {
+
+				if ( ! light.visible ) continue;
+
+				if ( _this.gammaInput ) {
+
+					r += color.r * color.r;
+					g += color.g * color.g;
+					b += color.b * color.b;
+
+				} else {
+
+					r += color.r;
+					g += color.g;
+					b += color.b;
+
+				}
+
+			} else if ( light instanceof THREE.DirectionalLight ) {
+
+				dirCount += 1;
+
+				if ( ! light.visible ) continue;
+
+				_direction.getPositionFromMatrix( light.matrixWorld );
+				_vector3.getPositionFromMatrix( light.target.matrixWorld );
+				_direction.sub( _vector3 );
+				_direction.normalize();
+
+				// skip lights with undefined direction
+				// these create troubles in OpenGL (making pixel black)
+
+				if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue;
+
+				dirOffset = dirLength * 3;
+
+				dirPositions[ dirOffset ]     = _direction.x;
+				dirPositions[ dirOffset + 1 ] = _direction.y;
+				dirPositions[ dirOffset + 2 ] = _direction.z;
+
+				if ( _this.gammaInput ) {
+
+					setColorGamma( dirColors, dirOffset, color, intensity * intensity );
+
+				} else {
+
+					setColorLinear( dirColors, dirOffset, color, intensity );
+
+				}
+
+				dirLength += 1;
+
+			} else if ( light instanceof THREE.PointLight ) {
+
+				pointCount += 1;
+
+				if ( ! light.visible ) continue;
+
+				pointOffset = pointLength * 3;
+
+				if ( _this.gammaInput ) {
+
+					setColorGamma( pointColors, pointOffset, color, intensity * intensity );
+
+				} else {
+
+					setColorLinear( pointColors, pointOffset, color, intensity );
+
+				}
+
+				_vector3.getPositionFromMatrix( light.matrixWorld );
+
+				pointPositions[ pointOffset ]     = _vector3.x;
+				pointPositions[ pointOffset + 1 ] = _vector3.y;
+				pointPositions[ pointOffset + 2 ] = _vector3.z;
+
+				pointDistances[ pointLength ] = distance;
+
+				pointLength += 1;
+
+			} else if ( light instanceof THREE.SpotLight ) {
+
+				spotCount += 1;
+
+				if ( ! light.visible ) continue;
+
+				spotOffset = spotLength * 3;
+
+				if ( _this.gammaInput ) {
+
+					setColorGamma( spotColors, spotOffset, color, intensity * intensity );
+
+				} else {
+
+					setColorLinear( spotColors, spotOffset, color, intensity );
+
+				}
+
+				_vector3.getPositionFromMatrix( light.matrixWorld );
+
+				spotPositions[ spotOffset ]     = _vector3.x;
+				spotPositions[ spotOffset + 1 ] = _vector3.y;
+				spotPositions[ spotOffset + 2 ] = _vector3.z;
+
+				spotDistances[ spotLength ] = distance;
+
+				_direction.copy( _vector3 );
+				_vector3.getPositionFromMatrix( light.target.matrixWorld );
+				_direction.sub( _vector3 );
+				_direction.normalize();
+
+				spotDirections[ spotOffset ]     = _direction.x;
+				spotDirections[ spotOffset + 1 ] = _direction.y;
+				spotDirections[ spotOffset + 2 ] = _direction.z;
+
+				spotAnglesCos[ spotLength ] = Math.cos( light.angle );
+				spotExponents[ spotLength ] = light.exponent;
+
+				spotLength += 1;
+
+			} else if ( light instanceof THREE.HemisphereLight ) {
+
+				hemiCount += 1;
+
+				if ( ! light.visible ) continue;
+
+				_direction.getPositionFromMatrix( light.matrixWorld );
+				_direction.normalize();
+
+				// skip lights with undefined direction
+				// these create troubles in OpenGL (making pixel black)
+
+				if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue;
+
+				hemiOffset = hemiLength * 3;
+
+				hemiPositions[ hemiOffset ]     = _direction.x;
+				hemiPositions[ hemiOffset + 1 ] = _direction.y;
+				hemiPositions[ hemiOffset + 2 ] = _direction.z;
+
+				skyColor = light.color;
+				groundColor = light.groundColor;
+
+				if ( _this.gammaInput ) {
+
+					intensitySq = intensity * intensity;
+
+					setColorGamma( hemiSkyColors, hemiOffset, skyColor, intensitySq );
+					setColorGamma( hemiGroundColors, hemiOffset, groundColor, intensitySq );
+
+				} else {
+
+					setColorLinear( hemiSkyColors, hemiOffset, skyColor, intensity );
+					setColorLinear( hemiGroundColors, hemiOffset, groundColor, intensity );
+
+				}
+
+				hemiLength += 1;
+
+			}
+
+		}
+
+		// null eventual remains from removed lights
+		// (this is to avoid if in shader)
+
+		for ( l = dirLength * 3, ll = Math.max( dirColors.length, dirCount * 3 ); l < ll; l ++ ) dirColors[ l ] = 0.0;
+		for ( l = pointLength * 3, ll = Math.max( pointColors.length, pointCount * 3 ); l < ll; l ++ ) pointColors[ l ] = 0.0;
+		for ( l = spotLength * 3, ll = Math.max( spotColors.length, spotCount * 3 ); l < ll; l ++ ) spotColors[ l ] = 0.0;
+		for ( l = hemiLength * 3, ll = Math.max( hemiSkyColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiSkyColors[ l ] = 0.0;
+		for ( l = hemiLength * 3, ll = Math.max( hemiGroundColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiGroundColors[ l ] = 0.0;
+
+		zlights.directional.length = dirLength;
+		zlights.point.length = pointLength;
+		zlights.spot.length = spotLength;
+		zlights.hemi.length = hemiLength;
+
+		zlights.ambient[ 0 ] = r;
+		zlights.ambient[ 1 ] = g;
+		zlights.ambient[ 2 ] = b;
+
+	};
+
+	// GL state setting
+
+	this.setFaceCulling = function ( cullFace, frontFaceDirection ) {
+
+		if ( cullFace === THREE.CullFaceNone ) {
+
+			_gl.disable( _gl.CULL_FACE );
+
+		} else {
+
+			if ( frontFaceDirection === THREE.FrontFaceDirectionCW ) {
+
+				_gl.frontFace( _gl.CW );
+
+			} else {
+
+				_gl.frontFace( _gl.CCW );
+
+			}
+
+			if ( cullFace === THREE.CullFaceBack ) {
+
+				_gl.cullFace( _gl.BACK );
+
+			} else if ( cullFace === THREE.CullFaceFront ) {
+
+				_gl.cullFace( _gl.FRONT );
+
+			} else {
+
+				_gl.cullFace( _gl.FRONT_AND_BACK );
+
+			}
+
+			_gl.enable( _gl.CULL_FACE );
+
+		}
+
+	};
+
+	this.setMaterialFaces = function ( material ) {
+
+		var doubleSided = material.side === THREE.DoubleSide;
+		var flipSided = material.side === THREE.BackSide;
+
+		if ( _oldDoubleSided !== doubleSided ) {
+
+			if ( doubleSided ) {
+
+				_gl.disable( _gl.CULL_FACE );
+
+			} else {
+
+				_gl.enable( _gl.CULL_FACE );
+
+			}
+
+			_oldDoubleSided = doubleSided;
+
+		}
+
+		if ( _oldFlipSided !== flipSided ) {
+
+			if ( flipSided ) {
+
+				_gl.frontFace( _gl.CW );
+
+			} else {
+
+				_gl.frontFace( _gl.CCW );
+
+			}
+
+			_oldFlipSided = flipSided;
+
+		}
+
+	};
+
+	this.setDepthTest = function ( depthTest ) {
+
+		if ( _oldDepthTest !== depthTest ) {
+
+			if ( depthTest ) {
+
+				_gl.enable( _gl.DEPTH_TEST );
+
+			} else {
+
+				_gl.disable( _gl.DEPTH_TEST );
+
+			}
+
+			_oldDepthTest = depthTest;
+
+		}
+
+	};
+
+	this.setDepthWrite = function ( depthWrite ) {
+
+		if ( _oldDepthWrite !== depthWrite ) {
+
+			_gl.depthMask( depthWrite );
+			_oldDepthWrite = depthWrite;
+
+		}
+
+	};
+
+	function setLineWidth ( width ) {
+
+		if ( width !== _oldLineWidth ) {
+
+			_gl.lineWidth( width );
+
+			_oldLineWidth = width;
+
+		}
+
+	};
+
+	function setPolygonOffset ( polygonoffset, factor, units ) {
+
+		if ( _oldPolygonOffset !== polygonoffset ) {
+
+			if ( polygonoffset ) {
+
+				_gl.enable( _gl.POLYGON_OFFSET_FILL );
+
+			} else {
+
+				_gl.disable( _gl.POLYGON_OFFSET_FILL );
+
+			}
+
+			_oldPolygonOffset = polygonoffset;
+
+		}
+
+		if ( polygonoffset && ( _oldPolygonOffsetFactor !== factor || _oldPolygonOffsetUnits !== units ) ) {
+
+			_gl.polygonOffset( factor, units );
+
+			_oldPolygonOffsetFactor = factor;
+			_oldPolygonOffsetUnits = units;
+
+		}
+
+	};
+
+	this.setBlending = function ( blending, blendEquation, blendSrc, blendDst ) {
+
+		if ( blending !== _oldBlending ) {
+
+			if ( blending === THREE.NoBlending ) {
+
+				_gl.disable( _gl.BLEND );
+
+			} else if ( blending === THREE.AdditiveBlending ) {
+
+				_gl.enable( _gl.BLEND );
+				_gl.blendEquation( _gl.FUNC_ADD );
+				_gl.blendFunc( _gl.SRC_ALPHA, _gl.ONE );
+
+			} else if ( blending === THREE.SubtractiveBlending ) {
+
+				// TODO: Find blendFuncSeparate() combination
+				_gl.enable( _gl.BLEND );
+				_gl.blendEquation( _gl.FUNC_ADD );
+				_gl.blendFunc( _gl.ZERO, _gl.ONE_MINUS_SRC_COLOR );
+
+			} else if ( blending === THREE.MultiplyBlending ) {
+
+				// TODO: Find blendFuncSeparate() combination
+				_gl.enable( _gl.BLEND );
+				_gl.blendEquation( _gl.FUNC_ADD );
+				_gl.blendFunc( _gl.ZERO, _gl.SRC_COLOR );
+
+			} else if ( blending === THREE.CustomBlending ) {
+
+				_gl.enable( _gl.BLEND );
+
+			} else {
+
+				_gl.enable( _gl.BLEND );
+				_gl.blendEquationSeparate( _gl.FUNC_ADD, _gl.FUNC_ADD );
+				_gl.blendFuncSeparate( _gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA );
+
+			}
+
+			_oldBlending = blending;
+
+		}
+
+		if ( blending === THREE.CustomBlending ) {
+
+			if ( blendEquation !== _oldBlendEquation ) {
+
+				_gl.blendEquation( paramThreeToGL( blendEquation ) );
+
+				_oldBlendEquation = blendEquation;
+
+			}
+
+			if ( blendSrc !== _oldBlendSrc || blendDst !== _oldBlendDst ) {
+
+				_gl.blendFunc( paramThreeToGL( blendSrc ), paramThreeToGL( blendDst ) );
+
+				_oldBlendSrc = blendSrc;
+				_oldBlendDst = blendDst;
+
+			}
+
+		} else {
+
+			_oldBlendEquation = null;
+			_oldBlendSrc = null;
+			_oldBlendDst = null;
+
+		}
+
+	};
+
+	// Defines
+
+	function generateDefines ( defines ) {
+
+		var value, chunk, chunks = [];
+
+		for ( var d in defines ) {
+
+			value = defines[ d ];
+			if ( value === false ) continue;
+
+			chunk = "#define " + d + " " + value;
+			chunks.push( chunk );
+
+		}
+
+		return chunks.join( "\n" );
+
+	};
+
+	// Shaders
+
+	function buildProgram ( shaderID, fragmentShader, vertexShader, uniforms, attributes, defines, parameters ) {
+
+		var p, pl, d, program, code;
+		var chunks = [];
+
+		// Generate code
+
+		if ( shaderID ) {
+
+			chunks.push( shaderID );
+
+		} else {
+
+			chunks.push( fragmentShader );
+			chunks.push( vertexShader );
+
+		}
+
+		for ( d in defines ) {
+
+			chunks.push( d );
+			chunks.push( defines[ d ] );
+
+		}
+
+		for ( p in parameters ) {
+
+			chunks.push( p );
+			chunks.push( parameters[ p ] );
+
+		}
+
+		code = chunks.join();
+
+		// Check if code has been already compiled
+
+		for ( p = 0, pl = _programs.length; p < pl; p ++ ) {
+
+			var programInfo = _programs[ p ];
+
+			if ( programInfo.code === code ) {
+
+				// console.log( "Code already compiled." /*: \n\n" + code*/ );
+
+				programInfo.usedTimes ++;
+
+				return programInfo.program;
+
+			}
+
+		}
+
+		var shadowMapTypeDefine = "SHADOWMAP_TYPE_BASIC";
+
+		if ( parameters.shadowMapType === THREE.PCFShadowMap ) {
+
+			shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF";
+
+		} else if ( parameters.shadowMapType === THREE.PCFSoftShadowMap ) {
+
+			shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF_SOFT";
+
+		}
+
+		// console.log( "building new program " );
+
+		//
+
+		var customDefines = generateDefines( defines );
+
+		//
+
+		program = _gl.createProgram();
+
+		var prefix_vertex = [
+
+			"precision " + _precision + " float;",
+
+			customDefines,
+
+			_supportsVertexTextures ? "#define VERTEX_TEXTURES" : "",
+
+			_this.gammaInput ? "#define GAMMA_INPUT" : "",
+			_this.gammaOutput ? "#define GAMMA_OUTPUT" : "",
+			_this.physicallyBasedShading ? "#define PHYSICALLY_BASED_SHADING" : "",
+
+			"#define MAX_DIR_LIGHTS " + parameters.maxDirLights,
+			"#define MAX_POINT_LIGHTS " + parameters.maxPointLights,
+			"#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights,
+			"#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights,
+
+			"#define MAX_SHADOWS " + parameters.maxShadows,
+
+			"#define MAX_BONES " + parameters.maxBones,
+
+			parameters.map ? "#define USE_MAP" : "",
+			parameters.envMap ? "#define USE_ENVMAP" : "",
+			parameters.lightMap ? "#define USE_LIGHTMAP" : "",
+			parameters.bumpMap ? "#define USE_BUMPMAP" : "",
+			parameters.normalMap ? "#define USE_NORMALMAP" : "",
+			parameters.specularMap ? "#define USE_SPECULARMAP" : "",
+			parameters.vertexColors ? "#define USE_COLOR" : "",
+
+			parameters.skinning ? "#define USE_SKINNING" : "",
+			parameters.useVertexTexture ? "#define BONE_TEXTURE" : "",
+			parameters.boneTextureWidth ? "#define N_BONE_PIXEL_X " + parameters.boneTextureWidth.toFixed( 1 ) : "",
+			parameters.boneTextureHeight ? "#define N_BONE_PIXEL_Y " + parameters.boneTextureHeight.toFixed( 1 ) : "",
+
+			parameters.morphTargets ? "#define USE_MORPHTARGETS" : "",
+			parameters.morphNormals ? "#define USE_MORPHNORMALS" : "",
+			parameters.perPixel ? "#define PHONG_PER_PIXEL" : "",
+			parameters.wrapAround ? "#define WRAP_AROUND" : "",
+			parameters.doubleSided ? "#define DOUBLE_SIDED" : "",
+			parameters.flipSided ? "#define FLIP_SIDED" : "",
+
+			parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "",
+			parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "",
+			parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "",
+			parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "",
+
+			parameters.sizeAttenuation ? "#define USE_SIZEATTENUATION" : "",
+
+			"uniform mat4 modelMatrix;",
+			"uniform mat4 modelViewMatrix;",
+			"uniform mat4 projectionMatrix;",
+			"uniform mat4 viewMatrix;",
+			"uniform mat3 normalMatrix;",
+			"uniform vec3 cameraPosition;",
+
+			"attribute vec3 position;",
+			"attribute vec3 normal;",
+			"attribute vec2 uv;",
+			"attribute vec2 uv2;",
+
+			"#ifdef USE_COLOR",
+
+				"attribute vec3 color;",
+
+			"#endif",
+
+			"#ifdef USE_MORPHTARGETS",
+
+				"attribute vec3 morphTarget0;",
+				"attribute vec3 morphTarget1;",
+				"attribute vec3 morphTarget2;",
+				"attribute vec3 morphTarget3;",
+
+				"#ifdef USE_MORPHNORMALS",
+
+					"attribute vec3 morphNormal0;",
+					"attribute vec3 morphNormal1;",
+					"attribute vec3 morphNormal2;",
+					"attribute vec3 morphNormal3;",
+
+				"#else",
+
+					"attribute vec3 morphTarget4;",
+					"attribute vec3 morphTarget5;",
+					"attribute vec3 morphTarget6;",
+					"attribute vec3 morphTarget7;",
+
+				"#endif",
+
+			"#endif",
+
+			"#ifdef USE_SKINNING",
+
+				"attribute vec4 skinIndex;",
+				"attribute vec4 skinWeight;",
+
+			"#endif",
+
+			""
+
+		].join("\n");
+
+		var prefix_fragment = [
+
+			"precision " + _precision + " float;",
+
+			( parameters.bumpMap || parameters.normalMap ) ? "#extension GL_OES_standard_derivatives : enable" : "",
+
+			customDefines,
+
+			"#define MAX_DIR_LIGHTS " + parameters.maxDirLights,
+			"#define MAX_POINT_LIGHTS " + parameters.maxPointLights,
+			"#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights,
+			"#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights,
+
+			"#define MAX_SHADOWS " + parameters.maxShadows,
+
+			parameters.alphaTest ? "#define ALPHATEST " + parameters.alphaTest: "",
+
+			_this.gammaInput ? "#define GAMMA_INPUT" : "",
+			_this.gammaOutput ? "#define GAMMA_OUTPUT" : "",
+			_this.physicallyBasedShading ? "#define PHYSICALLY_BASED_SHADING" : "",
+
+			( parameters.useFog && parameters.fog ) ? "#define USE_FOG" : "",
+			( parameters.useFog && parameters.fogExp ) ? "#define FOG_EXP2" : "",
+
+			parameters.map ? "#define USE_MAP" : "",
+			parameters.envMap ? "#define USE_ENVMAP" : "",
+			parameters.lightMap ? "#define USE_LIGHTMAP" : "",
+			parameters.bumpMap ? "#define USE_BUMPMAP" : "",
+			parameters.normalMap ? "#define USE_NORMALMAP" : "",
+			parameters.specularMap ? "#define USE_SPECULARMAP" : "",
+			parameters.vertexColors ? "#define USE_COLOR" : "",
+
+			parameters.metal ? "#define METAL" : "",
+			parameters.perPixel ? "#define PHONG_PER_PIXEL" : "",
+			parameters.wrapAround ? "#define WRAP_AROUND" : "",
+			parameters.doubleSided ? "#define DOUBLE_SIDED" : "",
+			parameters.flipSided ? "#define FLIP_SIDED" : "",
+
+			parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "",
+			parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "",
+			parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "",
+			parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "",
+
+			"uniform mat4 viewMatrix;",
+			"uniform vec3 cameraPosition;",
+			""
+
+		].join("\n");
+
+		var glVertexShader = getShader( "vertex", prefix_vertex + vertexShader );
+		var glFragmentShader = getShader( "fragment", prefix_fragment + fragmentShader );
+
+		_gl.attachShader( program, glVertexShader );
+		_gl.attachShader( program, glFragmentShader );
+
+		_gl.linkProgram( program );
+
+		if ( !_gl.getProgramParameter( program, _gl.LINK_STATUS ) ) {
+
+			console.error( "Could not initialise shader\n" + "VALIDATE_STATUS: " + _gl.getProgramParameter( program, _gl.VALIDATE_STATUS ) + ", gl error [" + _gl.getError() + "]" );
+
+		}
+
+		// clean up
+
+		_gl.deleteShader( glFragmentShader );
+		_gl.deleteShader( glVertexShader );
+
+		// console.log( prefix_fragment + fragmentShader );
+		// console.log( prefix_vertex + vertexShader );
+
+		program.uniforms = {};
+		program.attributes = {};
+
+		var identifiers, u, a, i;
+
+		// cache uniform locations
+
+		identifiers = [
+
+			'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition',
+			'morphTargetInfluences'
+
+		];
+
+		if ( parameters.useVertexTexture ) {
+
+			identifiers.push( 'boneTexture' );
+
+		} else {
+
+			identifiers.push( 'boneGlobalMatrices' );
+
+		}
+
+		for ( u in uniforms ) {
+
+			identifiers.push( u );
+
+		}
+
+		cacheUniformLocations( program, identifiers );
+
+		// cache attributes locations
+
+		identifiers = [
+
+			"position", "normal", "uv", "uv2", "tangent", "color",
+			"skinIndex", "skinWeight", "lineDistance"
+
+		];
+
+		for ( i = 0; i < parameters.maxMorphTargets; i ++ ) {
+
+			identifiers.push( "morphTarget" + i );
+
+		}
+
+		for ( i = 0; i < parameters.maxMorphNormals; i ++ ) {
+
+			identifiers.push( "morphNormal" + i );
+
+		}
+
+		for ( a in attributes ) {
+
+			identifiers.push( a );
+
+		}
+
+		cacheAttributeLocations( program, identifiers );
+
+		program.id = _programs_counter ++;
+
+		_programs.push( { program: program, code: code, usedTimes: 1 } );
+
+		_this.info.memory.programs = _programs.length;
+
+		return program;
+
+	};
+
+	// Shader parameters cache
+
+	function cacheUniformLocations ( program, identifiers ) {
+
+		var i, l, id;
+
+		for( i = 0, l = identifiers.length; i < l; i ++ ) {
+
+			id = identifiers[ i ];
+			program.uniforms[ id ] = _gl.getUniformLocation( program, id );
+
+		}
+
+	};
+
+	function cacheAttributeLocations ( program, identifiers ) {
+
+		var i, l, id;
+
+		for( i = 0, l = identifiers.length; i < l; i ++ ) {
+
+			id = identifiers[ i ];
+			program.attributes[ id ] = _gl.getAttribLocation( program, id );
+
+		}
+
+	};
+
+	function addLineNumbers ( string ) {
+
+		var chunks = string.split( "\n" );
+
+		for ( var i = 0, il = chunks.length; i < il; i ++ ) {
+
+			// Chrome reports shader errors on lines
+			// starting counting from 1
+
+			chunks[ i ] = ( i + 1 ) + ": " + chunks[ i ];
+
+		}
+
+		return chunks.join( "\n" );
+
+	};
+
+	function getShader ( type, string ) {
+
+		var shader;
+
+		if ( type === "fragment" ) {
+
+			shader = _gl.createShader( _gl.FRAGMENT_SHADER );
+
+		} else if ( type === "vertex" ) {
+
+			shader = _gl.createShader( _gl.VERTEX_SHADER );
+
+		}
+
+		_gl.shaderSource( shader, string );
+		_gl.compileShader( shader );
+
+		if ( !_gl.getShaderParameter( shader, _gl.COMPILE_STATUS ) ) {
+
+			console.error( _gl.getShaderInfoLog( shader ) );
+			console.error( addLineNumbers( string ) );
+			return null;
+
+		}
+
+		return shader;
+
+	};
+
+	// Textures
+
+
+	function isPowerOfTwo ( value ) {
+
+		return ( value & ( value - 1 ) ) === 0;
+
+	};
+
+	function setTextureParameters ( textureType, texture, isImagePowerOfTwo ) {
+
+		if ( isImagePowerOfTwo ) {
+
+			_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, paramThreeToGL( texture.wrapS ) );
+			_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, paramThreeToGL( texture.wrapT ) );
+
+			_gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, paramThreeToGL( texture.magFilter ) );
+			_gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, paramThreeToGL( texture.minFilter ) );
+
+		} else {
+
+			_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE );
+			_gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE );
+
+			_gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) );
+			_gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) );
+
+		}
+
+		if ( _glExtensionTextureFilterAnisotropic && texture.type !== THREE.FloatType ) {
+
+			if ( texture.anisotropy > 1 || texture.__oldAnisotropy ) {
+
+				_gl.texParameterf( textureType, _glExtensionTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, _maxAnisotropy ) );
+				texture.__oldAnisotropy = texture.anisotropy;
+
+			}
+
+		}
+
+	};
+
+	this.setTexture = function ( texture, slot ) {
+
+		if ( texture.needsUpdate ) {
+
+			if ( ! texture.__webglInit ) {
+
+				texture.__webglInit = true;
+
+				texture.addEventListener( 'dispose', onTextureDispose );
+
+				texture.__webglTexture = _gl.createTexture();
+
+				_this.info.memory.textures ++;
+
+			}
+
+			_gl.activeTexture( _gl.TEXTURE0 + slot );
+			_gl.bindTexture( _gl.TEXTURE_2D, texture.__webglTexture );
+
+			_gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );
+			_gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha );
+			_gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment );
+
+			var image = texture.image,
+			isImagePowerOfTwo = isPowerOfTwo( image.width ) && isPowerOfTwo( image.height ),
+			glFormat = paramThreeToGL( texture.format ),
+			glType = paramThreeToGL( texture.type );
+
+			setTextureParameters( _gl.TEXTURE_2D, texture, isImagePowerOfTwo );
+
+			var mipmap, mipmaps = texture.mipmaps;
+
+			if ( texture instanceof THREE.DataTexture ) {
+
+				// use manually created mipmaps if available
+				// if there are no manual mipmaps
+				// set 0 level mipmap and then use GL to generate other mipmap levels
+
+				if ( mipmaps.length > 0 && isImagePowerOfTwo ) {
+
+					for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+						mipmap = mipmaps[ i ];
+						_gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+
+					}
+
+					texture.generateMipmaps = false;
+
+				} else {
+
+					_gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data );
+
+				}
+
+			} else if ( texture instanceof THREE.CompressedTexture ) {
+
+				// compressed textures can only use manually created mipmaps
+				// WebGL can't generate mipmaps for DDS textures
+
+				for( var i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+					mipmap = mipmaps[ i ];
+					_gl.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+
+				}
+
+			} else { // regular Texture (image, video, canvas)
+
+				// use manually created mipmaps if available
+				// if there are no manual mipmaps
+				// set 0 level mipmap and then use GL to generate other mipmap levels
+
+				if ( mipmaps.length > 0 && isImagePowerOfTwo ) {
+
+					for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+						mipmap = mipmaps[ i ];
+						_gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap );
+
+					}
+
+					texture.generateMipmaps = false;
+
+				} else {
+
+					_gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, texture.image );
+
+				}
+
+			}
+
+			if ( texture.generateMipmaps && isImagePowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D );
+
+			texture.needsUpdate = false;
+
+			if ( texture.onUpdate ) texture.onUpdate();
+
+		} else {
+
+			_gl.activeTexture( _gl.TEXTURE0 + slot );
+			_gl.bindTexture( _gl.TEXTURE_2D, texture.__webglTexture );
+
+		}
+
+	};
+
+	function clampToMaxSize ( image, maxSize ) {
+
+		if ( image.width <= maxSize && image.height <= maxSize ) {
+
+			return image;
+
+		}
+
+		// Warning: Scaling through the canvas will only work with images that use
+		// premultiplied alpha.
+
+		var maxDimension = Math.max( image.width, image.height );
+		var newWidth = Math.floor( image.width * maxSize / maxDimension );
+		var newHeight = Math.floor( image.height * maxSize / maxDimension );
+
+		var canvas = document.createElement( 'canvas' );
+		canvas.width = newWidth;
+		canvas.height = newHeight;
+
+		var ctx = canvas.getContext( "2d" );
+		ctx.drawImage( image, 0, 0, image.width, image.height, 0, 0, newWidth, newHeight );
+
+		return canvas;
+
+	}
+
+	function setCubeTexture ( texture, slot ) {
+
+		if ( texture.image.length === 6 ) {
+
+			if ( texture.needsUpdate ) {
+
+				if ( ! texture.image.__webglTextureCube ) {
+
+					texture.image.__webglTextureCube = _gl.createTexture();
+
+					_this.info.memory.textures ++;
+
+				}
+
+				_gl.activeTexture( _gl.TEXTURE0 + slot );
+				_gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube );
+
+				_gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );
+
+				var isCompressed = texture instanceof THREE.CompressedTexture;
+
+				var cubeImage = [];
+
+				for ( var i = 0; i < 6; i ++ ) {
+
+					if ( _this.autoScaleCubemaps && ! isCompressed ) {
+
+						cubeImage[ i ] = clampToMaxSize( texture.image[ i ], _maxCubemapSize );
+
+					} else {
+
+						cubeImage[ i ] = texture.image[ i ];
+
+					}
+
+				}
+
+				var image = cubeImage[ 0 ],
+				isImagePowerOfTwo = isPowerOfTwo( image.width ) && isPowerOfTwo( image.height ),
+				glFormat = paramThreeToGL( texture.format ),
+				glType = paramThreeToGL( texture.type );
+
+				setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isImagePowerOfTwo );
+
+				for ( var i = 0; i < 6; i ++ ) {
+
+					if ( isCompressed ) {
+
+						var mipmap, mipmaps = cubeImage[ i ].mipmaps;
+
+						for( var j = 0, jl = mipmaps.length; j < jl; j ++ ) {
+
+							mipmap = mipmaps[ j ];
+							_gl.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+
+						}
+
+					} else {
+
+						_gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] );
+
+					}
+
+				}
+
+				if ( texture.generateMipmaps && isImagePowerOfTwo ) {
+
+					_gl.generateMipmap( _gl.TEXTURE_CUBE_MAP );
+
+				}
+
+				texture.needsUpdate = false;
+
+				if ( texture.onUpdate ) texture.onUpdate();
+
+			} else {
+
+				_gl.activeTexture( _gl.TEXTURE0 + slot );
+				_gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube );
+
+			}
+
+		}
+
+	};
+
+	function setCubeTextureDynamic ( texture, slot ) {
+
+		_gl.activeTexture( _gl.TEXTURE0 + slot );
+		_gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.__webglTexture );
+
+	};
+
+	// Render targets
+
+	function setupFrameBuffer ( framebuffer, renderTarget, textureTarget ) {
+
+		_gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
+		_gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureTarget, renderTarget.__webglTexture, 0 );
+
+	};
+
+	function setupRenderBuffer ( renderbuffer, renderTarget  ) {
+
+		_gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer );
+
+		if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
+
+			_gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height );
+			_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
+
+		/* For some reason this is not working. Defaulting to RGBA4.
+		} else if( ! renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+			_gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.STENCIL_INDEX8, renderTarget.width, renderTarget.height );
+			_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
+		*/
+		} else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+			_gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height );
+			_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
+
+		} else {
+
+			_gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height );
+
+		}
+
+	};
+
+	this.setRenderTarget = function ( renderTarget ) {
+
+		var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube );
+
+		if ( renderTarget && ! renderTarget.__webglFramebuffer ) {
+
+			if ( renderTarget.depthBuffer === undefined ) renderTarget.depthBuffer = true;
+			if ( renderTarget.stencilBuffer === undefined ) renderTarget.stencilBuffer = true;
+
+			renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
+
+			renderTarget.__webglTexture = _gl.createTexture();
+
+			_this.info.memory.textures ++;
+
+			// Setup texture, create render and frame buffers
+
+			var isTargetPowerOfTwo = isPowerOfTwo( renderTarget.width ) && isPowerOfTwo( renderTarget.height ),
+				glFormat = paramThreeToGL( renderTarget.format ),
+				glType = paramThreeToGL( renderTarget.type );
+
+			if ( isCube ) {
+
+				renderTarget.__webglFramebuffer = [];
+				renderTarget.__webglRenderbuffer = [];
+
+				_gl.bindTexture( _gl.TEXTURE_CUBE_MAP, renderTarget.__webglTexture );
+				setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget, isTargetPowerOfTwo );
+
+				for ( var i = 0; i < 6; i ++ ) {
+
+					renderTarget.__webglFramebuffer[ i ] = _gl.createFramebuffer();
+					renderTarget.__webglRenderbuffer[ i ] = _gl.createRenderbuffer();
+
+					_gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+
+					setupFrameBuffer( renderTarget.__webglFramebuffer[ i ], renderTarget, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i );
+					setupRenderBuffer( renderTarget.__webglRenderbuffer[ i ], renderTarget );
+
+				}
+
+				if ( isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP );
+
+			} else {
+
+				renderTarget.__webglFramebuffer = _gl.createFramebuffer();
+
+				if ( renderTarget.shareDepthFrom ) {
+
+					renderTarget.__webglRenderbuffer = renderTarget.shareDepthFrom.__webglRenderbuffer;
+
+				} else {
+
+					renderTarget.__webglRenderbuffer = _gl.createRenderbuffer();
+
+				}
+
+				_gl.bindTexture( _gl.TEXTURE_2D, renderTarget.__webglTexture );
+				setTextureParameters( _gl.TEXTURE_2D, renderTarget, isTargetPowerOfTwo );
+
+				_gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+
+				setupFrameBuffer( renderTarget.__webglFramebuffer, renderTarget, _gl.TEXTURE_2D );
+
+				if ( renderTarget.shareDepthFrom ) {
+
+					if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
+
+						_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer );
+
+					} else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+						_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer );
+
+					}
+
+				} else {
+
+					setupRenderBuffer( renderTarget.__webglRenderbuffer, renderTarget );
+
+				}
+
+				if ( isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D );
+
+			}
+
+			// Release everything
+
+			if ( isCube ) {
+
+				_gl.bindTexture( _gl.TEXTURE_CUBE_MAP, null );
+
+			} else {
+
+				_gl.bindTexture( _gl.TEXTURE_2D, null );
+
+			}
+
+			_gl.bindRenderbuffer( _gl.RENDERBUFFER, null );
+			_gl.bindFramebuffer( _gl.FRAMEBUFFER, null );
+
+		}
+
+		var framebuffer, width, height, vx, vy;
+
+		if ( renderTarget ) {
+
+			if ( isCube ) {
+
+				framebuffer = renderTarget.__webglFramebuffer[ renderTarget.activeCubeFace ];
+
+			} else {
+
+				framebuffer = renderTarget.__webglFramebuffer;
+
+			}
+
+			width = renderTarget.width;
+			height = renderTarget.height;
+
+			vx = 0;
+			vy = 0;
+
+		} else {
+
+			framebuffer = null;
+
+			width = _viewportWidth;
+			height = _viewportHeight;
+
+			vx = _viewportX;
+			vy = _viewportY;
+
+		}
+
+		if ( framebuffer !== _currentFramebuffer ) {
+
+			_gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
+			_gl.viewport( vx, vy, width, height );
+
+			_currentFramebuffer = framebuffer;
+
+		}
+
+		_currentWidth = width;
+		_currentHeight = height;
+
+	};
+
+	function updateRenderTargetMipmap ( renderTarget ) {
+
+		if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) {
+
+			_gl.bindTexture( _gl.TEXTURE_CUBE_MAP, renderTarget.__webglTexture );
+			_gl.generateMipmap( _gl.TEXTURE_CUBE_MAP );
+			_gl.bindTexture( _gl.TEXTURE_CUBE_MAP, null );
+
+		} else {
+
+			_gl.bindTexture( _gl.TEXTURE_2D, renderTarget.__webglTexture );
+			_gl.generateMipmap( _gl.TEXTURE_2D );
+			_gl.bindTexture( _gl.TEXTURE_2D, null );
+
+		}
+
+	};
+
+	// Fallback filters for non-power-of-2 textures
+
+	function filterFallback ( f ) {
+
+		if ( f === THREE.NearestFilter || f === THREE.NearestMipMapNearestFilter || f === THREE.NearestMipMapLinearFilter ) {
+
+			return _gl.NEAREST;
+
+		}
+
+		return _gl.LINEAR;
+
+	};
+
+	// Map three.js constants to WebGL constants
+
+	function paramThreeToGL ( p ) {
+
+		if ( p === THREE.RepeatWrapping ) return _gl.REPEAT;
+		if ( p === THREE.ClampToEdgeWrapping ) return _gl.CLAMP_TO_EDGE;
+		if ( p === THREE.MirroredRepeatWrapping ) return _gl.MIRRORED_REPEAT;
+
+		if ( p === THREE.NearestFilter ) return _gl.NEAREST;
+		if ( p === THREE.NearestMipMapNearestFilter ) return _gl.NEAREST_MIPMAP_NEAREST;
+		if ( p === THREE.NearestMipMapLinearFilter ) return _gl.NEAREST_MIPMAP_LINEAR;
+
+		if ( p === THREE.LinearFilter ) return _gl.LINEAR;
+		if ( p === THREE.LinearMipMapNearestFilter ) return _gl.LINEAR_MIPMAP_NEAREST;
+		if ( p === THREE.LinearMipMapLinearFilter ) return _gl.LINEAR_MIPMAP_LINEAR;
+
+		if ( p === THREE.UnsignedByteType ) return _gl.UNSIGNED_BYTE;
+		if ( p === THREE.UnsignedShort4444Type ) return _gl.UNSIGNED_SHORT_4_4_4_4;
+		if ( p === THREE.UnsignedShort5551Type ) return _gl.UNSIGNED_SHORT_5_5_5_1;
+		if ( p === THREE.UnsignedShort565Type ) return _gl.UNSIGNED_SHORT_5_6_5;
+
+		if ( p === THREE.ByteType ) return _gl.BYTE;
+		if ( p === THREE.ShortType ) return _gl.SHORT;
+		if ( p === THREE.UnsignedShortType ) return _gl.UNSIGNED_SHORT;
+		if ( p === THREE.IntType ) return _gl.INT;
+		if ( p === THREE.UnsignedIntType ) return _gl.UNSIGNED_INT;
+		if ( p === THREE.FloatType ) return _gl.FLOAT;
+
+		if ( p === THREE.AlphaFormat ) return _gl.ALPHA;
+		if ( p === THREE.RGBFormat ) return _gl.RGB;
+		if ( p === THREE.RGBAFormat ) return _gl.RGBA;
+		if ( p === THREE.LuminanceFormat ) return _gl.LUMINANCE;
+		if ( p === THREE.LuminanceAlphaFormat ) return _gl.LUMINANCE_ALPHA;
+
+		if ( p === THREE.AddEquation ) return _gl.FUNC_ADD;
+		if ( p === THREE.SubtractEquation ) return _gl.FUNC_SUBTRACT;
+		if ( p === THREE.ReverseSubtractEquation ) return _gl.FUNC_REVERSE_SUBTRACT;
+
+		if ( p === THREE.ZeroFactor ) return _gl.ZERO;
+		if ( p === THREE.OneFactor ) return _gl.ONE;
+		if ( p === THREE.SrcColorFactor ) return _gl.SRC_COLOR;
+		if ( p === THREE.OneMinusSrcColorFactor ) return _gl.ONE_MINUS_SRC_COLOR;
+		if ( p === THREE.SrcAlphaFactor ) return _gl.SRC_ALPHA;
+		if ( p === THREE.OneMinusSrcAlphaFactor ) return _gl.ONE_MINUS_SRC_ALPHA;
+		if ( p === THREE.DstAlphaFactor ) return _gl.DST_ALPHA;
+		if ( p === THREE.OneMinusDstAlphaFactor ) return _gl.ONE_MINUS_DST_ALPHA;
+
+		if ( p === THREE.DstColorFactor ) return _gl.DST_COLOR;
+		if ( p === THREE.OneMinusDstColorFactor ) return _gl.ONE_MINUS_DST_COLOR;
+		if ( p === THREE.SrcAlphaSaturateFactor ) return _gl.SRC_ALPHA_SATURATE;
+
+		if ( _glExtensionCompressedTextureS3TC !== undefined ) {
+
+			if ( p === THREE.RGB_S3TC_DXT1_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGB_S3TC_DXT1_EXT;
+			if ( p === THREE.RGBA_S3TC_DXT1_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+			if ( p === THREE.RGBA_S3TC_DXT3_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT3_EXT;
+			if ( p === THREE.RGBA_S3TC_DXT5_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+		}
+
+		return 0;
+
+	};
+
+	// Allocations
+
+	function allocateBones ( object ) {
+
+		if ( _supportsBoneTextures && object && object.useVertexTexture ) {
+
+			return 1024;
+
+		} else {
+
+			// default for when object is not specified
+			// ( for example when prebuilding shader
+			//   to be used with multiple objects )
+			//
+			// 	- leave some extra space for other uniforms
+			//  - limit here is ANGLE's 254 max uniform vectors
+			//    (up to 54 should be safe)
+
+			var nVertexUniforms = _gl.getParameter( _gl.MAX_VERTEX_UNIFORM_VECTORS );
+			var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 );
+
+			var maxBones = nVertexMatrices;
+
+			if ( object !== undefined && object instanceof THREE.SkinnedMesh ) {
+
+				maxBones = Math.min( object.bones.length, maxBones );
+
+				if ( maxBones < object.bones.length ) {
+
+					console.warn( "WebGLRenderer: too many bones - " + object.bones.length + ", this GPU supports just " + maxBones + " (try OpenGL instead of ANGLE)" );
+
+				}
+
+			}
+
+			return maxBones;
+
+		}
+
+	};
+
+	function allocateLights ( lights ) {
+
+		var l, ll, light, dirLights, pointLights, spotLights, hemiLights;
+
+		dirLights = pointLights = spotLights = hemiLights = 0;
+
+		for ( l = 0, ll = lights.length; l < ll; l ++ ) {
+
+			light = lights[ l ];
+
+			if ( light.onlyShadow ) continue;
+
+			if ( light instanceof THREE.DirectionalLight ) dirLights ++;
+			if ( light instanceof THREE.PointLight ) pointLights ++;
+			if ( light instanceof THREE.SpotLight ) spotLights ++;
+			if ( light instanceof THREE.HemisphereLight ) hemiLights ++;
+
+		}
+
+		return { 'directional' : dirLights, 'point' : pointLights, 'spot': spotLights, 'hemi': hemiLights };
+
+	};
+
+	function allocateShadows ( lights ) {
+
+		var l, ll, light, maxShadows = 0;
+
+		for ( l = 0, ll = lights.length; l < ll; l++ ) {
+
+			light = lights[ l ];
+
+			if ( ! light.castShadow ) continue;
+
+			if ( light instanceof THREE.SpotLight ) maxShadows ++;
+			if ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) maxShadows ++;
+
+		}
+
+		return maxShadows;
+
+	};
+
+	// Initialization
+
+	function initGL () {
+
+		try {
+
+			if ( ! ( _gl = _canvas.getContext( 'experimental-webgl', { alpha: _alpha, premultipliedAlpha: _premultipliedAlpha, antialias: _antialias, stencil: _stencil, preserveDrawingBuffer: _preserveDrawingBuffer } ) ) ) {
+
+				throw 'Error creating WebGL context.';
+
+			}
+
+		} catch ( error ) {
+
+			console.error( error );
+
+		}
+
+		_glExtensionTextureFloat = _gl.getExtension( 'OES_texture_float' );
+		_glExtensionStandardDerivatives = _gl.getExtension( 'OES_standard_derivatives' );
+
+		_glExtensionTextureFilterAnisotropic = _gl.getExtension( 'EXT_texture_filter_anisotropic' ) ||
+											   _gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) ||
+											   _gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' );
+
+
+		_glExtensionCompressedTextureS3TC = _gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) ||
+											_gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) ||
+											_gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' );
+
+		if ( ! _glExtensionTextureFloat ) {
+
+			console.log( 'THREE.WebGLRenderer: Float textures not supported.' );
+
+		}
+
+		if ( ! _glExtensionStandardDerivatives ) {
+
+			console.log( 'THREE.WebGLRenderer: Standard derivatives not supported.' );
+
+		}
+
+		if ( ! _glExtensionTextureFilterAnisotropic ) {
+
+			console.log( 'THREE.WebGLRenderer: Anisotropic texture filtering not supported.' );
+
+		}
+
+		if ( ! _glExtensionCompressedTextureS3TC ) {
+
+			console.log( 'THREE.WebGLRenderer: S3TC compressed textures not supported.' );
+
+		}
+
+		if ( _gl.getShaderPrecisionFormat === undefined ) {
+
+			_gl.getShaderPrecisionFormat = function() {
+
+				return {
+					"rangeMin"  : 1,
+					"rangeMax"  : 1,
+					"precision" : 1
+				};
+
+			}
+		}
+
+	};
+
+	function setDefaultGLState () {
+
+		_gl.clearColor( 0, 0, 0, 1 );
+		_gl.clearDepth( 1 );
+		_gl.clearStencil( 0 );
+
+		_gl.enable( _gl.DEPTH_TEST );
+		_gl.depthFunc( _gl.LEQUAL );
+
+		_gl.frontFace( _gl.CCW );
+		_gl.cullFace( _gl.BACK );
+		_gl.enable( _gl.CULL_FACE );
+
+		_gl.enable( _gl.BLEND );
+		_gl.blendEquation( _gl.FUNC_ADD );
+		_gl.blendFunc( _gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA );
+
+		_gl.clearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha );
+
+	};
+
+	// default plugins (order is important)
+
+	this.shadowMapPlugin = new THREE.ShadowMapPlugin();
+	this.addPrePlugin( this.shadowMapPlugin );
+
+	this.addPostPlugin( new THREE.SpritePlugin() );
+	this.addPostPlugin( new THREE.LensFlarePlugin() );
+
+};
+/**
+ * @author szimek / https://github.com/szimek/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.WebGLRenderTarget = function ( width, height, options ) {
+
+	this.width = width;
+	this.height = height;
+
+	options = options || {};
+
+	this.wrapS = options.wrapS !== undefined ? options.wrapS : THREE.ClampToEdgeWrapping;
+	this.wrapT = options.wrapT !== undefined ? options.wrapT : THREE.ClampToEdgeWrapping;
+
+	this.magFilter = options.magFilter !== undefined ? options.magFilter : THREE.LinearFilter;
+	this.minFilter = options.minFilter !== undefined ? options.minFilter : THREE.LinearMipMapLinearFilter;
+
+	this.anisotropy = options.anisotropy !== undefined ? options.anisotropy : 1;
+
+	this.offset = new THREE.Vector2( 0, 0 );
+	this.repeat = new THREE.Vector2( 1, 1 );
+
+	this.format = options.format !== undefined ? options.format : THREE.RGBAFormat;
+	this.type = options.type !== undefined ? options.type : THREE.UnsignedByteType;
+
+	this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;
+	this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true;
+
+	this.generateMipmaps = true;
+
+	this.shareDepthFrom = null;
+
+};
+
+THREE.WebGLRenderTarget.prototype = {
+
+	constructor: THREE.WebGLRenderTarget,
+
+	addEventListener: THREE.EventDispatcher.prototype.addEventListener,
+	hasEventListener: THREE.EventDispatcher.prototype.hasEventListener,
+	removeEventListener: THREE.EventDispatcher.prototype.removeEventListener,
+	dispatchEvent: THREE.EventDispatcher.prototype.dispatchEvent,
+
+	clone: function () {
+
+		var tmp = new THREE.WebGLRenderTarget( this.width, this.height );
+
+		tmp.wrapS = this.wrapS;
+		tmp.wrapT = this.wrapT;
+
+		tmp.magFilter = this.magFilter;
+		tmp.minFilter = this.minFilter;
+
+		tmp.anisotropy = this.anisotropy;
+
+		tmp.offset.copy( this.offset );
+		tmp.repeat.copy( this.repeat );
+
+		tmp.format = this.format;
+		tmp.type = this.type;
+
+		tmp.depthBuffer = this.depthBuffer;
+		tmp.stencilBuffer = this.stencilBuffer;
+
+		tmp.generateMipmaps = this.generateMipmaps;
+
+		tmp.shareDepthFrom = this.shareDepthFrom;
+
+		return tmp;
+
+	},
+
+	dispose: function () {
+
+		this.dispatchEvent( { type: 'dispose' } );
+
+	}
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com
+ */
+
+THREE.WebGLRenderTargetCube = function ( width, height, options ) {
+
+	THREE.WebGLRenderTarget.call( this, width, height, options );
+
+	this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5
+
+};
+
+THREE.WebGLRenderTargetCube.prototype = Object.create( THREE.WebGLRenderTarget.prototype );
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableVertex = function () {
+
+	this.positionWorld = new THREE.Vector3();
+	this.positionScreen = new THREE.Vector4();
+
+	this.visible = true;
+
+};
+
+THREE.RenderableVertex.prototype.copy = function ( vertex ) {
+
+	this.positionWorld.copy( vertex.positionWorld );
+	this.positionScreen.copy( vertex.positionScreen );
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableFace3 = function () {
+
+	this.v1 = new THREE.RenderableVertex();
+	this.v2 = new THREE.RenderableVertex();
+	this.v3 = new THREE.RenderableVertex();
+
+	this.centroidModel = new THREE.Vector3();
+
+	this.normalModel = new THREE.Vector3();
+	this.normalModelView = new THREE.Vector3();
+
+	this.vertexNormalsLength = 0;
+	this.vertexNormalsModel = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+	this.vertexNormalsModelView = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+
+	this.color = null;
+	this.material = null;
+	this.uvs = [[]];
+
+	this.z = null;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableFace4 = function () {
+
+	this.v1 = new THREE.RenderableVertex();
+	this.v2 = new THREE.RenderableVertex();
+	this.v3 = new THREE.RenderableVertex();
+	this.v4 = new THREE.RenderableVertex();
+
+	this.centroidModel = new THREE.Vector3();
+
+	this.normalModel = new THREE.Vector3();
+	this.normalModelView = new THREE.Vector3();
+
+	this.vertexNormalsLength = 0;
+	this.vertexNormalsModel = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+	this.vertexNormalsModelView = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+
+	this.color = null;
+	this.material = null;
+	this.uvs = [[]];
+
+	this.z = null;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableObject = function () {
+
+	this.object = null;
+	this.z = null;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableParticle = function () {
+
+	this.object = null;
+
+	this.x = null;
+	this.y = null;
+	this.z = null;
+
+	this.rotation = null;
+	this.scale = new THREE.Vector2();
+
+	this.material = null;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableLine = function () {
+
+	this.z = null;
+
+	this.v1 = new THREE.RenderableVertex();
+	this.v2 = new THREE.RenderableVertex();
+
+	this.vertexColors = [ new THREE.Color(), new THREE.Color() ];
+	this.material = null;
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.GeometryUtils = {
+
+	// Merge two geometries or geometry and geometry from object (using object's transform)
+
+	merge: function ( geometry1, object2 /* mesh | geometry */, materialIndexOffset ) {
+
+		var matrix, normalMatrix,
+		vertexOffset = geometry1.vertices.length,
+		uvPosition = geometry1.faceVertexUvs[ 0 ].length,
+		geometry2 = object2 instanceof THREE.Mesh ? object2.geometry : object2,
+		vertices1 = geometry1.vertices,
+		vertices2 = geometry2.vertices,
+		faces1 = geometry1.faces,
+		faces2 = geometry2.faces,
+		uvs1 = geometry1.faceVertexUvs[ 0 ],
+		uvs2 = geometry2.faceVertexUvs[ 0 ];
+
+		if ( materialIndexOffset === undefined ) materialIndexOffset = 0;
+
+		if ( object2 instanceof THREE.Mesh ) {
+
+			object2.matrixAutoUpdate && object2.updateMatrix();
+
+			matrix = object2.matrix;
+
+			normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+		}
+
+		// vertices
+
+		for ( var i = 0, il = vertices2.length; i < il; i ++ ) {
+
+			var vertex = vertices2[ i ];
+
+			var vertexCopy = vertex.clone();
+
+			if ( matrix ) vertexCopy.applyMatrix4( matrix );
+
+			vertices1.push( vertexCopy );
+
+		}
+
+		// faces
+
+		for ( i = 0, il = faces2.length; i < il; i ++ ) {
+
+			var face = faces2[ i ], faceCopy, normal, color,
+			faceVertexNormals = face.vertexNormals,
+			faceVertexColors = face.vertexColors;
+
+			if ( face instanceof THREE.Face3 ) {
+
+				faceCopy = new THREE.Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset );
+
+			} else if ( face instanceof THREE.Face4 ) {
+
+				faceCopy = new THREE.Face4( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset, face.d + vertexOffset );
+
+			}
+
+			faceCopy.normal.copy( face.normal );
+
+			if ( normalMatrix ) {
+
+				faceCopy.normal.applyMatrix3( normalMatrix ).normalize();
+
+			}
+
+			for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) {
+
+				normal = faceVertexNormals[ j ].clone();
+
+				if ( normalMatrix ) {
+
+					normal.applyMatrix3( normalMatrix ).normalize();
+
+				}
+
+				faceCopy.vertexNormals.push( normal );
+
+			}
+
+			faceCopy.color.copy( face.color );
+
+			for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) {
+
+				color = faceVertexColors[ j ];
+				faceCopy.vertexColors.push( color.clone() );
+
+			}
+
+			faceCopy.materialIndex = face.materialIndex + materialIndexOffset;
+
+			faceCopy.centroid.copy( face.centroid );
+
+			if ( matrix ) {
+
+				faceCopy.centroid.applyMatrix4( matrix );
+
+			}
+
+			faces1.push( faceCopy );
+
+		}
+
+		// uvs
+
+		for ( i = 0, il = uvs2.length; i < il; i ++ ) {
+
+			var uv = uvs2[ i ], uvCopy = [];
+
+			for ( var j = 0, jl = uv.length; j < jl; j ++ ) {
+
+				uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) );
+
+			}
+
+			uvs1.push( uvCopy );
+
+		}
+
+	},
+
+	removeMaterials: function ( geometry, materialIndexArray ) {
+
+		var materialIndexMap = {};
+
+		for ( var i = 0, il = materialIndexArray.length; i < il; i ++ ) {
+
+			materialIndexMap[ materialIndexArray[i] ] = true;
+
+		}
+
+		var face, newFaces = [];
+
+		for ( var i = 0, il = geometry.faces.length; i < il; i ++ ) {
+
+			face = geometry.faces[ i ];
+			if ( ! ( face.materialIndex in materialIndexMap ) ) newFaces.push( face );
+
+		}
+
+		geometry.faces = newFaces;
+
+	},
+
+	// Get random point in triangle (via barycentric coordinates)
+	// 	(uniform distribution)
+	// 	http://www.cgafaq.info/wiki/Random_Point_In_Triangle
+
+	randomPointInTriangle: function ( vectorA, vectorB, vectorC ) {
+
+		var a, b, c,
+			point = new THREE.Vector3(),
+			tmp = THREE.GeometryUtils.__v1;
+
+		a = THREE.GeometryUtils.random();
+		b = THREE.GeometryUtils.random();
+
+		if ( ( a + b ) > 1 ) {
+
+			a = 1 - a;
+			b = 1 - b;
+
+		}
+
+		c = 1 - a - b;
+
+		point.copy( vectorA );
+		point.multiplyScalar( a );
+
+		tmp.copy( vectorB );
+		tmp.multiplyScalar( b );
+
+		point.add( tmp );
+
+		tmp.copy( vectorC );
+		tmp.multiplyScalar( c );
+
+		point.add( tmp );
+
+		return point;
+
+	},
+
+	// Get random point in face (triangle / quad)
+	// (uniform distribution)
+
+	randomPointInFace: function ( face, geometry, useCachedAreas ) {
+
+		var vA, vB, vC, vD;
+
+		if ( face instanceof THREE.Face3 ) {
+
+			vA = geometry.vertices[ face.a ];
+			vB = geometry.vertices[ face.b ];
+			vC = geometry.vertices[ face.c ];
+
+			return THREE.GeometryUtils.randomPointInTriangle( vA, vB, vC );
+
+		} else if ( face instanceof THREE.Face4 ) {
+
+			vA = geometry.vertices[ face.a ];
+			vB = geometry.vertices[ face.b ];
+			vC = geometry.vertices[ face.c ];
+			vD = geometry.vertices[ face.d ];
+
+			var area1, area2;
+
+			if ( useCachedAreas ) {
+
+				if ( face._area1 && face._area2 ) {
+
+					area1 = face._area1;
+					area2 = face._area2;
+
+				} else {
+
+					area1 = THREE.GeometryUtils.triangleArea( vA, vB, vD );
+					area2 = THREE.GeometryUtils.triangleArea( vB, vC, vD );
+
+					face._area1 = area1;
+					face._area2 = area2;
+
+				}
+
+			} else {
+
+				area1 = THREE.GeometryUtils.triangleArea( vA, vB, vD ),
+				area2 = THREE.GeometryUtils.triangleArea( vB, vC, vD );
+
+			}
+
+			var r = THREE.GeometryUtils.random() * ( area1 + area2 );
+
+			if ( r < area1 ) {
+
+				return THREE.GeometryUtils.randomPointInTriangle( vA, vB, vD );
+
+			} else {
+
+				return THREE.GeometryUtils.randomPointInTriangle( vB, vC, vD );
+
+			}
+
+		}
+
+	},
+
+	// Get uniformly distributed random points in mesh
+	// 	- create array with cumulative sums of face areas
+	//  - pick random number from 0 to total area
+	//  - find corresponding place in area array by binary search
+	//	- get random point in face
+
+	randomPointsInGeometry: function ( geometry, n ) {
+
+		var face, i,
+			faces = geometry.faces,
+			vertices = geometry.vertices,
+			il = faces.length,
+			totalArea = 0,
+			cumulativeAreas = [],
+			vA, vB, vC, vD;
+
+		// precompute face areas
+
+		for ( i = 0; i < il; i ++ ) {
+
+			face = faces[ i ];
+
+			if ( face instanceof THREE.Face3 ) {
+
+				vA = vertices[ face.a ];
+				vB = vertices[ face.b ];
+				vC = vertices[ face.c ];
+
+				face._area = THREE.GeometryUtils.triangleArea( vA, vB, vC );
+
+			} else if ( face instanceof THREE.Face4 ) {
+
+				vA = vertices[ face.a ];
+				vB = vertices[ face.b ];
+				vC = vertices[ face.c ];
+				vD = vertices[ face.d ];
+
+				face._area1 = THREE.GeometryUtils.triangleArea( vA, vB, vD );
+				face._area2 = THREE.GeometryUtils.triangleArea( vB, vC, vD );
+
+				face._area = face._area1 + face._area2;
+
+			}
+
+			totalArea += face._area;
+
+			cumulativeAreas[ i ] = totalArea;
+
+		}
+
+		// binary search cumulative areas array
+
+		function binarySearchIndices( value ) {
+
+			function binarySearch( start, end ) {
+
+				// return closest larger index
+				// if exact number is not found
+
+				if ( end < start )
+					return start;
+
+				var mid = start + Math.floor( ( end - start ) / 2 );
+
+				if ( cumulativeAreas[ mid ] > value ) {
+
+					return binarySearch( start, mid - 1 );
+
+				} else if ( cumulativeAreas[ mid ] < value ) {
+
+					return binarySearch( mid + 1, end );
+
+				} else {
+
+					return mid;
+
+				}
+
+			}
+
+			var result = binarySearch( 0, cumulativeAreas.length - 1 )
+			return result;
+
+		}
+
+		// pick random face weighted by face area
+
+		var r, index,
+			result = [];
+
+		var stats = {};
+
+		for ( i = 0; i < n; i ++ ) {
+
+			r = THREE.GeometryUtils.random() * totalArea;
+
+			index = binarySearchIndices( r );
+
+			result[ i ] = THREE.GeometryUtils.randomPointInFace( faces[ index ], geometry, true );
+
+			if ( ! stats[ index ] ) {
+
+				stats[ index ] = 1;
+
+			} else {
+
+				stats[ index ] += 1;
+
+			}
+
+		}
+
+		return result;
+
+	},
+
+	// Get triangle area (half of parallelogram)
+	//	http://mathworld.wolfram.com/TriangleArea.html
+
+	triangleArea: function ( vectorA, vectorB, vectorC ) {
+
+		var tmp1 = THREE.GeometryUtils.__v1,
+			tmp2 = THREE.GeometryUtils.__v2;
+
+		tmp1.subVectors( vectorB, vectorA );
+		tmp2.subVectors( vectorC, vectorA );
+		tmp1.cross( tmp2 );
+
+		return 0.5 * tmp1.length();
+
+	},
+
+	// Center geometry so that 0,0,0 is in center of bounding box
+
+	center: function ( geometry ) {
+
+		geometry.computeBoundingBox();
+
+		var bb = geometry.boundingBox;
+
+		var offset = new THREE.Vector3();
+
+		offset.addVectors( bb.min, bb.max );
+		offset.multiplyScalar( -0.5 );
+
+		geometry.applyMatrix( new THREE.Matrix4().makeTranslation( offset.x, offset.y, offset.z ) );
+		geometry.computeBoundingBox();
+
+		return offset;
+
+	},
+
+	// Normalize UVs to be from <0,1>
+	// (for now just the first set of UVs)
+
+	normalizeUVs: function ( geometry ) {
+
+		var uvSet = geometry.faceVertexUvs[ 0 ];
+
+		for ( var i = 0, il = uvSet.length; i < il; i ++ ) {
+
+			var uvs = uvSet[ i ];
+
+			for ( var j = 0, jl = uvs.length; j < jl; j ++ ) {
+
+				// texture repeat
+
+				if( uvs[ j ].x !== 1.0 ) uvs[ j ].x = uvs[ j ].x - Math.floor( uvs[ j ].x );
+				if( uvs[ j ].y !== 1.0 ) uvs[ j ].y = uvs[ j ].y - Math.floor( uvs[ j ].y );
+
+			}
+
+		}
+
+	},
+
+	triangulateQuads: function ( geometry ) {
+
+		var i, il, j, jl;
+
+		var faces = [];
+		var faceUvs = [];
+		var faceVertexUvs = [];
+
+		for ( i = 0, il = geometry.faceUvs.length; i < il; i ++ ) {
+
+			faceUvs[ i ] = [];
+
+		}
+
+		for ( i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) {
+
+			faceVertexUvs[ i ] = [];
+
+		}
+
+		for ( i = 0, il = geometry.faces.length; i < il; i ++ ) {
+
+			var face = geometry.faces[ i ];
+
+			if ( face instanceof THREE.Face4 ) {
+
+				var a = face.a;
+				var b = face.b;
+				var c = face.c;
+				var d = face.d;
+
+				var triA = new THREE.Face3();
+				var triB = new THREE.Face3();
+
+				triA.color.copy( face.color );
+				triB.color.copy( face.color );
+
+				triA.materialIndex = face.materialIndex;
+				triB.materialIndex = face.materialIndex;
+
+				triA.a = a;
+				triA.b = b;
+				triA.c = d;
+
+				triB.a = b;
+				triB.b = c;
+				triB.c = d;
+
+				if ( face.vertexColors.length === 4 ) {
+
+					triA.vertexColors[ 0 ] = face.vertexColors[ 0 ].clone();
+					triA.vertexColors[ 1 ] = face.vertexColors[ 1 ].clone();
+					triA.vertexColors[ 2 ] = face.vertexColors[ 3 ].clone();
+
+					triB.vertexColors[ 0 ] = face.vertexColors[ 1 ].clone();
+					triB.vertexColors[ 1 ] = face.vertexColors[ 2 ].clone();
+					triB.vertexColors[ 2 ] = face.vertexColors[ 3 ].clone();
+
+				}
+
+				faces.push( triA, triB );
+
+				for ( j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) {
+
+					if ( geometry.faceVertexUvs[ j ].length ) {
+
+						var uvs = geometry.faceVertexUvs[ j ][ i ];
+
+						var uvA = uvs[ 0 ];
+						var uvB = uvs[ 1 ];
+						var uvC = uvs[ 2 ];
+						var uvD = uvs[ 3 ];
+
+						var uvsTriA = [ uvA.clone(), uvB.clone(), uvD.clone() ];
+						var uvsTriB = [ uvB.clone(), uvC.clone(), uvD.clone() ];
+
+						faceVertexUvs[ j ].push( uvsTriA, uvsTriB );
+
+					}
+
+				}
+
+				for ( j = 0, jl = geometry.faceUvs.length; j < jl; j ++ ) {
+
+					if ( geometry.faceUvs[ j ].length ) {
+
+						var faceUv = geometry.faceUvs[ j ][ i ];
+
+						faceUvs[ j ].push( faceUv, faceUv );
+
+					}
+
+				}
+
+			} else {
+
+				faces.push( face );
+
+				for ( j = 0, jl = geometry.faceUvs.length; j < jl; j ++ ) {
+
+					faceUvs[ j ].push( geometry.faceUvs[ j ][ i ] );
+
+				}
+
+				for ( j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) {
+
+					faceVertexUvs[ j ].push( geometry.faceVertexUvs[ j ][ i ] );
+
+				}
+
+			}
+
+		}
+
+		geometry.faces = faces;
+		geometry.faceUvs = faceUvs;
+		geometry.faceVertexUvs = faceVertexUvs;
+
+		geometry.computeCentroids();
+		geometry.computeFaceNormals();
+		geometry.computeVertexNormals();
+
+		if ( geometry.hasTangents ) geometry.computeTangents();
+
+	},
+
+	setMaterialIndex: function ( geometry, index, startFace, endFace ){
+
+		var faces = geometry.faces;
+		var start = startFace || 0;
+		var end = endFace || faces.length - 1;
+
+		for ( var i = start; i <= end; i ++ ) {
+
+			faces[i].materialIndex = index;
+
+		}
+
+    }
+
+};
+
+THREE.GeometryUtils.random = THREE.Math.random16;
+
+THREE.GeometryUtils.__v1 = new THREE.Vector3();
+THREE.GeometryUtils.__v2 = new THREE.Vector3();
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.ImageUtils = {
+
+	crossOrigin: 'anonymous',
+
+	loadTexture: function ( url, mapping, onLoad, onError ) {
+
+		var image = new Image();
+		var texture = new THREE.Texture( image, mapping );
+
+		var loader = new THREE.ImageLoader();
+
+		loader.addEventListener( 'load', function ( event ) {
+
+			texture.image = event.content;
+			texture.needsUpdate = true;
+
+			if ( onLoad ) onLoad( texture );
+
+		} );
+
+		loader.addEventListener( 'error', function ( event ) {
+
+			if ( onError ) onError( event.message );
+
+		} );
+
+		loader.crossOrigin = this.crossOrigin;
+		loader.load( url, image );
+
+		texture.sourceFile = url;
+
+		return texture;
+
+	},
+
+	loadCompressedTexture: function ( url, mapping, onLoad, onError ) {
+
+		var texture = new THREE.CompressedTexture();
+		texture.mapping = mapping;
+
+		var request = new XMLHttpRequest();
+
+		request.onload = function () {
+
+			var buffer = request.response;
+			var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+			texture.format = dds.format;
+
+			texture.mipmaps = dds.mipmaps;
+			texture.image.width = dds.width;
+			texture.image.height = dds.height;
+
+			// gl.generateMipmap fails for compressed textures
+			// mipmaps must be embedded in the DDS file
+			// or texture filters must not use mipmapping
+
+			texture.generateMipmaps = false;
+
+			texture.needsUpdate = true;
+
+			if ( onLoad ) onLoad( texture );
+
+		}
+
+		request.onerror = onError;
+
+		request.open( 'GET', url, true );
+		request.responseType = "arraybuffer";
+		request.send( null );
+
+		return texture;
+
+	},
+
+	loadTextureCube: function ( array, mapping, onLoad, onError ) {
+
+		var images = [];
+		images.loadCount = 0;
+
+		var texture = new THREE.Texture();
+		texture.image = images;
+		if ( mapping !== undefined ) texture.mapping = mapping;
+
+		// no flipping needed for cube textures
+
+		texture.flipY = false;
+
+		for ( var i = 0, il = array.length; i < il; ++ i ) {
+
+			var cubeImage = new Image();
+			images[ i ] = cubeImage;
+
+			cubeImage.onload = function () {
+
+				images.loadCount += 1;
+
+				if ( images.loadCount === 6 ) {
+
+					texture.needsUpdate = true;
+					if ( onLoad ) onLoad( texture );
+
+				}
+
+			};
+
+			cubeImage.onerror = onError;
+
+			cubeImage.crossOrigin = this.crossOrigin;
+			cubeImage.src = array[ i ];
+
+		}
+
+		return texture;
+
+	},
+
+	loadCompressedTextureCube: function ( array, mapping, onLoad, onError ) {
+
+		var images = [];
+		images.loadCount = 0;
+
+		var texture = new THREE.CompressedTexture();
+		texture.image = images;
+		if ( mapping !== undefined ) texture.mapping = mapping;
+
+		// no flipping for cube textures
+		// (also flipping doesn't work for compressed textures )
+
+		texture.flipY = false;
+
+		// can't generate mipmaps for compressed textures
+		// mips must be embedded in DDS files
+
+		texture.generateMipmaps = false;
+
+		var generateCubeFaceCallback = function ( rq, img ) {
+
+			return function () {
+
+				var buffer = rq.response;
+				var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+				img.format = dds.format;
+
+				img.mipmaps = dds.mipmaps;
+				img.width = dds.width;
+				img.height = dds.height;
+
+				images.loadCount += 1;
+
+				if ( images.loadCount === 6 ) {
+
+					texture.format = dds.format;
+					texture.needsUpdate = true;
+					if ( onLoad ) onLoad( texture );
+
+				}
+
+			}
+
+		}
+
+		// compressed cubemap textures as 6 separate DDS files
+
+		if ( array instanceof Array ) {
+
+			for ( var i = 0, il = array.length; i < il; ++ i ) {
+
+				var cubeImage = {};
+				images[ i ] = cubeImage;
+
+				var request = new XMLHttpRequest();
+
+				request.onload = generateCubeFaceCallback( request, cubeImage );
+				request.onerror = onError;
+
+				var url = array[ i ];
+
+				request.open( 'GET', url, true );
+				request.responseType = "arraybuffer";
+				request.send( null );
+
+			}
+
+		// compressed cubemap texture stored in a single DDS file
+
+		} else {
+
+			var url = array;
+			var request = new XMLHttpRequest();
+
+			request.onload = function( ) {
+
+				var buffer = request.response;
+				var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+				if ( dds.isCubemap ) {
+
+					var faces = dds.mipmaps.length / dds.mipmapCount;
+
+					for ( var f = 0; f < faces; f ++ ) {
+
+						images[ f ] = { mipmaps : [] };
+
+						for ( var i = 0; i < dds.mipmapCount; i ++ ) {
+
+							images[ f ].mipmaps.push( dds.mipmaps[ f * dds.mipmapCount + i ] );
+							images[ f ].format = dds.format;
+							images[ f ].width = dds.width;
+							images[ f ].height = dds.height;
+
+						}
+
+					}
+
+					texture.format = dds.format;
+					texture.needsUpdate = true;
+					if ( onLoad ) onLoad( texture );
+
+				}
+
+			}
+
+			request.onerror = onError;
+
+			request.open( 'GET', url, true );
+			request.responseType = "arraybuffer";
+			request.send( null );
+
+		}
+
+		return texture;
+
+	},
+
+	parseDDS: function ( buffer, loadMipmaps ) {
+
+		var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 };
+
+		// Adapted from @toji's DDS utils
+		//	https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js
+
+		// All values and structures referenced from:
+		// http://msdn.microsoft.com/en-us/library/bb943991.aspx/
+
+		var DDS_MAGIC = 0x20534444;
+
+		var DDSD_CAPS = 0x1,
+			DDSD_HEIGHT = 0x2,
+			DDSD_WIDTH = 0x4,
+			DDSD_PITCH = 0x8,
+			DDSD_PIXELFORMAT = 0x1000,
+			DDSD_MIPMAPCOUNT = 0x20000,
+			DDSD_LINEARSIZE = 0x80000,
+			DDSD_DEPTH = 0x800000;
+
+		var DDSCAPS_COMPLEX = 0x8,
+			DDSCAPS_MIPMAP = 0x400000,
+			DDSCAPS_TEXTURE = 0x1000;
+
+		var DDSCAPS2_CUBEMAP = 0x200,
+			DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,
+			DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,
+			DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,
+			DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,
+			DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,
+			DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,
+			DDSCAPS2_VOLUME = 0x200000;
+
+		var DDPF_ALPHAPIXELS = 0x1,
+			DDPF_ALPHA = 0x2,
+			DDPF_FOURCC = 0x4,
+			DDPF_RGB = 0x40,
+			DDPF_YUV = 0x200,
+			DDPF_LUMINANCE = 0x20000;
+
+		function fourCCToInt32( value ) {
+
+			return value.charCodeAt(0) +
+				(value.charCodeAt(1) << 8) +
+				(value.charCodeAt(2) << 16) +
+				(value.charCodeAt(3) << 24);
+
+		}
+
+		function int32ToFourCC( value ) {
+
+			return String.fromCharCode(
+				value & 0xff,
+				(value >> 8) & 0xff,
+				(value >> 16) & 0xff,
+				(value >> 24) & 0xff
+			);
+		}
+
+		var FOURCC_DXT1 = fourCCToInt32("DXT1");
+		var FOURCC_DXT3 = fourCCToInt32("DXT3");
+		var FOURCC_DXT5 = fourCCToInt32("DXT5");
+
+		var headerLengthInt = 31; // The header length in 32 bit ints
+
+		// Offsets into the header array
+
+		var off_magic = 0;
+
+		var off_size = 1;
+		var off_flags = 2;
+		var off_height = 3;
+		var off_width = 4;
+
+		var off_mipmapCount = 7;
+
+		var off_pfFlags = 20;
+		var off_pfFourCC = 21;
+
+		var off_caps = 27;
+		var off_caps2 = 28;
+		var off_caps3 = 29;
+		var off_caps4 = 30;
+
+		// Parse header
+
+		var header = new Int32Array( buffer, 0, headerLengthInt );
+
+		if ( header[ off_magic ] !== DDS_MAGIC ) {
+
+			console.error( "ImageUtils.parseDDS(): Invalid magic number in DDS header" );
+			return dds;
+
+		}
+
+		if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) {
+
+			console.error( "ImageUtils.parseDDS(): Unsupported format, must contain a FourCC code" );
+			return dds;
+
+		}
+
+		var blockBytes;
+
+		var fourCC = header[ off_pfFourCC ];
+
+		switch ( fourCC ) {
+
+			case FOURCC_DXT1:
+
+				blockBytes = 8;
+				dds.format = THREE.RGB_S3TC_DXT1_Format;
+				break;
+
+			case FOURCC_DXT3:
+
+				blockBytes = 16;
+				dds.format = THREE.RGBA_S3TC_DXT3_Format;
+				break;
+
+			case FOURCC_DXT5:
+
+				blockBytes = 16;
+				dds.format = THREE.RGBA_S3TC_DXT5_Format;
+				break;
+
+			default:
+
+				console.error( "ImageUtils.parseDDS(): Unsupported FourCC code: ", int32ToFourCC( fourCC ) );
+				return dds;
+
+		}
+
+		dds.mipmapCount = 1;
+
+		if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) {
+
+			dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] );
+
+		}
+
+		//TODO: Verify that all faces of the cubemap are present with DDSCAPS2_CUBEMAP_POSITIVEX, etc.
+
+		dds.isCubemap = header[ off_caps2 ] & DDSCAPS2_CUBEMAP ? true : false;
+
+		dds.width = header[ off_width ];
+		dds.height = header[ off_height ];
+
+		var dataOffset = header[ off_size ] + 4;
+
+		// Extract mipmaps buffers
+
+		var width = dds.width;
+		var height = dds.height;
+
+		var faces = dds.isCubemap ? 6 : 1;
+
+		for ( var face = 0; face < faces; face ++ ) {
+
+			for ( var i = 0; i < dds.mipmapCount; i ++ ) {
+
+				var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes;
+				var byteArray = new Uint8Array( buffer, dataOffset, dataLength );
+
+				var mipmap = { "data": byteArray, "width": width, "height": height };
+				dds.mipmaps.push( mipmap );
+
+				dataOffset += dataLength;
+
+				width = Math.max( width * 0.5, 1 );
+				height = Math.max( height * 0.5, 1 );
+
+			}
+
+			width = dds.width;
+			height = dds.height;
+
+		}
+
+		return dds;
+
+	},
+
+	getNormalMap: function ( image, depth ) {
+
+		// Adapted from http://www.paulbrunt.co.uk/lab/heightnormal/
+
+		var cross = function ( a, b ) {
+
+			return [ a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ], a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ], a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ] ];
+
+		}
+
+		var subtract = function ( a, b ) {
+
+			return [ a[ 0 ] - b[ 0 ], a[ 1 ] - b[ 1 ], a[ 2 ] - b[ 2 ] ];
+
+		}
+
+		var normalize = function ( a ) {
+
+			var l = Math.sqrt( a[ 0 ] * a[ 0 ] + a[ 1 ] * a[ 1 ] + a[ 2 ] * a[ 2 ] );
+			return [ a[ 0 ] / l, a[ 1 ] / l, a[ 2 ] / l ];
+
+		}
+
+		depth = depth | 1;
+
+		var width = image.width;
+		var height = image.height;
+
+		var canvas = document.createElement( 'canvas' );
+		canvas.width = width;
+		canvas.height = height;
+
+		var context = canvas.getContext( '2d' );
+		context.drawImage( image, 0, 0 );
+
+		var data = context.getImageData( 0, 0, width, height ).data;
+		var imageData = context.createImageData( width, height );
+		var output = imageData.data;
+
+		for ( var x = 0; x < width; x ++ ) {
+
+			for ( var y = 0; y < height; y ++ ) {
+
+				var ly = y - 1 < 0 ? 0 : y - 1;
+				var uy = y + 1 > height - 1 ? height - 1 : y + 1;
+				var lx = x - 1 < 0 ? 0 : x - 1;
+				var ux = x + 1 > width - 1 ? width - 1 : x + 1;
+
+				var points = [];
+				var origin = [ 0, 0, data[ ( y * width + x ) * 4 ] / 255 * depth ];
+				points.push( [ - 1, 0, data[ ( y * width + lx ) * 4 ] / 255 * depth ] );
+				points.push( [ - 1, - 1, data[ ( ly * width + lx ) * 4 ] / 255 * depth ] );
+				points.push( [ 0, - 1, data[ ( ly * width + x ) * 4 ] / 255 * depth ] );
+				points.push( [  1, - 1, data[ ( ly * width + ux ) * 4 ] / 255 * depth ] );
+				points.push( [ 1, 0, data[ ( y * width + ux ) * 4 ] / 255 * depth ] );
+				points.push( [ 1, 1, data[ ( uy * width + ux ) * 4 ] / 255 * depth ] );
+				points.push( [ 0, 1, data[ ( uy * width + x ) * 4 ] / 255 * depth ] );
+				points.push( [ - 1, 1, data[ ( uy * width + lx ) * 4 ] / 255 * depth ] );
+
+				var normals = [];
+				var num_points = points.length;
+
+				for ( var i = 0; i < num_points; i ++ ) {
+
+					var v1 = points[ i ];
+					var v2 = points[ ( i + 1 ) % num_points ];
+					v1 = subtract( v1, origin );
+					v2 = subtract( v2, origin );
+					normals.push( normalize( cross( v1, v2 ) ) );
+
+				}
+
+				var normal = [ 0, 0, 0 ];
+
+				for ( var i = 0; i < normals.length; i ++ ) {
+
+					normal[ 0 ] += normals[ i ][ 0 ];
+					normal[ 1 ] += normals[ i ][ 1 ];
+					normal[ 2 ] += normals[ i ][ 2 ];
+
+				}
+
+				normal[ 0 ] /= normals.length;
+				normal[ 1 ] /= normals.length;
+				normal[ 2 ] /= normals.length;
+
+				var idx = ( y * width + x ) * 4;
+
+				output[ idx ] = ( ( normal[ 0 ] + 1.0 ) / 2.0 * 255 ) | 0;
+				output[ idx + 1 ] = ( ( normal[ 1 ] + 1.0 ) / 2.0 * 255 ) | 0;
+				output[ idx + 2 ] = ( normal[ 2 ] * 255 ) | 0;
+				output[ idx + 3 ] = 255;
+
+			}
+
+		}
+
+		context.putImageData( imageData, 0, 0 );
+
+		return canvas;
+
+	},
+
+	generateDataTexture: function ( width, height, color ) {
+
+		var size = width * height;
+		var data = new Uint8Array( 3 * size );
+
+		var r = Math.floor( color.r * 255 );
+		var g = Math.floor( color.g * 255 );
+		var b = Math.floor( color.b * 255 );
+
+		for ( var i = 0; i < size; i ++ ) {
+
+			data[ i * 3 ] 	  = r;
+			data[ i * 3 + 1 ] = g;
+			data[ i * 3 + 2 ] = b;
+
+		}
+
+		var texture = new THREE.DataTexture( data, width, height, THREE.RGBFormat );
+		texture.needsUpdate = true;
+
+		return texture;
+
+	}
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SceneUtils = {
+
+	createMultiMaterialObject: function ( geometry, materials ) {
+
+		var group = new THREE.Object3D();
+
+		for ( var i = 0, l = materials.length; i < l; i ++ ) {
+
+			group.add( new THREE.Mesh( geometry, materials[ i ] ) );
+
+		}
+
+		return group;
+
+	},
+
+	detach : function ( child, parent, scene ) {
+
+		child.applyMatrix( parent.matrixWorld );
+		parent.remove( child );
+		scene.add( child );
+
+	},
+
+	attach: function ( child, scene, parent ) {
+
+		var matrixWorldInverse = new THREE.Matrix4();
+		matrixWorldInverse.getInverse( parent.matrixWorld );
+		child.applyMatrix( matrixWorldInverse );
+
+		scene.remove( child );
+		parent.add( child );
+
+	}
+
+};
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * For Text operations in three.js (See TextGeometry)
+ *
+ * It uses techniques used in:
+ *
+ * 	typeface.js and canvastext
+ * 		For converting fonts and rendering with javascript
+ *		http://typeface.neocracy.org
+ *
+ *	Triangulation ported from AS3
+ *		Simple Polygon Triangulation
+ *		http://actionsnippet.com/?p=1462
+ *
+ * 	A Method to triangulate shapes with holes
+ *		http://www.sakri.net/blog/2009/06/12/an-approach-to-triangulating-polygons-with-holes/
+ *
+ */
+
+THREE.FontUtils = {
+
+	faces : {},
+
+	// Just for now. face[weight][style]
+
+	face : "helvetiker",
+	weight: "normal",
+	style : "normal",
+	size : 150,
+	divisions : 10,
+
+	getFace : function() {
+
+		return this.faces[ this.face ][ this.weight ][ this.style ];
+
+	},
+
+	loadFace : function( data ) {
+
+		var family = data.familyName.toLowerCase();
+
+		var ThreeFont = this;
+
+		ThreeFont.faces[ family ] = ThreeFont.faces[ family ] || {};
+
+		ThreeFont.faces[ family ][ data.cssFontWeight ] = ThreeFont.faces[ family ][ data.cssFontWeight ] || {};
+		ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data;
+
+		var face = ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data;
+
+		return data;
+
+	},
+
+	drawText : function( text ) {
+
+		var characterPts = [], allPts = [];
+
+		// RenderText
+
+		var i, p,
+			face = this.getFace(),
+			scale = this.size / face.resolution,
+			offset = 0,
+			chars = String( text ).split( '' ),
+			length = chars.length;
+
+		var fontPaths = [];
+
+		for ( i = 0; i < length; i ++ ) {
+
+			var path = new THREE.Path();
+
+			var ret = this.extractGlyphPoints( chars[ i ], face, scale, offset, path );
+			offset += ret.offset;
+
+			fontPaths.push( ret.path );
+
+		}
+
+		// get the width
+
+		var width = offset / 2;
+		//
+		// for ( p = 0; p < allPts.length; p++ ) {
+		//
+		// 	allPts[ p ].x -= width;
+		//
+		// }
+
+		//var extract = this.extractPoints( allPts, characterPts );
+		//extract.contour = allPts;
+
+		//extract.paths = fontPaths;
+		//extract.offset = width;
+
+		return { paths : fontPaths, offset : width };
+
+	},
+
+
+
+
+	extractGlyphPoints : function( c, face, scale, offset, path ) {
+
+		var pts = [];
+
+		var i, i2, divisions,
+			outline, action, length,
+			scaleX, scaleY,
+			x, y, cpx, cpy, cpx0, cpy0, cpx1, cpy1, cpx2, cpy2,
+			laste,
+			glyph = face.glyphs[ c ] || face.glyphs[ '?' ];
+
+		if ( !glyph ) return;
+
+		if ( glyph.o ) {
+
+			outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
+			length = outline.length;
+
+			scaleX = scale;
+			scaleY = scale;
+
+			for ( i = 0; i < length; ) {
+
+				action = outline[ i ++ ];
+
+				//console.log( action );
+
+				switch( action ) {
+
+				case 'm':
+
+					// Move To
+
+					x = outline[ i++ ] * scaleX + offset;
+					y = outline[ i++ ] * scaleY;
+
+					path.moveTo( x, y );
+					break;
+
+				case 'l':
+
+					// Line To
+
+					x = outline[ i++ ] * scaleX + offset;
+					y = outline[ i++ ] * scaleY;
+					path.lineTo(x,y);
+					break;
+
+				case 'q':
+
+					// QuadraticCurveTo
+
+					cpx  = outline[ i++ ] * scaleX + offset;
+					cpy  = outline[ i++ ] * scaleY;
+					cpx1 = outline[ i++ ] * scaleX + offset;
+					cpy1 = outline[ i++ ] * scaleY;
+
+					path.quadraticCurveTo(cpx1, cpy1, cpx, cpy);
+
+					laste = pts[ pts.length - 1 ];
+
+					if ( laste ) {
+
+						cpx0 = laste.x;
+						cpy0 = laste.y;
+
+						for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) {
+
+							var t = i2 / divisions;
+							var tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx );
+							var ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy );
+					  }
+
+				  }
+
+				  break;
+
+				case 'b':
+
+					// Cubic Bezier Curve
+
+					cpx  = outline[ i++ ] *  scaleX + offset;
+					cpy  = outline[ i++ ] *  scaleY;
+					cpx1 = outline[ i++ ] *  scaleX + offset;
+					cpy1 = outline[ i++ ] * -scaleY;
+					cpx2 = outline[ i++ ] *  scaleX + offset;
+					cpy2 = outline[ i++ ] * -scaleY;
+
+					path.bezierCurveTo( cpx, cpy, cpx1, cpy1, cpx2, cpy2 );
+
+					laste = pts[ pts.length - 1 ];
+
+					if ( laste ) {
+
+						cpx0 = laste.x;
+						cpy0 = laste.y;
+
+						for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) {
+
+							var t = i2 / divisions;
+							var tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx );
+							var ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy );
+
+						}
+
+					}
+
+					break;
+
+				}
+
+			}
+		}
+
+
+
+		return { offset: glyph.ha*scale, path:path};
+	}
+
+};
+
+
+THREE.FontUtils.generateShapes = function( text, parameters ) {
+
+	// Parameters 
+
+	parameters = parameters || {};
+
+	var size = parameters.size !== undefined ? parameters.size : 100;
+	var curveSegments = parameters.curveSegments !== undefined ? parameters.curveSegments: 4;
+
+	var font = parameters.font !== undefined ? parameters.font : "helvetiker";
+	var weight = parameters.weight !== undefined ? parameters.weight : "normal";
+	var style = parameters.style !== undefined ? parameters.style : "normal";
+
+	THREE.FontUtils.size = size;
+	THREE.FontUtils.divisions = curveSegments;
+
+	THREE.FontUtils.face = font;
+	THREE.FontUtils.weight = weight;
+	THREE.FontUtils.style = style;
+
+	// Get a Font data json object
+
+	var data = THREE.FontUtils.drawText( text );
+
+	var paths = data.paths;
+	var shapes = [];
+
+	for ( var p = 0, pl = paths.length; p < pl; p ++ ) {
+
+		Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
+
+	}
+
+	return shapes;
+
+};
+
+
+/**
+ * This code is a quick port of code written in C++ which was submitted to
+ * flipcode.com by John W. Ratcliff  // July 22, 2000
+ * See original code and more information here:
+ * http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml
+ *
+ * ported to actionscript by Zevan Rosser
+ * www.actionsnippet.com
+ *
+ * ported to javascript by Joshua Koo
+ * http://www.lab4games.net/zz85/blog
+ *
+ */
+
+
+( function( namespace ) {
+
+	var EPSILON = 0.0000000001;
+
+	// takes in an contour array and returns
+
+	var process = function( contour, indices ) {
+
+		var n = contour.length;
+
+		if ( n < 3 ) return null;
+
+		var result = [],
+			verts = [],
+			vertIndices = [];
+
+		/* we want a counter-clockwise polygon in verts */
+
+		var u, v, w;
+
+		if ( area( contour ) > 0.0 ) {
+
+			for ( v = 0; v < n; v++ ) verts[ v ] = v;
+
+		} else {
+
+			for ( v = 0; v < n; v++ ) verts[ v ] = ( n - 1 ) - v;
+
+		}
+
+		var nv = n;
+
+		/*  remove nv - 2 vertices, creating 1 triangle every time */
+
+		var count = 2 * nv;   /* error detection */
+
+		for( v = nv - 1; nv > 2; ) {
+
+			/* if we loop, it is probably a non-simple polygon */
+
+			if ( ( count-- ) <= 0 ) {
+
+				//** Triangulate: ERROR - probable bad polygon!
+
+				//throw ( "Warning, unable to triangulate polygon!" );
+				//return null;
+				// Sometimes warning is fine, especially polygons are triangulated in reverse.
+				console.log( "Warning, unable to triangulate polygon!" );
+
+				if ( indices ) return vertIndices;
+				return result;
+
+			}
+
+			/* three consecutive vertices in current polygon, <u,v,w> */
+
+			u = v; 	 	if ( nv <= u ) u = 0;     /* previous */
+			v = u + 1;  if ( nv <= v ) v = 0;     /* new v    */
+			w = v + 1;  if ( nv <= w ) w = 0;     /* next     */
+
+			if ( snip( contour, u, v, w, nv, verts ) ) {
+
+				var a, b, c, s, t;
+
+				/* true names of the vertices */
+
+				a = verts[ u ];
+				b = verts[ v ];
+				c = verts[ w ];
+
+				/* output Triangle */
+
+				result.push( [ contour[ a ],
+					contour[ b ],
+					contour[ c ] ] );
+
+
+				vertIndices.push( [ verts[ u ], verts[ v ], verts[ w ] ] );
+
+				/* remove v from the remaining polygon */
+
+				for( s = v, t = v + 1; t < nv; s++, t++ ) {
+
+					verts[ s ] = verts[ t ];
+
+				}
+
+				nv--;
+
+				/* reset error detection counter */
+
+				count = 2 * nv;
+
+			}
+
+		}
+
+		if ( indices ) return vertIndices;
+		return result;
+
+	};
+
+	// calculate area of the contour polygon
+
+	var area = function ( contour ) {
+
+		var n = contour.length;
+		var a = 0.0;
+
+		for( var p = n - 1, q = 0; q < n; p = q++ ) {
+
+			a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
+
+		}
+
+		return a * 0.5;
+
+	};
+
+	var snip = function ( contour, u, v, w, n, verts ) {
+
+		var p;
+		var ax, ay, bx, by;
+		var cx, cy, px, py;
+
+		ax = contour[ verts[ u ] ].x;
+		ay = contour[ verts[ u ] ].y;
+
+		bx = contour[ verts[ v ] ].x;
+		by = contour[ verts[ v ] ].y;
+
+		cx = contour[ verts[ w ] ].x;
+		cy = contour[ verts[ w ] ].y;
+
+		if ( EPSILON > (((bx-ax)*(cy-ay)) - ((by-ay)*(cx-ax))) ) return false;
+
+		var aX, aY, bX, bY, cX, cY;
+		var apx, apy, bpx, bpy, cpx, cpy;
+		var cCROSSap, bCROSScp, aCROSSbp;
+
+		aX = cx - bx;  aY = cy - by;
+		bX = ax - cx;  bY = ay - cy;
+		cX = bx - ax;  cY = by - ay;
+
+		for ( p = 0; p < n; p++ ) {
+
+			if( (p === u) || (p === v) || (p === w) ) continue;
+
+			px = contour[ verts[ p ] ].x
+			py = contour[ verts[ p ] ].y
+
+			apx = px - ax;  apy = py - ay;
+			bpx = px - bx;  bpy = py - by;
+			cpx = px - cx;  cpy = py - cy;
+
+			// see if p is inside triangle abc
+
+			aCROSSbp = aX*bpy - aY*bpx;
+			cCROSSap = cX*apy - cY*apx;
+			bCROSScp = bX*cpy - bY*cpx;
+
+			if ( (aCROSSbp >= 0.0) && (bCROSScp >= 0.0) && (cCROSSap >= 0.0) ) return false;
+
+		}
+
+		return true;
+
+	};
+
+
+	namespace.Triangulate = process;
+	namespace.Triangulate.area = area;
+
+	return namespace;
+
+})(THREE.FontUtils);
+
+// To use the typeface.js face files, hook up the API
+self._typeface_js = { faces: THREE.FontUtils.faces, loadFace: THREE.FontUtils.loadFace };
+THREE.typeface_js = self._typeface_js;
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * Extensible curve object
+ *
+ * Some common of Curve methods
+ * .getPoint(t), getTangent(t)
+ * .getPointAt(u), getTagentAt(u)
+ * .getPoints(), .getSpacedPoints()
+ * .getLength()
+ * .updateArcLengths()
+ *
+ * This file contains following classes:
+ *
+ * -- 2d classes --
+ * THREE.Curve
+ * THREE.LineCurve
+ * THREE.QuadraticBezierCurve
+ * THREE.CubicBezierCurve
+ * THREE.SplineCurve
+ * THREE.ArcCurve
+ * THREE.EllipseCurve
+ *
+ * -- 3d classes --
+ * THREE.LineCurve3
+ * THREE.QuadraticBezierCurve3
+ * THREE.CubicBezierCurve3
+ * THREE.SplineCurve3
+ * THREE.ClosedSplineCurve3
+ *
+ * A series of curves can be represented as a THREE.CurvePath
+ *
+ **/
+
+/**************************************************************
+ *	Abstract Curve base class
+ **************************************************************/
+
+THREE.Curve = function () {
+
+};
+
+// Virtual base class method to overwrite and implement in subclasses
+//	- t [0 .. 1]
+
+THREE.Curve.prototype.getPoint = function ( t ) {
+
+	console.log( "Warning, getPoint() not implemented!" );
+	return null;
+
+};
+
+// Get point at relative position in curve according to arc length
+// - u [0 .. 1]
+
+THREE.Curve.prototype.getPointAt = function ( u ) {
+
+	var t = this.getUtoTmapping( u );
+	return this.getPoint( t );
+
+};
+
+// Get sequence of points using getPoint( t )
+
+THREE.Curve.prototype.getPoints = function ( divisions ) {
+
+	if ( !divisions ) divisions = 5;
+
+	var d, pts = [];
+
+	for ( d = 0; d <= divisions; d ++ ) {
+
+		pts.push( this.getPoint( d / divisions ) );
+
+	}
+
+	return pts;
+
+};
+
+// Get sequence of points using getPointAt( u )
+
+THREE.Curve.prototype.getSpacedPoints = function ( divisions ) {
+
+	if ( !divisions ) divisions = 5;
+
+	var d, pts = [];
+
+	for ( d = 0; d <= divisions; d ++ ) {
+
+		pts.push( this.getPointAt( d / divisions ) );
+
+	}
+
+	return pts;
+
+};
+
+// Get total curve arc length
+
+THREE.Curve.prototype.getLength = function () {
+
+	var lengths = this.getLengths();
+	return lengths[ lengths.length - 1 ];
+
+};
+
+// Get list of cumulative segment lengths
+
+THREE.Curve.prototype.getLengths = function ( divisions ) {
+
+	if ( !divisions ) divisions = (this.__arcLengthDivisions) ? (this.__arcLengthDivisions): 200;
+
+	if ( this.cacheArcLengths
+		&& ( this.cacheArcLengths.length == divisions + 1 )
+		&& !this.needsUpdate) {
+
+		//console.log( "cached", this.cacheArcLengths );
+		return this.cacheArcLengths;
+
+	}
+
+	this.needsUpdate = false;
+
+	var cache = [];
+	var current, last = this.getPoint( 0 );
+	var p, sum = 0;
+
+	cache.push( 0 );
+
+	for ( p = 1; p <= divisions; p ++ ) {
+
+		current = this.getPoint ( p / divisions );
+		sum += current.distanceTo( last );
+		cache.push( sum );
+		last = current;
+
+	}
+
+	this.cacheArcLengths = cache;
+
+	return cache; // { sums: cache, sum:sum }; Sum is in the last element.
+
+};
+
+
+THREE.Curve.prototype.updateArcLengths = function() {
+	this.needsUpdate = true;
+	this.getLengths();
+};
+
+// Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equi distance
+
+THREE.Curve.prototype.getUtoTmapping = function ( u, distance ) {
+
+	var arcLengths = this.getLengths();
+
+	var i = 0, il = arcLengths.length;
+
+	var targetArcLength; // The targeted u distance value to get
+
+	if ( distance ) {
+
+		targetArcLength = distance;
+
+	} else {
+
+		targetArcLength = u * arcLengths[ il - 1 ];
+
+	}
+
+	//var time = Date.now();
+
+	// binary search for the index with largest value smaller than target u distance
+
+	var low = 0, high = il - 1, comparison;
+
+	while ( low <= high ) {
+
+		i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats
+
+		comparison = arcLengths[ i ] - targetArcLength;
+
+		if ( comparison < 0 ) {
+
+			low = i + 1;
+			continue;
+
+		} else if ( comparison > 0 ) {
+
+			high = i - 1;
+			continue;
+
+		} else {
+
+			high = i;
+			break;
+
+			// DONE
+
+		}
+
+	}
+
+	i = high;
+
+	//console.log('b' , i, low, high, Date.now()- time);
+
+	if ( arcLengths[ i ] == targetArcLength ) {
+
+		var t = i / ( il - 1 );
+		return t;
+
+	}
+
+	// we could get finer grain at lengths, or use simple interpolatation between two points
+
+	var lengthBefore = arcLengths[ i ];
+    var lengthAfter = arcLengths[ i + 1 ];
+
+    var segmentLength = lengthAfter - lengthBefore;
+
+    // determine where we are between the 'before' and 'after' points
+
+    var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
+
+    // add that fractional amount to t
+
+    var t = ( i + segmentFraction ) / ( il -1 );
+
+	return t;
+
+};
+
+// Returns a unit vector tangent at t
+// In case any sub curve does not implement its tangent derivation,
+// 2 points a small delta apart will be used to find its gradient
+// which seems to give a reasonable approximation
+
+THREE.Curve.prototype.getTangent = function( t ) {
+
+	var delta = 0.0001;
+	var t1 = t - delta;
+	var t2 = t + delta;
+
+	// Capping in case of danger
+
+	if ( t1 < 0 ) t1 = 0;
+	if ( t2 > 1 ) t2 = 1;
+
+	var pt1 = this.getPoint( t1 );
+	var pt2 = this.getPoint( t2 );
+
+	var vec = pt2.clone().sub(pt1);
+	return vec.normalize();
+
+};
+
+
+THREE.Curve.prototype.getTangentAt = function ( u ) {
+
+	var t = this.getUtoTmapping( u );
+	return this.getTangent( t );
+
+};
+
+/**************************************************************
+ *	Line
+ **************************************************************/
+
+THREE.LineCurve = function ( v1, v2 ) {
+
+	this.v1 = v1;
+	this.v2 = v2;
+
+};
+
+THREE.LineCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.LineCurve.prototype.getPoint = function ( t ) {
+
+	var point = this.v2.clone().sub(this.v1);
+	point.multiplyScalar( t ).add( this.v1 );
+
+	return point;
+
+};
+
+// Line curve is linear, so we can overwrite default getPointAt
+
+THREE.LineCurve.prototype.getPointAt = function ( u ) {
+
+	return this.getPoint( u );
+
+};
+
+THREE.LineCurve.prototype.getTangent = function( t ) {
+
+	var tangent = this.v2.clone().sub(this.v1);
+
+	return tangent.normalize();
+
+};
+
+/**************************************************************
+ *	Quadratic Bezier curve
+ **************************************************************/
+
+
+THREE.QuadraticBezierCurve = function ( v0, v1, v2 ) {
+
+	this.v0 = v0;
+	this.v1 = v1;
+	this.v2 = v2;
+
+};
+
+THREE.QuadraticBezierCurve.prototype = Object.create( THREE.Curve.prototype );
+
+
+THREE.QuadraticBezierCurve.prototype.getPoint = function ( t ) {
+
+	var tx, ty;
+
+	tx = THREE.Shape.Utils.b2( t, this.v0.x, this.v1.x, this.v2.x );
+	ty = THREE.Shape.Utils.b2( t, this.v0.y, this.v1.y, this.v2.y );
+
+	return new THREE.Vector2( tx, ty );
+
+};
+
+
+THREE.QuadraticBezierCurve.prototype.getTangent = function( t ) {
+
+	var tx, ty;
+
+	tx = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.x, this.v1.x, this.v2.x );
+	ty = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.y, this.v1.y, this.v2.y );
+
+	// returns unit vector
+
+	var tangent = new THREE.Vector2( tx, ty );
+	tangent.normalize();
+
+	return tangent;
+
+};
+
+
+/**************************************************************
+ *	Cubic Bezier curve
+ **************************************************************/
+
+THREE.CubicBezierCurve = function ( v0, v1, v2, v3 ) {
+
+	this.v0 = v0;
+	this.v1 = v1;
+	this.v2 = v2;
+	this.v3 = v3;
+
+};
+
+THREE.CubicBezierCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.CubicBezierCurve.prototype.getPoint = function ( t ) {
+
+	var tx, ty;
+
+	tx = THREE.Shape.Utils.b3( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x );
+	ty = THREE.Shape.Utils.b3( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y );
+
+	return new THREE.Vector2( tx, ty );
+
+};
+
+THREE.CubicBezierCurve.prototype.getTangent = function( t ) {
+
+	var tx, ty;
+
+	tx = THREE.Curve.Utils.tangentCubicBezier( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x );
+	ty = THREE.Curve.Utils.tangentCubicBezier( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y );
+
+	var tangent = new THREE.Vector2( tx, ty );
+	tangent.normalize();
+
+	return tangent;
+
+};
+
+
+/**************************************************************
+ *	Spline curve
+ **************************************************************/
+
+THREE.SplineCurve = function ( points /* array of Vector2 */ ) {
+
+	this.points = (points == undefined) ? [] : points;
+
+};
+
+THREE.SplineCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.SplineCurve.prototype.getPoint = function ( t ) {
+
+	var v = new THREE.Vector2();
+	var c = [];
+	var points = this.points, point, intPoint, weight;
+	point = ( points.length - 1 ) * t;
+
+	intPoint = Math.floor( point );
+	weight = point - intPoint;
+
+	c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1;
+	c[ 1 ] = intPoint;
+	c[ 2 ] = intPoint  > points.length - 2 ? points.length -1 : intPoint + 1;
+	c[ 3 ] = intPoint  > points.length - 3 ? points.length -1 : intPoint + 2;
+
+	v.x = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].x, points[ c[ 1 ] ].x, points[ c[ 2 ] ].x, points[ c[ 3 ] ].x, weight );
+	v.y = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].y, points[ c[ 1 ] ].y, points[ c[ 2 ] ].y, points[ c[ 3 ] ].y, weight );
+
+	return v;
+
+};
+
+/**************************************************************
+ *	Ellipse curve
+ **************************************************************/
+
+THREE.EllipseCurve = function ( aX, aY, xRadius, yRadius,
+							aStartAngle, aEndAngle,
+							aClockwise ) {
+
+	this.aX = aX;
+	this.aY = aY;
+
+	this.xRadius = xRadius;
+	this.yRadius = yRadius;
+
+	this.aStartAngle = aStartAngle;
+	this.aEndAngle = aEndAngle;
+
+	this.aClockwise = aClockwise;
+
+};
+
+THREE.EllipseCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.EllipseCurve.prototype.getPoint = function ( t ) {
+
+	var deltaAngle = this.aEndAngle - this.aStartAngle;
+
+	if ( !this.aClockwise ) {
+
+		t = 1 - t;
+
+	}
+
+	var angle = this.aStartAngle + t * deltaAngle;
+
+	var tx = this.aX + this.xRadius * Math.cos( angle );
+	var ty = this.aY + this.yRadius * Math.sin( angle );
+
+	return new THREE.Vector2( tx, ty );
+
+};
+
+/**************************************************************
+ *	Arc curve
+ **************************************************************/
+
+THREE.ArcCurve = function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+	THREE.EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+};
+
+THREE.ArcCurve.prototype = Object.create( THREE.EllipseCurve.prototype );
+
+
+/**************************************************************
+ *	Utils
+ **************************************************************/
+
+THREE.Curve.Utils = {
+
+	tangentQuadraticBezier: function ( t, p0, p1, p2 ) {
+
+		return 2 * ( 1 - t ) * ( p1 - p0 ) + 2 * t * ( p2 - p1 );
+
+	},
+
+	// Puay Bing, thanks for helping with this derivative!
+
+	tangentCubicBezier: function (t, p0, p1, p2, p3 ) {
+
+		return -3 * p0 * (1 - t) * (1 - t)  +
+			3 * p1 * (1 - t) * (1-t) - 6 *t *p1 * (1-t) +
+			6 * t *  p2 * (1-t) - 3 * t * t * p2 +
+			3 * t * t * p3;
+	},
+
+
+	tangentSpline: function ( t, p0, p1, p2, p3 ) {
+
+		// To check if my formulas are correct
+
+		var h00 = 6 * t * t - 6 * t; 	// derived from 2t^3 − 3t^2 + 1
+		var h10 = 3 * t * t - 4 * t + 1; // t^3 − 2t^2 + t
+		var h01 = -6 * t * t + 6 * t; 	// − 2t3 + 3t2
+		var h11 = 3 * t * t - 2 * t;	// t3 − t2
+
+		return h00 + h10 + h01 + h11;
+
+	},
+
+	// Catmull-Rom
+
+	interpolate: function( p0, p1, p2, p3, t ) {
+
+		var v0 = ( p2 - p0 ) * 0.5;
+		var v1 = ( p3 - p1 ) * 0.5;
+		var t2 = t * t;
+		var t3 = t * t2;
+		return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+	}
+
+};
+
+
+// TODO: Transformation for Curves?
+
+/**************************************************************
+ *	3D Curves
+ **************************************************************/
+
+// A Factory method for creating new curve subclasses
+
+THREE.Curve.create = function ( constructor, getPointFunc ) {
+
+	constructor.prototype = Object.create( THREE.Curve.prototype );
+	constructor.prototype.getPoint = getPointFunc;
+
+	return constructor;
+
+};
+
+
+/**************************************************************
+ *	Line3D
+ **************************************************************/
+
+THREE.LineCurve3 = THREE.Curve.create(
+
+	function ( v1, v2 ) {
+
+		this.v1 = v1;
+		this.v2 = v2;
+
+	},
+
+	function ( t ) {
+
+		var r = new THREE.Vector3();
+
+
+		r.subVectors( this.v2, this.v1 ); // diff
+		r.multiplyScalar( t );
+		r.add( this.v1 );
+
+		return r;
+
+	}
+
+);
+
+
+/**************************************************************
+ *	Quadratic Bezier 3D curve
+ **************************************************************/
+
+THREE.QuadraticBezierCurve3 = THREE.Curve.create(
+
+	function ( v0, v1, v2 ) {
+
+		this.v0 = v0;
+		this.v1 = v1;
+		this.v2 = v2;
+
+	},
+
+	function ( t ) {
+
+		var tx, ty, tz;
+
+		tx = THREE.Shape.Utils.b2( t, this.v0.x, this.v1.x, this.v2.x );
+		ty = THREE.Shape.Utils.b2( t, this.v0.y, this.v1.y, this.v2.y );
+		tz = THREE.Shape.Utils.b2( t, this.v0.z, this.v1.z, this.v2.z );
+
+		return new THREE.Vector3( tx, ty, tz );
+
+	}
+
+);
+
+
+
+/**************************************************************
+ *	Cubic Bezier 3D curve
+ **************************************************************/
+
+THREE.CubicBezierCurve3 = THREE.Curve.create(
+
+	function ( v0, v1, v2, v3 ) {
+
+		this.v0 = v0;
+		this.v1 = v1;
+		this.v2 = v2;
+		this.v3 = v3;
+
+	},
+
+	function ( t ) {
+
+		var tx, ty, tz;
+
+		tx = THREE.Shape.Utils.b3( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x );
+		ty = THREE.Shape.Utils.b3( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y );
+		tz = THREE.Shape.Utils.b3( t, this.v0.z, this.v1.z, this.v2.z, this.v3.z );
+
+		return new THREE.Vector3( tx, ty, tz );
+
+	}
+
+);
+
+
+
+/**************************************************************
+ *	Spline 3D curve
+ **************************************************************/
+
+
+THREE.SplineCurve3 = THREE.Curve.create(
+
+	function ( points /* array of Vector3 */) {
+
+		this.points = (points == undefined) ? [] : points;
+
+	},
+
+	function ( t ) {
+
+		var v = new THREE.Vector3();
+		var c = [];
+		var points = this.points, point, intPoint, weight;
+		point = ( points.length - 1 ) * t;
+
+		intPoint = Math.floor( point );
+		weight = point - intPoint;
+
+		c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1;
+		c[ 1 ] = intPoint;
+		c[ 2 ] = intPoint  > points.length - 2 ? points.length - 1 : intPoint + 1;
+		c[ 3 ] = intPoint  > points.length - 3 ? points.length - 1 : intPoint + 2;
+
+		var pt0 = points[ c[0] ],
+			pt1 = points[ c[1] ],
+			pt2 = points[ c[2] ],
+			pt3 = points[ c[3] ];
+
+		v.x = THREE.Curve.Utils.interpolate(pt0.x, pt1.x, pt2.x, pt3.x, weight);
+		v.y = THREE.Curve.Utils.interpolate(pt0.y, pt1.y, pt2.y, pt3.y, weight);
+		v.z = THREE.Curve.Utils.interpolate(pt0.z, pt1.z, pt2.z, pt3.z, weight);
+
+		return v;
+
+	}
+
+);
+
+
+// THREE.SplineCurve3.prototype.getTangent = function(t) {
+// 		var v = new THREE.Vector3();
+// 		var c = [];
+// 		var points = this.points, point, intPoint, weight;
+// 		point = ( points.length - 1 ) * t;
+
+// 		intPoint = Math.floor( point );
+// 		weight = point - intPoint;
+
+// 		c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1;
+// 		c[ 1 ] = intPoint;
+// 		c[ 2 ] = intPoint  > points.length - 2 ? points.length - 1 : intPoint + 1;
+// 		c[ 3 ] = intPoint  > points.length - 3 ? points.length - 1 : intPoint + 2;
+
+// 		var pt0 = points[ c[0] ],
+// 			pt1 = points[ c[1] ],
+// 			pt2 = points[ c[2] ],
+// 			pt3 = points[ c[3] ];
+
+// 	// t = weight;
+// 	v.x = THREE.Curve.Utils.tangentSpline( t, pt0.x, pt1.x, pt2.x, pt3.x );
+// 	v.y = THREE.Curve.Utils.tangentSpline( t, pt0.y, pt1.y, pt2.y, pt3.y );
+// 	v.z = THREE.Curve.Utils.tangentSpline( t, pt0.z, pt1.z, pt2.z, pt3.z );
+
+// 	return v;
+
+// }
+
+/**************************************************************
+ *	Closed Spline 3D curve
+ **************************************************************/
+
+
+THREE.ClosedSplineCurve3 = THREE.Curve.create(
+
+	function ( points /* array of Vector3 */) {
+
+		this.points = (points == undefined) ? [] : points;
+
+	},
+
+    function ( t ) {
+
+        var v = new THREE.Vector3();
+        var c = [];
+        var points = this.points, point, intPoint, weight;
+        point = ( points.length - 0 ) * t;
+            // This needs to be from 0-length +1
+
+        intPoint = Math.floor( point );
+        weight = point - intPoint;
+
+        intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length;
+        c[ 0 ] = ( intPoint - 1 ) % points.length;
+        c[ 1 ] = ( intPoint ) % points.length;
+        c[ 2 ] = ( intPoint + 1 ) % points.length;
+        c[ 3 ] = ( intPoint + 2 ) % points.length;
+
+        v.x = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].x, points[ c[ 1 ] ].x, points[ c[ 2 ] ].x, points[ c[ 3 ] ].x, weight );
+        v.y = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].y, points[ c[ 1 ] ].y, points[ c[ 2 ] ].y, points[ c[ 3 ] ].y, weight );
+        v.z = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].z, points[ c[ 1 ] ].z, points[ c[ 2 ] ].z, points[ c[ 3 ] ].z, weight );
+
+        return v;
+
+    }
+
+);
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ *
+ **/
+
+/**************************************************************
+ *	Curved Path - a curve path is simply a array of connected
+ *  curves, but retains the api of a curve
+ **************************************************************/
+
+THREE.CurvePath = function () {
+
+	this.curves = [];
+	this.bends = [];
+	
+	this.autoClose = false; // Automatically closes the path
+};
+
+THREE.CurvePath.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.CurvePath.prototype.add = function ( curve ) {
+
+	this.curves.push( curve );
+
+};
+
+THREE.CurvePath.prototype.checkConnection = function() {
+	// TODO
+	// If the ending of curve is not connected to the starting
+	// or the next curve, then, this is not a real path
+};
+
+THREE.CurvePath.prototype.closePath = function() {
+	// TODO Test
+	// and verify for vector3 (needs to implement equals)
+	// Add a line curve if start and end of lines are not connected
+	var startPoint = this.curves[0].getPoint(0);
+	var endPoint = this.curves[this.curves.length-1].getPoint(1);
+	
+	if (!startPoint.equals(endPoint)) {
+		this.curves.push( new THREE.LineCurve(endPoint, startPoint) );
+	}
+	
+};
+
+// To get accurate point with reference to
+// entire path distance at time t,
+// following has to be done:
+
+// 1. Length of each sub path have to be known
+// 2. Locate and identify type of curve
+// 3. Get t for the curve
+// 4. Return curve.getPointAt(t')
+
+THREE.CurvePath.prototype.getPoint = function( t ) {
+
+	var d = t * this.getLength();
+	var curveLengths = this.getCurveLengths();
+	var i = 0, diff, curve;
+
+	// To think about boundaries points.
+
+	while ( i < curveLengths.length ) {
+
+		if ( curveLengths[ i ] >= d ) {
+
+			diff = curveLengths[ i ] - d;
+			curve = this.curves[ i ];
+
+			var u = 1 - diff / curve.getLength();
+
+			return curve.getPointAt( u );
+
+			break;
+		}
+
+		i ++;
+
+	}
+
+	return null;
+
+	// loop where sum != 0, sum > d , sum+1 <d
+
+};
+
+/*
+THREE.CurvePath.prototype.getTangent = function( t ) {
+};*/
+
+
+// We cannot use the default THREE.Curve getPoint() with getLength() because in
+// THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath
+// getPoint() depends on getLength
+
+THREE.CurvePath.prototype.getLength = function() {
+
+	var lens = this.getCurveLengths();
+	return lens[ lens.length - 1 ];
+
+};
+
+// Compute lengths and cache them
+// We cannot overwrite getLengths() because UtoT mapping uses it.
+
+THREE.CurvePath.prototype.getCurveLengths = function() {
+
+	// We use cache values if curves and cache array are same length
+
+	if ( this.cacheLengths && this.cacheLengths.length == this.curves.length ) {
+
+		return this.cacheLengths;
+
+	};
+
+	// Get length of subsurve
+	// Push sums into cached array
+
+	var lengths = [], sums = 0;
+	var i, il = this.curves.length;
+
+	for ( i = 0; i < il; i ++ ) {
+
+		sums += this.curves[ i ].getLength();
+		lengths.push( sums );
+
+	}
+
+	this.cacheLengths = lengths;
+
+	return lengths;
+
+};
+
+
+
+// Returns min and max coordinates, as well as centroid
+
+THREE.CurvePath.prototype.getBoundingBox = function () {
+
+	var points = this.getPoints();
+
+	var maxX, maxY, maxZ;
+	var minX, minY, minZ;
+
+	maxX = maxY = Number.NEGATIVE_INFINITY;
+	minX = minY = Number.POSITIVE_INFINITY;
+
+	var p, i, il, sum;
+
+	var v3 = points[0] instanceof THREE.Vector3;
+
+	sum = v3 ? new THREE.Vector3() : new THREE.Vector2();
+
+	for ( i = 0, il = points.length; i < il; i ++ ) {
+
+		p = points[ i ];
+
+		if ( p.x > maxX ) maxX = p.x;
+		else if ( p.x < minX ) minX = p.x;
+
+		if ( p.y > maxY ) maxY = p.y;
+		else if ( p.y < minY ) minY = p.y;
+
+		if ( v3 ) {
+
+			if ( p.z > maxZ ) maxZ = p.z;
+			else if ( p.z < minZ ) minZ = p.z;
+
+		}
+
+		sum.add( p );
+
+	}
+
+	var ret = {
+
+		minX: minX,
+		minY: minY,
+		maxX: maxX,
+		maxY: maxY,
+		centroid: sum.divideScalar( il )
+
+	};
+
+	if ( v3 ) {
+
+		ret.maxZ = maxZ;
+		ret.minZ = minZ;
+
+	}
+
+	return ret;
+
+};
+
+/**************************************************************
+ *	Create Geometries Helpers
+ **************************************************************/
+
+/// Generate geometry from path points (for Line or ParticleSystem objects)
+
+THREE.CurvePath.prototype.createPointsGeometry = function( divisions ) {
+
+	var pts = this.getPoints( divisions, true );
+	return this.createGeometry( pts );
+
+};
+
+// Generate geometry from equidistance sampling along the path
+
+THREE.CurvePath.prototype.createSpacedPointsGeometry = function( divisions ) {
+
+	var pts = this.getSpacedPoints( divisions, true );
+	return this.createGeometry( pts );
+
+};
+
+THREE.CurvePath.prototype.createGeometry = function( points ) {
+
+	var geometry = new THREE.Geometry();
+
+	for ( var i = 0; i < points.length; i ++ ) {
+
+		geometry.vertices.push( new THREE.Vector3( points[ i ].x, points[ i ].y, points[ i ].z || 0) );
+
+	}
+
+	return geometry;
+
+};
+
+
+/**************************************************************
+ *	Bend / Wrap Helper Methods
+ **************************************************************/
+
+// Wrap path / Bend modifiers?
+
+THREE.CurvePath.prototype.addWrapPath = function ( bendpath ) {
+
+	this.bends.push( bendpath );
+
+};
+
+THREE.CurvePath.prototype.getTransformedPoints = function( segments, bends ) {
+
+	var oldPts = this.getPoints( segments ); // getPoints getSpacedPoints
+	var i, il;
+
+	if ( !bends ) {
+
+		bends = this.bends;
+
+	}
+
+	for ( i = 0, il = bends.length; i < il; i ++ ) {
+
+		oldPts = this.getWrapPoints( oldPts, bends[ i ] );
+
+	}
+
+	return oldPts;
+
+};
+
+THREE.CurvePath.prototype.getTransformedSpacedPoints = function( segments, bends ) {
+
+	var oldPts = this.getSpacedPoints( segments );
+
+	var i, il;
+
+	if ( !bends ) {
+
+		bends = this.bends;
+
+	}
+
+	for ( i = 0, il = bends.length; i < il; i ++ ) {
+
+		oldPts = this.getWrapPoints( oldPts, bends[ i ] );
+
+	}
+
+	return oldPts;
+
+};
+
+// This returns getPoints() bend/wrapped around the contour of a path.
+// Read http://www.planetclegg.com/projects/WarpingTextToSplines.html
+
+THREE.CurvePath.prototype.getWrapPoints = function ( oldPts, path ) {
+
+	var bounds = this.getBoundingBox();
+
+	var i, il, p, oldX, oldY, xNorm;
+
+	for ( i = 0, il = oldPts.length; i < il; i ++ ) {
+
+		p = oldPts[ i ];
+
+		oldX = p.x;
+		oldY = p.y;
+
+		xNorm = oldX / bounds.maxX;
+
+		// If using actual distance, for length > path, requires line extrusions
+		//xNorm = path.getUtoTmapping(xNorm, oldX); // 3 styles. 1) wrap stretched. 2) wrap stretch by arc length 3) warp by actual distance
+
+		xNorm = path.getUtoTmapping( xNorm, oldX );
+
+		// check for out of bounds?
+
+		var pathPt = path.getPoint( xNorm );
+		var normal = path.getNormalVector( xNorm ).multiplyScalar( oldY );
+
+		p.x = pathPt.x + normal.x;
+		p.y = pathPt.y + normal.y;
+
+	}
+
+	return oldPts;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Gyroscope = function () {
+
+	THREE.Object3D.call( this );
+
+};
+
+THREE.Gyroscope.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Gyroscope.prototype.updateMatrixWorld = function ( force ) {
+
+	this.matrixAutoUpdate && this.updateMatrix();
+
+	// update matrixWorld
+
+	if ( this.matrixWorldNeedsUpdate || force ) {
+
+		if ( this.parent ) {
+
+			this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+			this.matrixWorld.decompose( this.translationWorld, this.rotationWorld, this.scaleWorld );
+			this.matrix.decompose( this.translationObject, this.rotationObject, this.scaleObject );
+
+			this.matrixWorld.makeFromPositionQuaternionScale( this.translationWorld, this.rotationObject, this.scaleWorld );
+
+
+		} else {
+
+			this.matrixWorld.copy( this.matrix );
+
+		}
+
+
+		this.matrixWorldNeedsUpdate = false;
+
+		force = true;
+
+	}
+
+	// update children
+
+	for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+		this.children[ i ].updateMatrixWorld( force );
+
+	}
+
+};
+
+THREE.Gyroscope.prototype.translationWorld = new THREE.Vector3();
+THREE.Gyroscope.prototype.translationObject = new THREE.Vector3();
+THREE.Gyroscope.prototype.rotationWorld = new THREE.Quaternion();
+THREE.Gyroscope.prototype.rotationObject = new THREE.Quaternion();
+THREE.Gyroscope.prototype.scaleWorld = new THREE.Vector3();
+THREE.Gyroscope.prototype.scaleObject = new THREE.Vector3();
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * Creates free form 2d path using series of points, lines or curves.
+ *
+ **/
+
+THREE.Path = function ( points ) {
+
+	THREE.CurvePath.call(this);
+
+	this.actions = [];
+
+	if ( points ) {
+
+		this.fromPoints( points );
+
+	}
+
+};
+
+THREE.Path.prototype = Object.create( THREE.CurvePath.prototype );
+
+THREE.PathActions = {
+
+	MOVE_TO: 'moveTo',
+	LINE_TO: 'lineTo',
+	QUADRATIC_CURVE_TO: 'quadraticCurveTo', // Bezier quadratic curve
+	BEZIER_CURVE_TO: 'bezierCurveTo', 		// Bezier cubic curve
+	CSPLINE_THRU: 'splineThru',				// Catmull-rom spline
+	ARC: 'arc',								// Circle
+	ELLIPSE: 'ellipse'
+};
+
+// TODO Clean up PATH API
+
+// Create path using straight lines to connect all points
+// - vectors: array of Vector2
+
+THREE.Path.prototype.fromPoints = function ( vectors ) {
+
+	this.moveTo( vectors[ 0 ].x, vectors[ 0 ].y );
+
+	for ( var v = 1, vlen = vectors.length; v < vlen; v ++ ) {
+
+		this.lineTo( vectors[ v ].x, vectors[ v ].y );
+
+	};
+
+};
+
+// startPath() endPath()?
+
+THREE.Path.prototype.moveTo = function ( x, y ) {
+
+	var args = Array.prototype.slice.call( arguments );
+	this.actions.push( { action: THREE.PathActions.MOVE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.lineTo = function ( x, y ) {
+
+	var args = Array.prototype.slice.call( arguments );
+
+	var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+	var x0 = lastargs[ lastargs.length - 2 ];
+	var y0 = lastargs[ lastargs.length - 1 ];
+
+	var curve = new THREE.LineCurve( new THREE.Vector2( x0, y0 ), new THREE.Vector2( x, y ) );
+	this.curves.push( curve );
+
+	this.actions.push( { action: THREE.PathActions.LINE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.quadraticCurveTo = function( aCPx, aCPy, aX, aY ) {
+
+	var args = Array.prototype.slice.call( arguments );
+
+	var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+	var x0 = lastargs[ lastargs.length - 2 ];
+	var y0 = lastargs[ lastargs.length - 1 ];
+
+	var curve = new THREE.QuadraticBezierCurve( new THREE.Vector2( x0, y0 ),
+												new THREE.Vector2( aCPx, aCPy ),
+												new THREE.Vector2( aX, aY ) );
+	this.curves.push( curve );
+
+	this.actions.push( { action: THREE.PathActions.QUADRATIC_CURVE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.bezierCurveTo = function( aCP1x, aCP1y,
+                                               aCP2x, aCP2y,
+                                               aX, aY ) {
+
+	var args = Array.prototype.slice.call( arguments );
+
+	var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+	var x0 = lastargs[ lastargs.length - 2 ];
+	var y0 = lastargs[ lastargs.length - 1 ];
+
+	var curve = new THREE.CubicBezierCurve( new THREE.Vector2( x0, y0 ),
+											new THREE.Vector2( aCP1x, aCP1y ),
+											new THREE.Vector2( aCP2x, aCP2y ),
+											new THREE.Vector2( aX, aY ) );
+	this.curves.push( curve );
+
+	this.actions.push( { action: THREE.PathActions.BEZIER_CURVE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.splineThru = function( pts /*Array of Vector*/ ) {
+
+	var args = Array.prototype.slice.call( arguments );
+	var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+	var x0 = lastargs[ lastargs.length - 2 ];
+	var y0 = lastargs[ lastargs.length - 1 ];
+//---
+	var npts = [ new THREE.Vector2( x0, y0 ) ];
+	Array.prototype.push.apply( npts, pts );
+
+	var curve = new THREE.SplineCurve( npts );
+	this.curves.push( curve );
+
+	this.actions.push( { action: THREE.PathActions.CSPLINE_THRU, args: args } );
+
+};
+
+// FUTURE: Change the API or follow canvas API?
+
+THREE.Path.prototype.arc = function ( aX, aY, aRadius,
+									  aStartAngle, aEndAngle, aClockwise ) {
+
+	var lastargs = this.actions[ this.actions.length - 1].args;
+	var x0 = lastargs[ lastargs.length - 2 ];
+	var y0 = lastargs[ lastargs.length - 1 ];
+
+	this.absarc(aX + x0, aY + y0, aRadius,
+		aStartAngle, aEndAngle, aClockwise );
+	
+ };
+
+ THREE.Path.prototype.absarc = function ( aX, aY, aRadius,
+									  aStartAngle, aEndAngle, aClockwise ) {
+	this.absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise);
+ };
+ 
+THREE.Path.prototype.ellipse = function ( aX, aY, xRadius, yRadius,
+									  aStartAngle, aEndAngle, aClockwise ) {
+
+	var lastargs = this.actions[ this.actions.length - 1].args;
+	var x0 = lastargs[ lastargs.length - 2 ];
+	var y0 = lastargs[ lastargs.length - 1 ];
+
+	this.absellipse(aX + x0, aY + y0, xRadius, yRadius,
+		aStartAngle, aEndAngle, aClockwise );
+
+ };
+ 
+
+THREE.Path.prototype.absellipse = function ( aX, aY, xRadius, yRadius,
+									  aStartAngle, aEndAngle, aClockwise ) {
+
+	var args = Array.prototype.slice.call( arguments );
+	var curve = new THREE.EllipseCurve( aX, aY, xRadius, yRadius,
+									aStartAngle, aEndAngle, aClockwise );
+	this.curves.push( curve );
+
+	var lastPoint = curve.getPoint(aClockwise ? 1 : 0);
+	args.push(lastPoint.x);
+	args.push(lastPoint.y);
+
+	this.actions.push( { action: THREE.PathActions.ELLIPSE, args: args } );
+
+ };
+
+THREE.Path.prototype.getSpacedPoints = function ( divisions, closedPath ) {
+
+	if ( ! divisions ) divisions = 40;
+
+	var points = [];
+
+	for ( var i = 0; i < divisions; i ++ ) {
+
+		points.push( this.getPoint( i / divisions ) );
+
+		//if( !this.getPoint( i / divisions ) ) throw "DIE";
+
+	}
+
+	// if ( closedPath ) {
+	//
+	// 	points.push( points[ 0 ] );
+	//
+	// }
+
+	return points;
+
+};
+
+/* Return an array of vectors based on contour of the path */
+
+THREE.Path.prototype.getPoints = function( divisions, closedPath ) {
+
+	if (this.useSpacedPoints) {
+		console.log('tata');
+		return this.getSpacedPoints( divisions, closedPath );
+	}
+
+	divisions = divisions || 12;
+
+	var points = [];
+
+	var i, il, item, action, args;
+	var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0,
+		laste, j,
+		t, tx, ty;
+
+	for ( i = 0, il = this.actions.length; i < il; i ++ ) {
+
+		item = this.actions[ i ];
+
+		action = item.action;
+		args = item.args;
+
+		switch( action ) {
+
+		case THREE.PathActions.MOVE_TO:
+
+			points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
+
+			break;
+
+		case THREE.PathActions.LINE_TO:
+
+			points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
+
+			break;
+
+		case THREE.PathActions.QUADRATIC_CURVE_TO:
+
+			cpx  = args[ 2 ];
+			cpy  = args[ 3 ];
+
+			cpx1 = args[ 0 ];
+			cpy1 = args[ 1 ];
+
+			if ( points.length > 0 ) {
+
+				laste = points[ points.length - 1 ];
+
+				cpx0 = laste.x;
+				cpy0 = laste.y;
+
+			} else {
+
+				laste = this.actions[ i - 1 ].args;
+
+				cpx0 = laste[ laste.length - 2 ];
+				cpy0 = laste[ laste.length - 1 ];
+
+			}
+
+			for ( j = 1; j <= divisions; j ++ ) {
+
+				t = j / divisions;
+
+				tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx );
+				ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy );
+
+				points.push( new THREE.Vector2( tx, ty ) );
+
+		  	}
+
+			break;
+
+		case THREE.PathActions.BEZIER_CURVE_TO:
+
+			cpx  = args[ 4 ];
+			cpy  = args[ 5 ];
+
+			cpx1 = args[ 0 ];
+			cpy1 = args[ 1 ];
+
+			cpx2 = args[ 2 ];
+			cpy2 = args[ 3 ];
+
+			if ( points.length > 0 ) {
+
+				laste = points[ points.length - 1 ];
+
+				cpx0 = laste.x;
+				cpy0 = laste.y;
+
+			} else {
+
+				laste = this.actions[ i - 1 ].args;
+
+				cpx0 = laste[ laste.length - 2 ];
+				cpy0 = laste[ laste.length - 1 ];
+
+			}
+
+
+			for ( j = 1; j <= divisions; j ++ ) {
+
+				t = j / divisions;
+
+				tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx );
+				ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy );
+
+				points.push( new THREE.Vector2( tx, ty ) );
+
+			}
+
+			break;
+
+		case THREE.PathActions.CSPLINE_THRU:
+
+			laste = this.actions[ i - 1 ].args;
+
+			var last = new THREE.Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] );
+			var spts = [ last ];
+
+			var n = divisions * args[ 0 ].length;
+
+			spts = spts.concat( args[ 0 ] );
+
+			var spline = new THREE.SplineCurve( spts );
+
+			for ( j = 1; j <= n; j ++ ) {
+
+				points.push( spline.getPointAt( j / n ) ) ;
+
+			}
+
+			break;
+
+		case THREE.PathActions.ARC:
+
+			var aX = args[ 0 ], aY = args[ 1 ],
+				aRadius = args[ 2 ],
+				aStartAngle = args[ 3 ], aEndAngle = args[ 4 ],
+				aClockwise = !!args[ 5 ];
+
+			var deltaAngle = aEndAngle - aStartAngle;
+			var angle;
+			var tdivisions = divisions * 2;
+
+			for ( j = 1; j <= tdivisions; j ++ ) {
+
+				t = j / tdivisions;
+
+				if ( ! aClockwise ) {
+
+					t = 1 - t;
+
+				}
+
+				angle = aStartAngle + t * deltaAngle;
+
+				tx = aX + aRadius * Math.cos( angle );
+				ty = aY + aRadius * Math.sin( angle );
+
+				//console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
+
+				points.push( new THREE.Vector2( tx, ty ) );
+
+			}
+
+			//console.log(points);
+
+		  break;
+		  
+		case THREE.PathActions.ELLIPSE:
+
+			var aX = args[ 0 ], aY = args[ 1 ],
+				xRadius = args[ 2 ],
+				yRadius = args[ 3 ],
+				aStartAngle = args[ 4 ], aEndAngle = args[ 5 ],
+				aClockwise = !!args[ 6 ];
+
+
+			var deltaAngle = aEndAngle - aStartAngle;
+			var angle;
+			var tdivisions = divisions * 2;
+
+			for ( j = 1; j <= tdivisions; j ++ ) {
+
+				t = j / tdivisions;
+
+				if ( ! aClockwise ) {
+
+					t = 1 - t;
+
+				}
+
+				angle = aStartAngle + t * deltaAngle;
+
+				tx = aX + xRadius * Math.cos( angle );
+				ty = aY + yRadius * Math.sin( angle );
+
+				//console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
+
+				points.push( new THREE.Vector2( tx, ty ) );
+
+			}
+
+			//console.log(points);
+
+		  break;
+
+		} // end switch
+
+	}
+
+
+
+	// Normalize to remove the closing point by default.
+	var lastPoint = points[ points.length - 1];
+	var EPSILON = 0.0000000001;
+	if ( Math.abs(lastPoint.x - points[ 0 ].x) < EPSILON &&
+             Math.abs(lastPoint.y - points[ 0 ].y) < EPSILON)
+		points.splice( points.length - 1, 1);
+	if ( closedPath ) {
+
+		points.push( points[ 0 ] );
+
+	}
+
+	return points;
+
+};
+
+// Breaks path into shapes
+
+THREE.Path.prototype.toShapes = function() {
+
+	var i, il, item, action, args;
+
+	var subPaths = [], lastPath = new THREE.Path();
+
+	for ( i = 0, il = this.actions.length; i < il; i ++ ) {
+
+		item = this.actions[ i ];
+
+		args = item.args;
+		action = item.action;
+
+		if ( action == THREE.PathActions.MOVE_TO ) {
+
+			if ( lastPath.actions.length != 0 ) {
+
+				subPaths.push( lastPath );
+				lastPath = new THREE.Path();
+
+			}
+
+		}
+
+		lastPath[ action ].apply( lastPath, args );
+
+	}
+
+	if ( lastPath.actions.length != 0 ) {
+
+		subPaths.push( lastPath );
+
+	}
+
+	// console.log(subPaths);
+
+	if ( subPaths.length == 0 ) return [];
+
+	var tmpPath, tmpShape, shapes = [];
+
+	var holesFirst = !THREE.Shape.Utils.isClockWise( subPaths[ 0 ].getPoints() );
+	// console.log("Holes first", holesFirst);
+
+	if ( subPaths.length == 1) {
+		tmpPath = subPaths[0];
+		tmpShape = new THREE.Shape();
+		tmpShape.actions = tmpPath.actions;
+		tmpShape.curves = tmpPath.curves;
+		shapes.push( tmpShape );
+		return shapes;
+	};
+
+	if ( holesFirst ) {
+
+		tmpShape = new THREE.Shape();
+
+		for ( i = 0, il = subPaths.length; i < il; i ++ ) {
+
+			tmpPath = subPaths[ i ];
+
+			if ( THREE.Shape.Utils.isClockWise( tmpPath.getPoints() ) ) {
+
+				tmpShape.actions = tmpPath.actions;
+				tmpShape.curves = tmpPath.curves;
+
+				shapes.push( tmpShape );
+				tmpShape = new THREE.Shape();
+
+				//console.log('cw', i);
+
+			} else {
+
+				tmpShape.holes.push( tmpPath );
+
+				//console.log('ccw', i);
+
+			}
+
+		}
+
+	} else {
+
+		// Shapes first
+
+		for ( i = 0, il = subPaths.length; i < il; i ++ ) {
+
+			tmpPath = subPaths[ i ];
+
+			if ( THREE.Shape.Utils.isClockWise( tmpPath.getPoints() ) ) {
+
+
+				if ( tmpShape ) shapes.push( tmpShape );
+
+				tmpShape = new THREE.Shape();
+				tmpShape.actions = tmpPath.actions;
+				tmpShape.curves = tmpPath.curves;
+
+			} else {
+
+				tmpShape.holes.push( tmpPath );
+
+			}
+
+		}
+
+		shapes.push( tmpShape );
+
+	}
+
+	//console.log("shape", shapes);
+
+	return shapes;
+
+};
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * Defines a 2d shape plane using paths.
+ **/
+
+// STEP 1 Create a path.
+// STEP 2 Turn path into shape.
+// STEP 3 ExtrudeGeometry takes in Shape/Shapes
+// STEP 3a - Extract points from each shape, turn to vertices
+// STEP 3b - Triangulate each shape, add faces.
+
+THREE.Shape = function () {
+
+	THREE.Path.apply( this, arguments );
+	this.holes = [];
+
+};
+
+THREE.Shape.prototype = Object.create( THREE.Path.prototype );
+
+// Convenience method to return ExtrudeGeometry
+
+THREE.Shape.prototype.extrude = function ( options ) {
+
+	var extruded = new THREE.ExtrudeGeometry( this, options );
+	return extruded;
+
+};
+
+// Convenience method to return ShapeGeometry
+
+THREE.Shape.prototype.makeGeometry = function ( options ) {
+
+	var geometry = new THREE.ShapeGeometry( this, options );
+	return geometry;
+
+};
+
+// Get points of holes
+
+THREE.Shape.prototype.getPointsHoles = function ( divisions ) {
+
+	var i, il = this.holes.length, holesPts = [];
+
+	for ( i = 0; i < il; i ++ ) {
+
+		holesPts[ i ] = this.holes[ i ].getTransformedPoints( divisions, this.bends );
+
+	}
+
+	return holesPts;
+
+};
+
+// Get points of holes (spaced by regular distance)
+
+THREE.Shape.prototype.getSpacedPointsHoles = function ( divisions ) {
+
+	var i, il = this.holes.length, holesPts = [];
+
+	for ( i = 0; i < il; i ++ ) {
+
+		holesPts[ i ] = this.holes[ i ].getTransformedSpacedPoints( divisions, this.bends );
+
+	}
+
+	return holesPts;
+
+};
+
+
+// Get points of shape and holes (keypoints based on segments parameter)
+
+THREE.Shape.prototype.extractAllPoints = function ( divisions ) {
+
+	return {
+
+		shape: this.getTransformedPoints( divisions ),
+		holes: this.getPointsHoles( divisions )
+
+	};
+
+};
+
+THREE.Shape.prototype.extractPoints = function ( divisions ) {
+
+	if (this.useSpacedPoints) {
+		return this.extractAllSpacedPoints(divisions);
+	}
+
+	return this.extractAllPoints(divisions);
+
+};
+
+//
+// THREE.Shape.prototype.extractAllPointsWithBend = function ( divisions, bend ) {
+//
+// 	return {
+//
+// 		shape: this.transform( bend, divisions ),
+// 		holes: this.getPointsHoles( divisions, bend )
+//
+// 	};
+//
+// };
+
+// Get points of shape and holes (spaced by regular distance)
+
+THREE.Shape.prototype.extractAllSpacedPoints = function ( divisions ) {
+
+	return {
+
+		shape: this.getTransformedSpacedPoints( divisions ),
+		holes: this.getSpacedPointsHoles( divisions )
+
+	};
+
+};
+
+/**************************************************************
+ *	Utils
+ **************************************************************/
+
+THREE.Shape.Utils = {
+
+	/*
+		contour - array of vector2 for contour
+		holes   - array of array of vector2
+	*/
+
+	removeHoles: function ( contour, holes ) {
+
+		var shape = contour.concat(); // work on this shape
+		var allpoints = shape.concat();
+
+		/* For each isolated shape, find the closest points and break to the hole to allow triangulation */
+
+
+		var prevShapeVert, nextShapeVert,
+			prevHoleVert, nextHoleVert,
+			holeIndex, shapeIndex,
+			shapeId, shapeGroup,
+			h, h2,
+			hole, shortest, d,
+			p, pts1, pts2,
+			tmpShape1, tmpShape2,
+			tmpHole1, tmpHole2,
+			verts = [];
+
+		for ( h = 0; h < holes.length; h ++ ) {
+
+			hole = holes[ h ];
+
+			/*
+			shapeholes[ h ].concat(); // preserves original
+			holes.push( hole );
+			*/
+
+			Array.prototype.push.apply( allpoints, hole );
+
+			shortest = Number.POSITIVE_INFINITY;
+
+
+			// Find the shortest pair of pts between shape and hole
+
+			// Note: Actually, I'm not sure now if we could optimize this to be faster than O(m*n)
+			// Using distanceToSquared() intead of distanceTo() should speed a little
+			// since running square roots operations are reduced.
+
+			for ( h2 = 0; h2 < hole.length; h2 ++ ) {
+
+				pts1 = hole[ h2 ];
+				var dist = [];
+
+				for ( p = 0; p < shape.length; p++ ) {
+
+					pts2 = shape[ p ];
+					d = pts1.distanceToSquared( pts2 );
+					dist.push( d );
+
+					if ( d < shortest ) {
+
+						shortest = d;
+						holeIndex = h2;
+						shapeIndex = p;
+
+					}
+
+				}
+
+			}
+
+			//console.log("shortest", shortest, dist);
+
+			prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
+			prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
+
+			var areaapts = [
+
+				hole[ holeIndex ],
+				shape[ shapeIndex ],
+				shape[ prevShapeVert ]
+
+			];
+
+			var areaa = THREE.FontUtils.Triangulate.area( areaapts );
+
+			var areabpts = [
+
+				hole[ holeIndex ],
+				hole[ prevHoleVert ],
+				shape[ shapeIndex ]
+
+			];
+
+			var areab = THREE.FontUtils.Triangulate.area( areabpts );
+
+			var shapeOffset = 1;
+			var holeOffset = -1;
+
+			var oldShapeIndex = shapeIndex, oldHoleIndex = holeIndex;
+			shapeIndex += shapeOffset;
+			holeIndex += holeOffset;
+
+			if ( shapeIndex < 0 ) { shapeIndex += shape.length;  }
+			shapeIndex %= shape.length;
+
+			if ( holeIndex < 0 ) { holeIndex += hole.length;  }
+			holeIndex %= hole.length;
+
+			prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
+			prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
+
+			areaapts = [
+
+				hole[ holeIndex ],
+				shape[ shapeIndex ],
+				shape[ prevShapeVert ]
+
+			];
+
+			var areaa2 = THREE.FontUtils.Triangulate.area( areaapts );
+
+			areabpts = [
+
+				hole[ holeIndex ],
+				hole[ prevHoleVert ],
+				shape[ shapeIndex ]
+
+			];
+
+			var areab2 = THREE.FontUtils.Triangulate.area( areabpts );
+			//console.log(areaa,areab ,areaa2,areab2, ( areaa + areab ),  ( areaa2 + areab2 ));
+
+			if ( ( areaa + areab ) > ( areaa2 + areab2 ) ) {
+
+				// In case areas are not correct.
+				//console.log("USE THIS");
+
+				shapeIndex = oldShapeIndex;
+				holeIndex = oldHoleIndex ;
+
+				if ( shapeIndex < 0 ) { shapeIndex += shape.length;  }
+				shapeIndex %= shape.length;
+
+				if ( holeIndex < 0 ) { holeIndex += hole.length;  }
+				holeIndex %= hole.length;
+
+				prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
+				prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
+
+			} else {
+
+				//console.log("USE THAT ")
+
+			}
+
+			tmpShape1 = shape.slice( 0, shapeIndex );
+			tmpShape2 = shape.slice( shapeIndex );
+			tmpHole1 = hole.slice( holeIndex );
+			tmpHole2 = hole.slice( 0, holeIndex );
+
+			// Should check orders here again?
+
+			var trianglea = [
+
+				hole[ holeIndex ],
+				shape[ shapeIndex ],
+				shape[ prevShapeVert ]
+
+			];
+
+			var triangleb = [
+
+				hole[ holeIndex ] ,
+				hole[ prevHoleVert ],
+				shape[ shapeIndex ]
+
+			];
+
+			verts.push( trianglea );
+			verts.push( triangleb );
+
+			shape = tmpShape1.concat( tmpHole1 ).concat( tmpHole2 ).concat( tmpShape2 );
+
+		}
+
+		return {
+
+			shape:shape, 		/* shape with no holes */
+			isolatedPts: verts, /* isolated faces */
+			allpoints: allpoints
+
+		}
+
+
+	},
+
+	triangulateShape: function ( contour, holes ) {
+
+		var shapeWithoutHoles = THREE.Shape.Utils.removeHoles( contour, holes );
+
+		var shape = shapeWithoutHoles.shape,
+			allpoints = shapeWithoutHoles.allpoints,
+			isolatedPts = shapeWithoutHoles.isolatedPts;
+
+		var triangles = THREE.FontUtils.Triangulate( shape, false ); // True returns indices for points of spooled shape
+
+		// To maintain reference to old shape, one must match coordinates, or offset the indices from original arrays. It's probably easier to do the first.
+
+		//console.log( "triangles",triangles, triangles.length );
+		//console.log( "allpoints",allpoints, allpoints.length );
+
+		var i, il, f, face,
+			key, index,
+			allPointsMap = {},
+			isolatedPointsMap = {};
+
+		// prepare all points map
+
+		for ( i = 0, il = allpoints.length; i < il; i ++ ) {
+
+			key = allpoints[ i ].x + ":" + allpoints[ i ].y;
+
+			if ( allPointsMap[ key ] !== undefined ) {
+
+				console.log( "Duplicate point", key );
+
+			}
+
+			allPointsMap[ key ] = i;
+
+		}
+
+		// check all face vertices against all points map
+
+		for ( i = 0, il = triangles.length; i < il; i ++ ) {
+
+			face = triangles[ i ];
+
+			for ( f = 0; f < 3; f ++ ) {
+
+				key = face[ f ].x + ":" + face[ f ].y;
+
+				index = allPointsMap[ key ];
+
+				if ( index !== undefined ) {
+
+					face[ f ] = index;
+
+				}
+
+			}
+
+		}
+
+		// check isolated points vertices against all points map
+
+		for ( i = 0, il = isolatedPts.length; i < il; i ++ ) {
+
+			face = isolatedPts[ i ];
+
+			for ( f = 0; f < 3; f ++ ) {
+
+				key = face[ f ].x + ":" + face[ f ].y;
+
+				index = allPointsMap[ key ];
+
+				if ( index !== undefined ) {
+
+					face[ f ] = index;
+
+				}
+
+			}
+
+		}
+
+		return triangles.concat( isolatedPts );
+
+	}, // end triangulate shapes
+
+	/*
+	triangulate2 : function( pts, holes ) {
+
+		// For use with Poly2Tri.js
+
+		var allpts = pts.concat();
+		var shape = [];
+		for (var p in pts) {
+			shape.push(new js.poly2tri.Point(pts[p].x, pts[p].y));
+		}
+
+		var swctx = new js.poly2tri.SweepContext(shape);
+
+		for (var h in holes) {
+			var aHole = holes[h];
+			var newHole = []
+			for (i in aHole) {
+				newHole.push(new js.poly2tri.Point(aHole[i].x, aHole[i].y));
+				allpts.push(aHole[i]);
+			}
+			swctx.AddHole(newHole);
+		}
+
+		var find;
+		var findIndexForPt = function (pt) {
+			find = new THREE.Vector2(pt.x, pt.y);
+			var p;
+			for (p=0, pl = allpts.length; p<pl; p++) {
+				if (allpts[p].equals(find)) return p;
+			}
+			return -1;
+		};
+
+		// triangulate
+		js.poly2tri.sweep.Triangulate(swctx);
+
+		var triangles =  swctx.GetTriangles();
+		var tr ;
+		var facesPts = [];
+		for (var t in triangles) {
+			tr =  triangles[t];
+			facesPts.push([
+				findIndexForPt(tr.GetPoint(0)),
+				findIndexForPt(tr.GetPoint(1)),
+				findIndexForPt(tr.GetPoint(2))
+					]);
+		}
+
+
+	//	console.log(facesPts);
+	//	console.log("triangles", triangles.length, triangles);
+
+		// Returns array of faces with 3 element each
+	return facesPts;
+	},
+*/
+
+	isClockWise: function ( pts ) {
+
+		return THREE.FontUtils.Triangulate.area( pts ) < 0;
+
+	},
+
+	// Bezier Curves formulas obtained from
+	// http://en.wikipedia.org/wiki/B%C3%A9zier_curve
+
+	// Quad Bezier Functions
+
+	b2p0: function ( t, p ) {
+
+		var k = 1 - t;
+		return k * k * p;
+
+	},
+
+	b2p1: function ( t, p ) {
+
+		return 2 * ( 1 - t ) * t * p;
+
+	},
+
+	b2p2: function ( t, p ) {
+
+		return t * t * p;
+
+	},
+
+	b2: function ( t, p0, p1, p2 ) {
+
+		return this.b2p0( t, p0 ) + this.b2p1( t, p1 ) + this.b2p2( t, p2 );
+
+	},
+
+	// Cubic Bezier Functions
+
+	b3p0: function ( t, p ) {
+
+		var k = 1 - t;
+		return k * k * k * p;
+
+	},
+
+	b3p1: function ( t, p ) {
+
+		var k = 1 - t;
+		return 3 * k * k * t * p;
+
+	},
+
+	b3p2: function ( t, p ) {
+
+		var k = 1 - t;
+		return 3 * k * t * t * p;
+
+	},
+
+	b3p3: function ( t, p ) {
+
+		return t * t * t * p;
+
+	},
+
+	b3: function ( t, p0, p1, p2, p3 ) {
+
+		return this.b3p0( t, p0 ) + this.b3p1( t, p1 ) + this.b3p2( t, p2 ) +  this.b3p3( t, p3 );
+
+	}
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ */
+
+THREE.AnimationHandler = (function() {
+
+	var playing = [];
+	var library = {};
+	var that    = {};
+
+
+	//--- update ---
+
+	that.update = function( deltaTimeMS ) {
+
+		for( var i = 0; i < playing.length; i ++ )
+			playing[ i ].update( deltaTimeMS );
+
+	};
+
+
+	//--- add ---
+
+	that.addToUpdate = function( animation ) {
+
+		if ( playing.indexOf( animation ) === -1 )
+			playing.push( animation );
+
+	};
+
+
+	//--- remove ---
+
+	that.removeFromUpdate = function( animation ) {
+
+		var index = playing.indexOf( animation );
+
+		if( index !== -1 )
+			playing.splice( index, 1 );
+
+	};
+
+
+	//--- add ---
+
+	that.add = function( data ) {
+
+		if ( library[ data.name ] !== undefined )
+			console.log( "THREE.AnimationHandler.add: Warning! " + data.name + " already exists in library. Overwriting." );
+
+		library[ data.name ] = data;
+		initData( data );
+
+	};
+
+
+	//--- get ---
+
+	that.get = function( name ) {
+
+		if ( typeof name === "string" ) {
+
+			if ( library[ name ] ) {
+
+				return library[ name ];
+
+			} else {
+
+				console.log( "THREE.AnimationHandler.get: Couldn't find animation " + name );
+				return null;
+
+			}
+
+		} else {
+
+			// todo: add simple tween library
+
+		}
+
+	};
+
+	//--- parse ---
+
+	that.parse = function( root ) {
+
+		// setup hierarchy
+
+		var hierarchy = [];
+
+		if ( root instanceof THREE.SkinnedMesh ) {
+
+			for( var b = 0; b < root.bones.length; b++ ) {
+
+				hierarchy.push( root.bones[ b ] );
+
+			}
+
+		} else {
+
+			parseRecurseHierarchy( root, hierarchy );
+
+		}
+
+		return hierarchy;
+
+	};
+
+	var parseRecurseHierarchy = function( root, hierarchy ) {
+
+		hierarchy.push( root );
+
+		for( var c = 0; c < root.children.length; c++ )
+			parseRecurseHierarchy( root.children[ c ], hierarchy );
+
+	}
+
+
+	//--- init data ---
+
+	var initData = function( data ) {
+
+		if( data.initialized === true )
+			return;
+
+
+		// loop through all keys
+
+		for( var h = 0; h < data.hierarchy.length; h ++ ) {
+
+			for( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+				// remove minus times
+
+				if( data.hierarchy[ h ].keys[ k ].time < 0 )
+					data.hierarchy[ h ].keys[ k ].time = 0;
+
+
+				// create quaternions
+
+				if( data.hierarchy[ h ].keys[ k ].rot !== undefined &&
+				 !( data.hierarchy[ h ].keys[ k ].rot instanceof THREE.Quaternion ) ) {
+
+					var quat = data.hierarchy[ h ].keys[ k ].rot;
+					data.hierarchy[ h ].keys[ k ].rot = new THREE.Quaternion( quat[0], quat[1], quat[2], quat[3] );
+
+				}
+
+			}
+
+
+			// prepare morph target keys
+
+			if( data.hierarchy[ h ].keys.length && data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) {
+
+				// get all used
+
+				var usedMorphTargets = {};
+
+				for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+					for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) {
+
+						var morphTargetName = data.hierarchy[ h ].keys[ k ].morphTargets[ m ];
+						usedMorphTargets[ morphTargetName ] = -1;
+
+					}
+
+				}
+
+				data.hierarchy[ h ].usedMorphTargets = usedMorphTargets;
+
+
+				// set all used on all frames
+
+				for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+					var influences = {};
+
+					for ( var morphTargetName in usedMorphTargets ) {
+
+						for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) {
+
+							if ( data.hierarchy[ h ].keys[ k ].morphTargets[ m ] === morphTargetName ) {
+
+								influences[ morphTargetName ] = data.hierarchy[ h ].keys[ k ].morphTargetsInfluences[ m ];
+								break;
+
+							}
+
+						}
+
+						if ( m === data.hierarchy[ h ].keys[ k ].morphTargets.length ) {
+
+							influences[ morphTargetName ] = 0;
+
+						}
+
+					}
+
+					data.hierarchy[ h ].keys[ k ].morphTargetsInfluences = influences;
+
+				}
+
+			}
+
+
+			// remove all keys that are on the same time
+
+			for ( var k = 1; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+				if ( data.hierarchy[ h ].keys[ k ].time === data.hierarchy[ h ].keys[ k - 1 ].time ) {
+
+					data.hierarchy[ h ].keys.splice( k, 1 );
+					k --;
+
+				}
+
+			}
+
+
+			// set index
+
+			for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+				data.hierarchy[ h ].keys[ k ].index = k;
+
+			}
+
+		}
+
+
+		// JIT
+
+		var lengthInFrames = parseInt( data.length * data.fps, 10 );
+
+		data.JIT = {};
+		data.JIT.hierarchy = [];
+
+		for( var h = 0; h < data.hierarchy.length; h ++ )
+			data.JIT.hierarchy.push( new Array( lengthInFrames ) );
+
+
+		// done
+
+		data.initialized = true;
+
+	};
+
+
+	// interpolation types
+
+	that.LINEAR = 0;
+	that.CATMULLROM = 1;
+	that.CATMULLROM_FORWARD = 2;
+
+	return that;
+
+}());
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Animation = function ( root, name, interpolationType ) {
+
+	this.root = root;
+	this.data = THREE.AnimationHandler.get( name );
+	this.hierarchy = THREE.AnimationHandler.parse( root );
+
+	this.currentTime = 0;
+	this.timeScale = 1;
+
+	this.isPlaying = false;
+	this.isPaused = true;
+	this.loop = true;
+
+	this.interpolationType = interpolationType !== undefined ? interpolationType : THREE.AnimationHandler.LINEAR;
+
+	this.points = [];
+	this.target = new THREE.Vector3();
+
+};
+
+THREE.Animation.prototype.play = function ( loop, startTimeMS ) {
+
+	if ( this.isPlaying === false ) {
+
+		this.isPlaying = true;
+		this.loop = loop !== undefined ? loop : true;
+		this.currentTime = startTimeMS !== undefined ? startTimeMS : 0;
+
+		// reset key cache
+
+		var h, hl = this.hierarchy.length,
+			object;
+
+		for ( h = 0; h < hl; h ++ ) {
+
+			object = this.hierarchy[ h ];
+
+			if ( this.interpolationType !== THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+				object.useQuaternion = true;
+
+			}
+
+			object.matrixAutoUpdate = true;
+
+			if ( object.animationCache === undefined ) {
+
+				object.animationCache = {};
+				object.animationCache.prevKey = { pos: 0, rot: 0, scl: 0 };
+				object.animationCache.nextKey = { pos: 0, rot: 0, scl: 0 };
+				object.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix;
+
+			}
+
+			var prevKey = object.animationCache.prevKey;
+			var nextKey = object.animationCache.nextKey;
+
+			prevKey.pos = this.data.hierarchy[ h ].keys[ 0 ];
+			prevKey.rot = this.data.hierarchy[ h ].keys[ 0 ];
+			prevKey.scl = this.data.hierarchy[ h ].keys[ 0 ];
+
+			nextKey.pos = this.getNextKeyWith( "pos", h, 1 );
+			nextKey.rot = this.getNextKeyWith( "rot", h, 1 );
+			nextKey.scl = this.getNextKeyWith( "scl", h, 1 );
+
+		}
+
+		this.update( 0 );
+
+	}
+
+	this.isPaused = false;
+
+	THREE.AnimationHandler.addToUpdate( this );
+
+};
+
+
+THREE.Animation.prototype.pause = function() {
+
+	if ( this.isPaused === true ) {
+
+		THREE.AnimationHandler.addToUpdate( this );
+
+	} else {
+
+		THREE.AnimationHandler.removeFromUpdate( this );
+
+	}
+
+	this.isPaused = !this.isPaused;
+
+};
+
+
+THREE.Animation.prototype.stop = function() {
+
+	this.isPlaying = false;
+	this.isPaused  = false;
+	THREE.AnimationHandler.removeFromUpdate( this );
+
+};
+
+
+THREE.Animation.prototype.update = function ( deltaTimeMS ) {
+
+	// early out
+
+	if ( this.isPlaying === false ) return;
+
+
+	// vars
+
+	var types = [ "pos", "rot", "scl" ];
+	var type;
+	var scale;
+	var vector;
+	var prevXYZ, nextXYZ;
+	var prevKey, nextKey;
+	var object;
+	var animationCache;
+	var frame;
+	var JIThierarchy = this.data.JIT.hierarchy;
+	var currentTime, unloopedCurrentTime;
+	var currentPoint, forwardPoint, angle;
+
+
+	this.currentTime += deltaTimeMS * this.timeScale;
+
+	unloopedCurrentTime = this.currentTime;
+	currentTime = this.currentTime = this.currentTime % this.data.length;
+	frame = parseInt( Math.min( currentTime * this.data.fps, this.data.length * this.data.fps ), 10 );
+
+
+	for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) {
+
+		object = this.hierarchy[ h ];
+		animationCache = object.animationCache;
+
+		// loop through pos/rot/scl
+
+		for ( var t = 0; t < 3; t ++ ) {
+
+			// get keys
+
+			type    = types[ t ];
+			prevKey = animationCache.prevKey[ type ];
+			nextKey = animationCache.nextKey[ type ];
+
+			// switch keys?
+
+			if ( nextKey.time <= unloopedCurrentTime ) {
+
+				// did we loop?
+
+				if ( currentTime < unloopedCurrentTime ) {
+
+					if ( this.loop ) {
+
+						prevKey = this.data.hierarchy[ h ].keys[ 0 ];
+						nextKey = this.getNextKeyWith( type, h, 1 );
+
+						while( nextKey.time < currentTime ) {
+
+							prevKey = nextKey;
+							nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 );
+
+						}
+
+					} else {
+
+						this.stop();
+						return;
+
+					}
+
+				} else {
+
+					do {
+
+						prevKey = nextKey;
+						nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 );
+
+					} while( nextKey.time < currentTime )
+
+				}
+
+				animationCache.prevKey[ type ] = prevKey;
+				animationCache.nextKey[ type ] = nextKey;
+
+			}
+
+
+			object.matrixAutoUpdate = true;
+			object.matrixWorldNeedsUpdate = true;
+
+			scale = ( currentTime - prevKey.time ) / ( nextKey.time - prevKey.time );
+			prevXYZ = prevKey[ type ];
+			nextXYZ = nextKey[ type ];
+
+
+			// check scale error
+
+			if ( scale < 0 || scale > 1 ) {
+
+				console.log( "THREE.Animation.update: Warning! Scale out of bounds:" + scale + " on bone " + h );
+				scale = scale < 0 ? 0 : 1;
+
+			}
+
+			// interpolate
+
+			if ( type === "pos" ) {
+
+				vector = object.position;
+
+				if ( this.interpolationType === THREE.AnimationHandler.LINEAR ) {
+
+					vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale;
+					vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale;
+					vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale;
+
+				} else if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM ||
+						    this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+					this.points[ 0 ] = this.getPrevKeyWith( "pos", h, prevKey.index - 1 )[ "pos" ];
+					this.points[ 1 ] = prevXYZ;
+					this.points[ 2 ] = nextXYZ;
+					this.points[ 3 ] = this.getNextKeyWith( "pos", h, nextKey.index + 1 )[ "pos" ];
+
+					scale = scale * 0.33 + 0.33;
+
+					currentPoint = this.interpolateCatmullRom( this.points, scale );
+
+					vector.x = currentPoint[ 0 ];
+					vector.y = currentPoint[ 1 ];
+					vector.z = currentPoint[ 2 ];
+
+					if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+						forwardPoint = this.interpolateCatmullRom( this.points, scale * 1.01 );
+
+						this.target.set( forwardPoint[ 0 ], forwardPoint[ 1 ], forwardPoint[ 2 ] );
+						this.target.sub( vector );
+						this.target.y = 0;
+						this.target.normalize();
+
+						angle = Math.atan2( this.target.x, this.target.z );
+						object.rotation.set( 0, angle, 0 );
+
+					}
+
+				}
+
+			} else if ( type === "rot" ) {
+
+				THREE.Quaternion.slerp( prevXYZ, nextXYZ, object.quaternion, scale );
+
+			} else if ( type === "scl" ) {
+
+				vector = object.scale;
+
+				vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale;
+				vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale;
+				vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale;
+
+			}
+
+		}
+
+	}
+
+};
+
+// Catmull-Rom spline
+
+THREE.Animation.prototype.interpolateCatmullRom = function ( points, scale ) {
+
+	var c = [], v3 = [],
+	point, intPoint, weight, w2, w3,
+	pa, pb, pc, pd;
+
+	point = ( points.length - 1 ) * scale;
+	intPoint = Math.floor( point );
+	weight = point - intPoint;
+
+	c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1;
+	c[ 1 ] = intPoint;
+	c[ 2 ] = intPoint > points.length - 2 ? intPoint : intPoint + 1;
+	c[ 3 ] = intPoint > points.length - 3 ? intPoint : intPoint + 2;
+
+	pa = points[ c[ 0 ] ];
+	pb = points[ c[ 1 ] ];
+	pc = points[ c[ 2 ] ];
+	pd = points[ c[ 3 ] ];
+
+	w2 = weight * weight;
+	w3 = weight * w2;
+
+	v3[ 0 ] = this.interpolate( pa[ 0 ], pb[ 0 ], pc[ 0 ], pd[ 0 ], weight, w2, w3 );
+	v3[ 1 ] = this.interpolate( pa[ 1 ], pb[ 1 ], pc[ 1 ], pd[ 1 ], weight, w2, w3 );
+	v3[ 2 ] = this.interpolate( pa[ 2 ], pb[ 2 ], pc[ 2 ], pd[ 2 ], weight, w2, w3 );
+
+	return v3;
+
+};
+
+THREE.Animation.prototype.interpolate = function ( p0, p1, p2, p3, t, t2, t3 ) {
+
+	var v0 = ( p2 - p0 ) * 0.5,
+		v1 = ( p3 - p1 ) * 0.5;
+
+	return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+};
+
+
+
+// Get next key with
+
+THREE.Animation.prototype.getNextKeyWith = function ( type, h, key ) {
+
+	var keys = this.data.hierarchy[ h ].keys;
+
+	if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM ||
+		 this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+		key = key < keys.length - 1 ? key : keys.length - 1;
+
+	} else {
+
+		key = key % keys.length;
+
+	}
+
+	for ( ; key < keys.length; key++ ) {
+
+		if ( keys[ key ][ type ] !== undefined ) {
+
+			return keys[ key ];
+
+		}
+
+	}
+
+	return this.data.hierarchy[ h ].keys[ 0 ];
+
+};
+
+// Get previous key with
+
+THREE.Animation.prototype.getPrevKeyWith = function ( type, h, key ) {
+
+	var keys = this.data.hierarchy[ h ].keys;
+
+	if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM ||
+		 this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+		key = key > 0 ? key : 0;
+
+	} else {
+
+		key = key >= 0 ? key : key + keys.length;
+
+	}
+
+
+	for ( ; key >= 0; key -- ) {
+
+		if ( keys[ key ][ type ] !== undefined ) {
+
+			return keys[ key ];
+
+		}
+
+	}
+
+	return this.data.hierarchy[ h ].keys[ keys.length - 1 ];
+
+};
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author khang duong
+ * @author erik kitson
+ */
+
+THREE.KeyFrameAnimation = function( root, data, JITCompile ) {
+
+	this.root = root;
+	this.data = THREE.AnimationHandler.get( data );
+	this.hierarchy = THREE.AnimationHandler.parse( root );
+	this.currentTime = 0;
+	this.timeScale = 0.001;
+	this.isPlaying = false;
+	this.isPaused = true;
+	this.loop = true;
+	this.JITCompile = JITCompile !== undefined ? JITCompile : true;
+
+	// initialize to first keyframes
+
+	for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+		var keys = this.data.hierarchy[h].keys,
+			sids = this.data.hierarchy[h].sids,
+			obj = this.hierarchy[h];
+
+		if ( keys.length && sids ) {
+
+			for ( var s = 0; s < sids.length; s++ ) {
+
+				var sid = sids[ s ],
+					next = this.getNextKeyWith( sid, h, 0 );
+
+				if ( next ) {
+
+					next.apply( sid );
+
+				}
+
+			}
+
+			obj.matrixAutoUpdate = false;
+			this.data.hierarchy[h].node.updateMatrix();
+			obj.matrixWorldNeedsUpdate = true;
+
+		}
+
+	}
+
+};
+
+// Play
+
+THREE.KeyFrameAnimation.prototype.play = function( loop, startTimeMS ) {
+
+	if( !this.isPlaying ) {
+
+		this.isPlaying = true;
+		this.loop = loop !== undefined ? loop : true;
+		this.currentTime = startTimeMS !== undefined ? startTimeMS : 0;
+		this.startTimeMs = startTimeMS;
+		this.startTime = 10000000;
+		this.endTime = -this.startTime;
+
+
+		// reset key cache
+
+		var h, hl = this.hierarchy.length,
+			object,
+			node;
+
+		for ( h = 0; h < hl; h++ ) {
+
+			object = this.hierarchy[ h ];
+			node = this.data.hierarchy[ h ];
+			object.useQuaternion = true;
+
+			if ( node.animationCache === undefined ) {
+
+				node.animationCache = {};
+				node.animationCache.prevKey = null;
+				node.animationCache.nextKey = null;
+				node.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix;
+
+			}
+
+			var keys = this.data.hierarchy[h].keys;
+
+			if (keys.length) {
+
+				node.animationCache.prevKey = keys[ 0 ];
+				node.animationCache.nextKey = keys[ 1 ];
+
+				this.startTime = Math.min( keys[0].time, this.startTime );
+				this.endTime = Math.max( keys[keys.length - 1].time, this.endTime );
+
+			}
+
+		}
+
+		this.update( 0 );
+
+	}
+
+	this.isPaused = false;
+
+	THREE.AnimationHandler.addToUpdate( this );
+
+};
+
+
+
+// Pause
+
+THREE.KeyFrameAnimation.prototype.pause = function() {
+
+	if( this.isPaused ) {
+
+		THREE.AnimationHandler.addToUpdate( this );
+
+	} else {
+
+		THREE.AnimationHandler.removeFromUpdate( this );
+
+	}
+
+	this.isPaused = !this.isPaused;
+
+};
+
+
+// Stop
+
+THREE.KeyFrameAnimation.prototype.stop = function() {
+
+	this.isPlaying = false;
+	this.isPaused  = false;
+	THREE.AnimationHandler.removeFromUpdate( this );
+
+
+	// reset JIT matrix and remove cache
+
+	for ( var h = 0; h < this.data.hierarchy.length; h++ ) {
+        
+        var obj = this.hierarchy[ h ];
+		var node = this.data.hierarchy[ h ];
+
+		if ( node.animationCache !== undefined ) {
+
+			var original = node.animationCache.originalMatrix;
+
+			if( obj instanceof THREE.Bone ) {
+
+				original.copy( obj.skinMatrix );
+				obj.skinMatrix = original;
+
+			} else {
+
+				original.copy( obj.matrix );
+				obj.matrix = original;
+
+			}
+
+			delete node.animationCache;
+
+		}
+
+	}
+
+};
+
+
+// Update
+
+THREE.KeyFrameAnimation.prototype.update = function( deltaTimeMS ) {
+
+	// early out
+
+	if( !this.isPlaying ) return;
+
+
+	// vars
+
+	var prevKey, nextKey;
+	var object;
+	var node;
+	var frame;
+	var JIThierarchy = this.data.JIT.hierarchy;
+	var currentTime, unloopedCurrentTime;
+	var looped;
+
+
+	// update
+
+	this.currentTime += deltaTimeMS * this.timeScale;
+
+	unloopedCurrentTime = this.currentTime;
+	currentTime         = this.currentTime = this.currentTime % this.data.length;
+
+	// if looped around, the current time should be based on the startTime
+	if ( currentTime < this.startTimeMs ) {
+
+		currentTime = this.currentTime = this.startTimeMs + currentTime;
+
+	}
+
+	frame               = parseInt( Math.min( currentTime * this.data.fps, this.data.length * this.data.fps ), 10 );
+	looped 				= currentTime < unloopedCurrentTime;
+
+	if ( looped && !this.loop ) {
+
+		// Set the animation to the last keyframes and stop
+		for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+			var keys = this.data.hierarchy[h].keys,
+				sids = this.data.hierarchy[h].sids,
+				end = keys.length-1,
+				obj = this.hierarchy[h];
+
+			if ( keys.length ) {
+
+				for ( var s = 0; s < sids.length; s++ ) {
+
+					var sid = sids[ s ],
+						prev = this.getPrevKeyWith( sid, h, end );
+
+					if ( prev ) {
+						prev.apply( sid );
+
+					}
+
+				}
+
+				this.data.hierarchy[h].node.updateMatrix();
+				obj.matrixWorldNeedsUpdate = true;
+
+			}
+
+		}
+
+		this.stop();
+		return;
+
+	}
+
+	// check pre-infinity
+	if ( currentTime < this.startTime ) {
+
+		return;
+
+	}
+
+	// update
+
+	for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+		object = this.hierarchy[ h ];
+		node = this.data.hierarchy[ h ];
+
+		var keys = node.keys,
+			animationCache = node.animationCache;
+
+		// use JIT?
+
+		if ( this.JITCompile && JIThierarchy[ h ][ frame ] !== undefined ) {
+
+			if( object instanceof THREE.Bone ) {
+
+				object.skinMatrix = JIThierarchy[ h ][ frame ];
+				object.matrixWorldNeedsUpdate = false;
+
+			} else {
+
+				object.matrix = JIThierarchy[ h ][ frame ];
+				object.matrixWorldNeedsUpdate = true;
+
+			}
+
+		// use interpolation
+
+		} else if ( keys.length ) {
+
+			// make sure so original matrix and not JIT matrix is set
+
+			if ( this.JITCompile && animationCache ) {
+
+				if( object instanceof THREE.Bone ) {
+
+					object.skinMatrix = animationCache.originalMatrix;
+
+				} else {
+
+					object.matrix = animationCache.originalMatrix;
+
+				}
+
+			}
+
+			prevKey = animationCache.prevKey;
+			nextKey = animationCache.nextKey;
+
+			if ( prevKey && nextKey ) {
+
+				// switch keys?
+
+				if ( nextKey.time <= unloopedCurrentTime ) {
+
+					// did we loop?
+
+					if ( looped && this.loop ) {
+
+						prevKey = keys[ 0 ];
+						nextKey = keys[ 1 ];
+
+						while ( nextKey.time < currentTime ) {
+
+							prevKey = nextKey;
+							nextKey = keys[ prevKey.index + 1 ];
+
+						}
+
+					} else if ( !looped ) {
+
+						var lastIndex = keys.length - 1;
+
+						while ( nextKey.time < currentTime && nextKey.index !== lastIndex ) {
+
+							prevKey = nextKey;
+							nextKey = keys[ prevKey.index + 1 ];
+
+						}
+
+					}
+
+					animationCache.prevKey = prevKey;
+					animationCache.nextKey = nextKey;
+
+				}
+                if(nextKey.time >= currentTime)
+                    prevKey.interpolate( nextKey, currentTime );
+                else
+                    prevKey.interpolate( nextKey, nextKey.time);
+
+			}
+
+			this.data.hierarchy[h].node.updateMatrix();
+			object.matrixWorldNeedsUpdate = true;
+
+		}
+
+	}
+
+	// update JIT?
+
+	if ( this.JITCompile ) {
+
+		if ( JIThierarchy[ 0 ][ frame ] === undefined ) {
+
+			this.hierarchy[ 0 ].updateMatrixWorld( true );
+
+			for ( var h = 0; h < this.hierarchy.length; h++ ) {
+
+				if( this.hierarchy[ h ] instanceof THREE.Bone ) {
+
+					JIThierarchy[ h ][ frame ] = this.hierarchy[ h ].skinMatrix.clone();
+
+				} else {
+
+					JIThierarchy[ h ][ frame ] = this.hierarchy[ h ].matrix.clone();
+
+				}
+
+			}
+
+		}
+
+	}
+
+};
+
+// Get next key with
+
+THREE.KeyFrameAnimation.prototype.getNextKeyWith = function( sid, h, key ) {
+
+	var keys = this.data.hierarchy[ h ].keys;
+	key = key % keys.length;
+
+	for ( ; key < keys.length; key++ ) {
+
+		if ( keys[ key ].hasTarget( sid ) ) {
+
+			return keys[ key ];
+
+		}
+
+	}
+
+	return keys[ 0 ];
+
+};
+
+// Get previous key with
+
+THREE.KeyFrameAnimation.prototype.getPrevKeyWith = function( sid, h, key ) {
+
+	var keys = this.data.hierarchy[ h ].keys;
+	key = key >= 0 ? key : key + keys.length;
+
+	for ( ; key >= 0; key-- ) {
+
+		if ( keys[ key ].hasTarget( sid ) ) {
+
+			return keys[ key ];
+
+		}
+
+	}
+
+	return keys[ keys.length - 1 ];
+
+};
+/**
+ * Camera for rendering cube maps
+ *	- renders scene into axis-aligned cube
+ *
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.CubeCamera = function ( near, far, cubeResolution ) {
+
+	THREE.Object3D.call( this );
+
+	var fov = 90, aspect = 1;
+
+	var cameraPX = new THREE.PerspectiveCamera( fov, aspect, near, far );
+	cameraPX.up.set( 0, -1, 0 );
+	cameraPX.lookAt( new THREE.Vector3( 1, 0, 0 ) );
+	this.add( cameraPX );
+
+	var cameraNX = new THREE.PerspectiveCamera( fov, aspect, near, far );
+	cameraNX.up.set( 0, -1, 0 );
+	cameraNX.lookAt( new THREE.Vector3( -1, 0, 0 ) );
+	this.add( cameraNX );
+
+	var cameraPY = new THREE.PerspectiveCamera( fov, aspect, near, far );
+	cameraPY.up.set( 0, 0, 1 );
+	cameraPY.lookAt( new THREE.Vector3( 0, 1, 0 ) );
+	this.add( cameraPY );
+
+	var cameraNY = new THREE.PerspectiveCamera( fov, aspect, near, far );
+	cameraNY.up.set( 0, 0, -1 );
+	cameraNY.lookAt( new THREE.Vector3( 0, -1, 0 ) );
+	this.add( cameraNY );
+
+	var cameraPZ = new THREE.PerspectiveCamera( fov, aspect, near, far );
+	cameraPZ.up.set( 0, -1, 0 );
+	cameraPZ.lookAt( new THREE.Vector3( 0, 0, 1 ) );
+	this.add( cameraPZ );
+
+	var cameraNZ = new THREE.PerspectiveCamera( fov, aspect, near, far );
+	cameraNZ.up.set( 0, -1, 0 );
+	cameraNZ.lookAt( new THREE.Vector3( 0, 0, -1 ) );
+	this.add( cameraNZ );
+
+	this.renderTarget = new THREE.WebGLRenderTargetCube( cubeResolution, cubeResolution, { format: THREE.RGBFormat, magFilter: THREE.LinearFilter, minFilter: THREE.LinearFilter } );
+
+	this.updateCubeMap = function ( renderer, scene ) {
+
+		var renderTarget = this.renderTarget;
+		var generateMipmaps = renderTarget.generateMipmaps;
+
+		renderTarget.generateMipmaps = false;
+
+		renderTarget.activeCubeFace = 0;
+		renderer.render( scene, cameraPX, renderTarget );
+
+		renderTarget.activeCubeFace = 1;
+		renderer.render( scene, cameraNX, renderTarget );
+
+		renderTarget.activeCubeFace = 2;
+		renderer.render( scene, cameraPY, renderTarget );
+
+		renderTarget.activeCubeFace = 3;
+		renderer.render( scene, cameraNY, renderTarget );
+
+		renderTarget.activeCubeFace = 4;
+		renderer.render( scene, cameraPZ, renderTarget );
+
+		renderTarget.generateMipmaps = generateMipmaps;
+
+		renderTarget.activeCubeFace = 5;
+		renderer.render( scene, cameraNZ, renderTarget );
+
+	};
+
+};
+
+THREE.CubeCamera.prototype = Object.create( THREE.Object3D.prototype );
+/*
+ *	@author zz85 / http://twitter.com/blurspline / http://www.lab4games.net/zz85/blog
+ *
+ *	A general perpose camera, for setting FOV, Lens Focal Length,
+ *		and switching between perspective and orthographic views easily.
+ *		Use this only if you do not wish to manage
+ *		both a Orthographic and Perspective Camera
+ *
+ */
+
+
+THREE.CombinedCamera = function ( width, height, fov, near, far, orthoNear, orthoFar ) {
+
+	THREE.Camera.call( this );
+
+	this.fov = fov;
+
+	this.left = -width / 2;
+	this.right = width / 2
+	this.top = height / 2;
+	this.bottom = -height / 2;
+
+	// We could also handle the projectionMatrix internally, but just wanted to test nested camera objects
+
+	this.cameraO = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 	orthoNear, orthoFar );
+	this.cameraP = new THREE.PerspectiveCamera( fov, width / height, near, far );
+
+	this.zoom = 1;
+
+	this.toPerspective();
+
+	var aspect = width/height;
+
+};
+
+THREE.CombinedCamera.prototype = Object.create( THREE.Camera.prototype );
+
+THREE.CombinedCamera.prototype.toPerspective = function () {
+
+	// Switches to the Perspective Camera
+
+	this.near = this.cameraP.near;
+	this.far = this.cameraP.far;
+
+	this.cameraP.fov =  this.fov / this.zoom ;
+
+	this.cameraP.updateProjectionMatrix();
+
+	this.projectionMatrix = this.cameraP.projectionMatrix;
+
+	this.inPerspectiveMode = true;
+	this.inOrthographicMode = false;
+
+};
+
+THREE.CombinedCamera.prototype.toOrthographic = function () {
+
+	// Switches to the Orthographic camera estimating viewport from Perspective
+
+	var fov = this.fov;
+	var aspect = this.cameraP.aspect;
+	var near = this.cameraP.near;
+	var far = this.cameraP.far;
+
+	// The size that we set is the mid plane of the viewing frustum
+
+	var hyperfocus = ( near + far ) / 2;
+
+	var halfHeight = Math.tan( fov / 2 ) * hyperfocus;
+	var planeHeight = 2 * halfHeight;
+	var planeWidth = planeHeight * aspect;
+	var halfWidth = planeWidth / 2;
+
+	halfHeight /= this.zoom;
+	halfWidth /= this.zoom;
+
+	this.cameraO.left = -halfWidth;
+	this.cameraO.right = halfWidth;
+	this.cameraO.top = halfHeight;
+	this.cameraO.bottom = -halfHeight;
+
+	// this.cameraO.left = -farHalfWidth;
+	// this.cameraO.right = farHalfWidth;
+	// this.cameraO.top = farHalfHeight;
+	// this.cameraO.bottom = -farHalfHeight;
+
+	// this.cameraO.left = this.left / this.zoom;
+	// this.cameraO.right = this.right / this.zoom;
+	// this.cameraO.top = this.top / this.zoom;
+	// this.cameraO.bottom = this.bottom / this.zoom;
+
+	this.cameraO.updateProjectionMatrix();
+
+	this.near = this.cameraO.near;
+	this.far = this.cameraO.far;
+	this.projectionMatrix = this.cameraO.projectionMatrix;
+
+	this.inPerspectiveMode = false;
+	this.inOrthographicMode = true;
+
+};
+
+
+THREE.CombinedCamera.prototype.setSize = function( width, height ) {
+
+	this.cameraP.aspect = width / height;
+	this.left = -width / 2;
+	this.right = width / 2
+	this.top = height / 2;
+	this.bottom = -height / 2;
+
+};
+
+
+THREE.CombinedCamera.prototype.setFov = function( fov ) {
+
+	this.fov = fov;
+
+	if ( this.inPerspectiveMode ) {
+
+		this.toPerspective();
+
+	} else {
+
+		this.toOrthographic();
+
+	}
+
+};
+
+// For mantaining similar API with PerspectiveCamera
+
+THREE.CombinedCamera.prototype.updateProjectionMatrix = function() {
+
+	if ( this.inPerspectiveMode ) {
+
+		this.toPerspective();
+
+	} else {
+
+		this.toPerspective();
+		this.toOrthographic();
+
+	}
+
+};
+
+/*
+* Uses Focal Length (in mm) to estimate and set FOV
+* 35mm (fullframe) camera is used if frame size is not specified;
+* Formula based on http://www.bobatkins.com/photography/technical/field_of_view.html
+*/
+THREE.CombinedCamera.prototype.setLens = function ( focalLength, frameHeight ) {
+
+	if ( frameHeight === undefined ) frameHeight = 24;
+
+	var fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) );
+
+	this.setFov( fov );
+
+	return fov;
+};
+
+
+THREE.CombinedCamera.prototype.setZoom = function( zoom ) {
+
+	this.zoom = zoom;
+
+	if ( this.inPerspectiveMode ) {
+
+		this.toPerspective();
+
+	} else {
+
+		this.toOrthographic();
+
+	}
+
+};
+
+THREE.CombinedCamera.prototype.toFrontView = function() {
+
+	this.rotation.x = 0;
+	this.rotation.y = 0;
+	this.rotation.z = 0;
+
+	// should we be modifing the matrix instead?
+
+	this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toBackView = function() {
+
+	this.rotation.x = 0;
+	this.rotation.y = Math.PI;
+	this.rotation.z = 0;
+	this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toLeftView = function() {
+
+	this.rotation.x = 0;
+	this.rotation.y = - Math.PI / 2;
+	this.rotation.z = 0;
+	this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toRightView = function() {
+
+	this.rotation.x = 0;
+	this.rotation.y = Math.PI / 2;
+	this.rotation.z = 0;
+	this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toTopView = function() {
+
+	this.rotation.x = - Math.PI / 2;
+	this.rotation.y = 0;
+	this.rotation.z = 0;
+	this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toBottomView = function() {
+
+	this.rotation.x = Math.PI / 2;
+	this.rotation.y = 0;
+	this.rotation.z = 0;
+	this.rotationAutoUpdate = false;
+
+};
+
+/**
+ * @author hughes
+ */
+
+THREE.CircleGeometry = function ( radius, segments, thetaStart, thetaLength ) {
+
+	THREE.Geometry.call( this );
+
+	radius = radius || 50;
+
+	thetaStart = thetaStart !== undefined ? thetaStart : 0;
+	thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;
+	segments = segments !== undefined ? Math.max( 3, segments ) : 8;
+
+	var i, uvs = [],
+	center = new THREE.Vector3(), centerUV = new THREE.Vector2( 0.5, 0.5 );
+
+	this.vertices.push(center);
+	uvs.push( centerUV );
+
+	for ( i = 0; i <= segments; i ++ ) {
+
+		var vertex = new THREE.Vector3();
+		var segment = thetaStart + i / segments * thetaLength;
+
+		vertex.x = radius * Math.cos( segment );
+		vertex.y = radius * Math.sin( segment );
+
+		this.vertices.push( vertex );
+		uvs.push( new THREE.Vector2( ( vertex.x / radius + 1 ) / 2, ( vertex.y / radius + 1 ) / 2 ) );
+
+	}
+
+	var n = new THREE.Vector3( 0, 0, 1 );
+
+	for ( i = 1; i <= segments; i ++ ) {
+
+		var v1 = i;
+		var v2 = i + 1 ;
+		var v3 = 0;
+
+		this.faces.push( new THREE.Face3( v1, v2, v3, [ n, n, n ] ) );
+		this.faceVertexUvs[ 0 ].push( [ uvs[ i ], uvs[ i + 1 ], centerUV ] );
+
+	}
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+
+	this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+};
+
+THREE.CircleGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Cube.as
+ */
+
+THREE.CubeGeometry = function ( width, height, depth, widthSegments, heightSegments, depthSegments ) {
+
+	THREE.Geometry.call( this );
+
+	var scope = this;
+
+	this.width = width;
+	this.height = height;
+	this.depth = depth;
+
+	this.widthSegments = widthSegments || 1;
+	this.heightSegments = heightSegments || 1;
+	this.depthSegments = depthSegments || 1;
+
+	var width_half = this.width / 2;
+	var height_half = this.height / 2;
+	var depth_half = this.depth / 2;
+
+	buildPlane( 'z', 'y', - 1, - 1, this.depth, this.height, width_half, 0 ); // px
+	buildPlane( 'z', 'y',   1, - 1, this.depth, this.height, - width_half, 1 ); // nx
+	buildPlane( 'x', 'z',   1,   1, this.width, this.depth, height_half, 2 ); // py
+	buildPlane( 'x', 'z',   1, - 1, this.width, this.depth, - height_half, 3 ); // ny
+	buildPlane( 'x', 'y',   1, - 1, this.width, this.height, depth_half, 4 ); // pz
+	buildPlane( 'x', 'y', - 1, - 1, this.width, this.height, - depth_half, 5 ); // nz
+
+	function buildPlane( u, v, udir, vdir, width, height, depth, materialIndex ) {
+
+		var w, ix, iy,
+		gridX = scope.widthSegments,
+		gridY = scope.heightSegments,
+		width_half = width / 2,
+		height_half = height / 2,
+		offset = scope.vertices.length;
+
+		if ( ( u === 'x' && v === 'y' ) || ( u === 'y' && v === 'x' ) ) {
+
+			w = 'z';
+
+		} else if ( ( u === 'x' && v === 'z' ) || ( u === 'z' && v === 'x' ) ) {
+
+			w = 'y';
+			gridY = scope.depthSegments;
+
+		} else if ( ( u === 'z' && v === 'y' ) || ( u === 'y' && v === 'z' ) ) {
+
+			w = 'x';
+			gridX = scope.depthSegments;
+
+		}
+
+		var gridX1 = gridX + 1,
+		gridY1 = gridY + 1,
+		segment_width = width / gridX,
+		segment_height = height / gridY,
+		normal = new THREE.Vector3();
+
+		normal[ w ] = depth > 0 ? 1 : - 1;
+
+		for ( iy = 0; iy < gridY1; iy ++ ) {
+
+			for ( ix = 0; ix < gridX1; ix ++ ) {
+
+				var vector = new THREE.Vector3();
+				vector[ u ] = ( ix * segment_width - width_half ) * udir;
+				vector[ v ] = ( iy * segment_height - height_half ) * vdir;
+				vector[ w ] = depth;
+
+				scope.vertices.push( vector );
+
+			}
+
+		}
+
+		for ( iy = 0; iy < gridY; iy++ ) {
+
+			for ( ix = 0; ix < gridX; ix++ ) {
+
+				var a = ix + gridX1 * iy;
+				var b = ix + gridX1 * ( iy + 1 );
+				var c = ( ix + 1 ) + gridX1 * ( iy + 1 );
+				var d = ( ix + 1 ) + gridX1 * iy;
+
+				var face = new THREE.Face4( a + offset, b + offset, c + offset, d + offset );
+				face.normal.copy( normal );
+				face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone(), normal.clone() );
+				face.materialIndex = materialIndex;
+
+				scope.faces.push( face );
+				scope.faceVertexUvs[ 0 ].push( [
+							new THREE.Vector2( ix / gridX, 1 - iy / gridY ),
+							new THREE.Vector2( ix / gridX, 1 - ( iy + 1 ) / gridY ),
+							new THREE.Vector2( ( ix + 1 ) / gridX, 1- ( iy + 1 ) / gridY ),
+							new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iy / gridY )
+						] );
+
+			}
+
+		}
+
+	}
+
+	this.computeCentroids();
+	this.mergeVertices();
+
+};
+
+THREE.CubeGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.CylinderGeometry = function ( radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded ) {
+
+	THREE.Geometry.call( this );
+
+	this.radiusTop = radiusTop = radiusTop !== undefined ? radiusTop : 20;
+	this.radiusBottom = radiusBottom = radiusBottom !== undefined ? radiusBottom : 20;
+	this.height = height = height !== undefined ? height : 100;
+
+	this.radiusSegments = radiusSegments = radiusSegments || 8;
+	this.heightSegments = heightSegments = heightSegments || 1;
+
+	this.openEnded = openEnded = openEnded !== undefined ? openEnded : false;
+
+	var heightHalf = height / 2;
+
+	var x, y, vertices = [], uvs = [];
+
+	for ( y = 0; y <= heightSegments; y ++ ) {
+
+		var verticesRow = [];
+		var uvsRow = [];
+
+		var v = y / heightSegments;
+		var radius = v * ( radiusBottom - radiusTop ) + radiusTop;
+
+		for ( x = 0; x <= radiusSegments; x ++ ) {
+
+			var u = x / radiusSegments;
+
+			var vertex = new THREE.Vector3();
+			vertex.x = radius * Math.sin( u * Math.PI * 2 );
+			vertex.y = - v * height + heightHalf;
+			vertex.z = radius * Math.cos( u * Math.PI * 2 );
+
+			this.vertices.push( vertex );
+
+			verticesRow.push( this.vertices.length - 1 );
+			uvsRow.push( new THREE.Vector2( u, 1 - v ) );
+
+		}
+
+		vertices.push( verticesRow );
+		uvs.push( uvsRow );
+
+	}
+
+	var tanTheta = ( radiusBottom - radiusTop ) / height;
+	var na, nb;
+
+	for ( x = 0; x < radiusSegments; x ++ ) {
+
+		if ( radiusTop !== 0 ) {
+
+			na = this.vertices[ vertices[ 0 ][ x ] ].clone();
+			nb = this.vertices[ vertices[ 0 ][ x + 1 ] ].clone();
+
+		} else {
+
+			na = this.vertices[ vertices[ 1 ][ x ] ].clone();
+			nb = this.vertices[ vertices[ 1 ][ x + 1 ] ].clone();
+
+		}
+
+		na.setY( Math.sqrt( na.x * na.x + na.z * na.z ) * tanTheta ).normalize();
+		nb.setY( Math.sqrt( nb.x * nb.x + nb.z * nb.z ) * tanTheta ).normalize();
+
+		for ( y = 0; y < heightSegments; y ++ ) {
+
+			var v1 = vertices[ y ][ x ];
+			var v2 = vertices[ y + 1 ][ x ];
+			var v3 = vertices[ y + 1 ][ x + 1 ];
+			var v4 = vertices[ y ][ x + 1 ];
+
+			var n1 = na.clone();
+			var n2 = na.clone();
+			var n3 = nb.clone();
+			var n4 = nb.clone();
+
+			var uv1 = uvs[ y ][ x ].clone();
+			var uv2 = uvs[ y + 1 ][ x ].clone();
+			var uv3 = uvs[ y + 1 ][ x + 1 ].clone();
+			var uv4 = uvs[ y ][ x + 1 ].clone();
+
+			this.faces.push( new THREE.Face4( v1, v2, v3, v4, [ n1, n2, n3, n4 ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3, uv4 ] );
+
+		}
+
+	}
+
+	// top cap
+
+	if ( openEnded === false && radiusTop > 0 ) {
+
+		this.vertices.push( new THREE.Vector3( 0, heightHalf, 0 ) );
+
+		for ( x = 0; x < radiusSegments; x ++ ) {
+
+			var v1 = vertices[ 0 ][ x ];
+			var v2 = vertices[ 0 ][ x + 1 ];
+			var v3 = this.vertices.length - 1;
+
+			var n1 = new THREE.Vector3( 0, 1, 0 );
+			var n2 = new THREE.Vector3( 0, 1, 0 );
+			var n3 = new THREE.Vector3( 0, 1, 0 );
+
+			var uv1 = uvs[ 0 ][ x ].clone();
+			var uv2 = uvs[ 0 ][ x + 1 ].clone();
+			var uv3 = new THREE.Vector2( uv2.u, 0 );
+
+			this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] );
+
+		}
+
+	}
+
+	// bottom cap
+
+	if ( openEnded === false && radiusBottom > 0 ) {
+
+		this.vertices.push( new THREE.Vector3( 0, - heightHalf, 0 ) );
+
+		for ( x = 0; x < radiusSegments; x ++ ) {
+
+			var v1 = vertices[ y ][ x + 1 ];
+			var v2 = vertices[ y ][ x ];
+			var v3 = this.vertices.length - 1;
+
+			var n1 = new THREE.Vector3( 0, - 1, 0 );
+			var n2 = new THREE.Vector3( 0, - 1, 0 );
+			var n3 = new THREE.Vector3( 0, - 1, 0 );
+
+			var uv1 = uvs[ y ][ x + 1 ].clone();
+			var uv2 = uvs[ y ][ x ].clone();
+			var uv3 = new THREE.Vector2( uv2.u, 1 );
+
+			this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] );
+
+		}
+
+	}
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+
+}
+
+THREE.CylinderGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ *
+ * Creates extruded geometry from a path shape.
+ *
+ * parameters = {
+ *
+ *  size: <float>, // size of the text
+ *  height: <float>, // thickness to extrude text
+ *  curveSegments: <int>, // number of points on the curves
+ *  steps: <int>, // number of points for z-side extrusions / used for subdividing segements of extrude spline too
+ *  amount: <int>, // Amount
+ *
+ *  bevelEnabled: <bool>, // turn on bevel
+ *  bevelThickness: <float>, // how deep into text bevel goes
+ *  bevelSize: <float>, // how far from text outline is bevel
+ *  bevelSegments: <int>, // number of bevel layers
+ *
+ *  extrudePath: <THREE.CurvePath> // 3d spline path to extrude shape along. (creates Frames if .frames aren't defined)
+ *  frames: <THREE.TubeGeometry.FrenetFrames> // containing arrays of tangents, normals, binormals
+ *
+ *  material: <int> // material index for front and back faces
+ *  extrudeMaterial: <int> // material index for extrusion and beveled faces
+ *  uvGenerator: <Object> // object that provides UV generator functions
+ *
+ * }
+ **/
+
+THREE.ExtrudeGeometry = function ( shapes, options ) {
+
+	if ( typeof( shapes ) === "undefined" ) {
+		shapes = [];
+		return;
+	}
+
+	THREE.Geometry.call( this );
+
+	shapes = shapes instanceof Array ? shapes : [ shapes ];
+
+	this.shapebb = shapes[ shapes.length - 1 ].getBoundingBox();
+
+	this.addShapeList( shapes, options );
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+
+	// can't really use automatic vertex normals
+	// as then front and back sides get smoothed too
+	// should do separate smoothing just for sides
+
+	//this.computeVertexNormals();
+
+	//console.log( "took", ( Date.now() - startTime ) );
+
+};
+
+THREE.ExtrudeGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+THREE.ExtrudeGeometry.prototype.addShapeList = function ( shapes, options ) {
+	var sl = shapes.length;
+
+	for ( var s = 0; s < sl; s ++ ) {
+		var shape = shapes[ s ];
+		this.addShape( shape, options );
+	}
+};
+
+THREE.ExtrudeGeometry.prototype.addShape = function ( shape, options ) {
+
+	var amount = options.amount !== undefined ? options.amount : 100;
+
+	var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6; // 10
+	var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2; // 8
+	var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3;
+
+	var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false
+
+	var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;
+
+	var steps = options.steps !== undefined ? options.steps : 1;
+
+	var extrudePath = options.extrudePath;
+	var extrudePts, extrudeByPath = false;
+
+	var material = options.material;
+	var extrudeMaterial = options.extrudeMaterial;
+
+	// Use default WorldUVGenerator if no UV generators are specified.
+	var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : THREE.ExtrudeGeometry.WorldUVGenerator;
+
+	var shapebb = this.shapebb;
+	//shapebb = shape.getBoundingBox();
+
+
+
+	var splineTube, binormal, normal, position2;
+	if ( extrudePath ) {
+
+		extrudePts = extrudePath.getSpacedPoints( steps );
+
+		extrudeByPath = true;
+		bevelEnabled = false; // bevels not supported for path extrusion
+
+		// SETUP TNB variables
+
+		// Reuse TNB from TubeGeomtry for now.
+		// TODO1 - have a .isClosed in spline?
+
+		splineTube = options.frames !== undefined ? options.frames : new THREE.TubeGeometry.FrenetFrames(extrudePath, steps, false);
+
+		// console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);
+
+		binormal = new THREE.Vector3();
+		normal = new THREE.Vector3();
+		position2 = new THREE.Vector3();
+
+	}
+
+	// Safeguards if bevels are not enabled
+
+	if ( ! bevelEnabled ) {
+
+		bevelSegments = 0;
+		bevelThickness = 0;
+		bevelSize = 0;
+
+	}
+
+	// Variables initalization
+
+	var ahole, h, hl; // looping of holes
+	var scope = this;
+	var bevelPoints = [];
+
+	var shapesOffset = this.vertices.length;
+
+	var shapePoints = shape.extractPoints( curveSegments );
+
+	var vertices = shapePoints.shape;
+	var holes = shapePoints.holes;
+
+	var reverse = !THREE.Shape.Utils.isClockWise( vertices ) ;
+
+	if ( reverse ) {
+
+		vertices = vertices.reverse();
+
+		// Maybe we should also check if holes are in the opposite direction, just to be safe ...
+
+		for ( h = 0, hl = holes.length; h < hl; h ++ ) {
+
+			ahole = holes[ h ];
+
+			if ( THREE.Shape.Utils.isClockWise( ahole ) ) {
+
+				holes[ h ] = ahole.reverse();
+
+			}
+
+		}
+
+		reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)!
+
+	}
+
+
+	var faces = THREE.Shape.Utils.triangulateShape ( vertices, holes );
+
+	/* Vertices */
+
+	var contour = vertices; // vertices has all points but contour has only points of circumference
+
+	for ( h = 0, hl = holes.length;  h < hl; h ++ ) {
+
+		ahole = holes[ h ];
+
+		vertices = vertices.concat( ahole );
+
+	}
+
+
+	function scalePt2 ( pt, vec, size ) {
+
+		if ( !vec ) console.log( "die" );
+
+		return vec.clone().multiplyScalar( size ).add( pt );
+
+	}
+
+	var b, bs, t, z,
+		vert, vlen = vertices.length,
+		face, flen = faces.length,
+		cont, clen = contour.length;
+
+
+	// Find directions for point movement
+
+	var RAD_TO_DEGREES = 180 / Math.PI;
+
+
+	function getBevelVec( pt_i, pt_j, pt_k ) {
+
+		// Algorithm 2
+
+		return getBevelVec2( pt_i, pt_j, pt_k );
+
+	}
+
+	function getBevelVec1( pt_i, pt_j, pt_k ) {
+
+		var anglea = Math.atan2( pt_j.y - pt_i.y, pt_j.x - pt_i.x );
+		var angleb = Math.atan2( pt_k.y - pt_i.y, pt_k.x - pt_i.x );
+
+		if ( anglea > angleb ) {
+
+			angleb += Math.PI * 2;
+
+		}
+
+		var anglec = ( anglea + angleb ) / 2;
+
+
+		//console.log('angle1', anglea * RAD_TO_DEGREES,'angle2', angleb * RAD_TO_DEGREES, 'anglec', anglec *RAD_TO_DEGREES);
+
+		var x = - Math.cos( anglec );
+		var y = - Math.sin( anglec );
+
+		var vec = new THREE.Vector2( x, y ); //.normalize();
+
+		return vec;
+
+	}
+
+	function getBevelVec2( pt_i, pt_j, pt_k ) {
+
+		var a = THREE.ExtrudeGeometry.__v1,
+			b = THREE.ExtrudeGeometry.__v2,
+			v_hat = THREE.ExtrudeGeometry.__v3,
+			w_hat = THREE.ExtrudeGeometry.__v4,
+			p = THREE.ExtrudeGeometry.__v5,
+			q = THREE.ExtrudeGeometry.__v6,
+			v, w,
+			v_dot_w_hat, q_sub_p_dot_w_hat,
+			s, intersection;
+
+		// good reading for line-line intersection
+		// http://sputsoft.com/blog/2010/03/line-line-intersection.html
+
+		// define a as vector j->i
+		// define b as vectot k->i
+
+		a.set( pt_i.x - pt_j.x, pt_i.y - pt_j.y );
+		b.set( pt_i.x - pt_k.x, pt_i.y - pt_k.y );
+
+		// get unit vectors
+
+		v = a.normalize();
+		w = b.normalize();
+
+		// normals from pt i
+
+		v_hat.set( -v.y, v.x );
+		w_hat.set( w.y, -w.x );
+
+		// pts from i
+
+		p.copy( pt_i ).add( v_hat );
+		q.copy( pt_i ).add( w_hat );
+
+		if ( p.equals( q ) ) {
+
+			//console.log("Warning: lines are straight");
+			return w_hat.clone();
+
+		}
+
+		// Points from j, k. helps prevents points cross overover most of the time
+
+		p.copy( pt_j ).add( v_hat );
+		q.copy( pt_k ).add( w_hat );
+
+		v_dot_w_hat = v.dot( w_hat );
+		q_sub_p_dot_w_hat = q.sub( p ).dot( w_hat );
+
+		// We should not reach these conditions
+
+		if ( v_dot_w_hat === 0 ) {
+
+			console.log( "Either infinite or no solutions!" );
+
+			if ( q_sub_p_dot_w_hat === 0 ) {
+
+				console.log( "Its finite solutions." );
+
+			} else {
+
+				console.log( "Too bad, no solutions." );
+
+			}
+
+		}
+
+		s = q_sub_p_dot_w_hat / v_dot_w_hat;
+
+		if ( s < 0 ) {
+
+			// in case of emergecy, revert to algorithm 1.
+
+			return getBevelVec1( pt_i, pt_j, pt_k );
+
+		}
+
+		intersection = v.multiplyScalar( s ).add( p );
+
+		return intersection.sub( pt_i ).clone(); // Don't normalize!, otherwise sharp corners become ugly
+
+	}
+
+	var contourMovements = [];
+
+	for ( var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+
+		if ( j === il ) j = 0;
+		if ( k === il ) k = 0;
+
+		//  (j)---(i)---(k)
+		// console.log('i,j,k', i, j , k)
+
+		var pt_i = contour[ i ];
+		var pt_j = contour[ j ];
+		var pt_k = contour[ k ];
+
+		contourMovements[ i ]= getBevelVec( contour[ i ], contour[ j ], contour[ k ] );
+
+	}
+
+	var holesMovements = [], oneHoleMovements, verticesMovements = contourMovements.concat();
+
+	for ( h = 0, hl = holes.length; h < hl; h ++ ) {
+
+		ahole = holes[ h ];
+
+		oneHoleMovements = [];
+
+		for ( i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+
+			if ( j === il ) j = 0;
+			if ( k === il ) k = 0;
+
+			//  (j)---(i)---(k)
+			oneHoleMovements[ i ]= getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
+
+		}
+
+		holesMovements.push( oneHoleMovements );
+		verticesMovements = verticesMovements.concat( oneHoleMovements );
+
+	}
+
+
+	// Loop bevelSegments, 1 for the front, 1 for the back
+
+	for ( b = 0; b < bevelSegments; b ++ ) {
+	//for ( b = bevelSegments; b > 0; b -- ) {
+
+		t = b / bevelSegments;
+		z = bevelThickness * ( 1 - t );
+
+		//z = bevelThickness * t;
+		bs = bevelSize * ( Math.sin ( t * Math.PI/2 ) ) ; // curved
+		//bs = bevelSize * t ; // linear
+
+		// contract shape
+
+		for ( i = 0, il = contour.length; i < il; i ++ ) {
+
+			vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+			//vert = scalePt( contour[ i ], contourCentroid, bs, false );
+			v( vert.x, vert.y,  - z );
+
+		}
+
+		// expand holes
+
+		for ( h = 0, hl = holes.length; h < hl; h++ ) {
+
+			ahole = holes[ h ];
+			oneHoleMovements = holesMovements[ h ];
+
+			for ( i = 0, il = ahole.length; i < il; i++ ) {
+
+				vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+				//vert = scalePt( ahole[ i ], holesCentroids[ h ], bs, true );
+
+				v( vert.x, vert.y,  -z );
+
+			}
+
+		}
+
+	}
+
+	bs = bevelSize;
+
+	// Back facing vertices
+
+	for ( i = 0; i < vlen; i ++ ) {
+
+		vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+
+		if ( !extrudeByPath ) {
+
+			v( vert.x, vert.y, 0 );
+
+		} else {
+
+			// v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );
+
+			normal.copy( splineTube.normals[0] ).multiplyScalar(vert.x);
+			binormal.copy( splineTube.binormals[0] ).multiplyScalar(vert.y);
+
+			position2.copy( extrudePts[0] ).add(normal).add(binormal);
+
+			v( position2.x, position2.y, position2.z );
+
+		}
+
+	}
+
+	// Add stepped vertices...
+	// Including front facing vertices
+
+	var s;
+
+	for ( s = 1; s <= steps; s ++ ) {
+
+		for ( i = 0; i < vlen; i ++ ) {
+
+			vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+
+			if ( !extrudeByPath ) {
+
+				v( vert.x, vert.y, amount / steps * s );
+
+			} else {
+
+				// v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );
+
+				normal.copy( splineTube.normals[s] ).multiplyScalar( vert.x );
+				binormal.copy( splineTube.binormals[s] ).multiplyScalar( vert.y );
+
+				position2.copy( extrudePts[s] ).add( normal ).add( binormal );
+
+				v( position2.x, position2.y, position2.z );
+
+			}
+
+		}
+
+	}
+
+
+	// Add bevel segments planes
+
+	//for ( b = 1; b <= bevelSegments; b ++ ) {
+	for ( b = bevelSegments - 1; b >= 0; b -- ) {
+
+		t = b / bevelSegments;
+		z = bevelThickness * ( 1 - t );
+		//bs = bevelSize * ( 1-Math.sin ( ( 1 - t ) * Math.PI/2 ) );
+		bs = bevelSize * Math.sin ( t * Math.PI/2 ) ;
+
+		// contract shape
+
+		for ( i = 0, il = contour.length; i < il; i ++ ) {
+
+			vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+			v( vert.x, vert.y,  amount + z );
+
+		}
+
+		// expand holes
+
+		for ( h = 0, hl = holes.length; h < hl; h ++ ) {
+
+			ahole = holes[ h ];
+			oneHoleMovements = holesMovements[ h ];
+
+			for ( i = 0, il = ahole.length; i < il; i ++ ) {
+
+				vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+
+				if ( !extrudeByPath ) {
+
+					v( vert.x, vert.y,  amount + z );
+
+				} else {
+
+					v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z );
+
+				}
+
+			}
+
+		}
+
+	}
+
+	/* Faces */
+
+	// Top and bottom faces
+
+	buildLidFaces();
+
+	// Sides faces
+
+	buildSideFaces();
+
+
+	/////  Internal functions
+
+	function buildLidFaces() {
+
+		if ( bevelEnabled ) {
+
+			var layer = 0 ; // steps + 1
+			var offset = vlen * layer;
+
+			// Bottom faces
+
+			for ( i = 0; i < flen; i ++ ) {
+
+				face = faces[ i ];
+				f3( face[ 2 ]+ offset, face[ 1 ]+ offset, face[ 0 ] + offset, true );
+
+			}
+
+			layer = steps + bevelSegments * 2;
+			offset = vlen * layer;
+
+			// Top faces
+
+			for ( i = 0; i < flen; i ++ ) {
+
+				face = faces[ i ];
+				f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset, false );
+
+			}
+
+		} else {
+
+			// Bottom faces
+
+			for ( i = 0; i < flen; i++ ) {
+
+				face = faces[ i ];
+				f3( face[ 2 ], face[ 1 ], face[ 0 ], true );
+
+			}
+
+			// Top faces
+
+			for ( i = 0; i < flen; i ++ ) {
+
+				face = faces[ i ];
+				f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps, false );
+
+			}
+		}
+
+	}
+
+	// Create faces for the z-sides of the shape
+
+	function buildSideFaces() {
+
+		var layeroffset = 0;
+		sidewalls( contour, layeroffset );
+		layeroffset += contour.length;
+
+		for ( h = 0, hl = holes.length;  h < hl; h ++ ) {
+
+			ahole = holes[ h ];
+			sidewalls( ahole, layeroffset );
+
+			//, true
+			layeroffset += ahole.length;
+
+		}
+
+	}
+
+	function sidewalls( contour, layeroffset ) {
+
+		var j, k;
+		i = contour.length;
+
+		while ( --i >= 0 ) {
+
+			j = i;
+			k = i - 1;
+			if ( k < 0 ) k = contour.length - 1;
+
+			//console.log('b', i,j, i-1, k,vertices.length);
+
+			var s = 0, sl = steps  + bevelSegments * 2;
+
+			for ( s = 0; s < sl; s ++ ) {
+
+				var slen1 = vlen * s;
+				var slen2 = vlen * ( s + 1 );
+
+				var a = layeroffset + j + slen1,
+					b = layeroffset + k + slen1,
+					c = layeroffset + k + slen2,
+					d = layeroffset + j + slen2;
+
+				f4( a, b, c, d, contour, s, sl, j, k );
+
+			}
+		}
+
+	}
+
+
+	function v( x, y, z ) {
+
+		scope.vertices.push( new THREE.Vector3( x, y, z ) );
+
+	}
+
+	function f3( a, b, c, isBottom ) {
+
+		a += shapesOffset;
+		b += shapesOffset;
+		c += shapesOffset;
+
+		// normal, color, material
+		scope.faces.push( new THREE.Face3( a, b, c, null, null, material ) );
+
+		var uvs = isBottom ? uvgen.generateBottomUV( scope, shape, options, a, b, c ) : uvgen.generateTopUV( scope, shape, options, a, b, c );
+
+ 		scope.faceVertexUvs[ 0 ].push( uvs );
+
+	}
+
+	function f4( a, b, c, d, wallContour, stepIndex, stepsLength, contourIndex1, contourIndex2 ) {
+
+		a += shapesOffset;
+		b += shapesOffset;
+		c += shapesOffset;
+		d += shapesOffset;
+
+ 		scope.faces.push( new THREE.Face4( a, b, c, d, null, null, extrudeMaterial ) );
+
+ 		var uvs = uvgen.generateSideWallUV( scope, shape, wallContour, options, a, b, c, d,
+ 		                                    stepIndex, stepsLength, contourIndex1, contourIndex2 );
+ 		scope.faceVertexUvs[ 0 ].push( uvs );
+
+	}
+
+};
+
+THREE.ExtrudeGeometry.WorldUVGenerator = {
+
+	generateTopUV: function( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ) {
+		var ax = geometry.vertices[ indexA ].x,
+			ay = geometry.vertices[ indexA ].y,
+
+			bx = geometry.vertices[ indexB ].x,
+			by = geometry.vertices[ indexB ].y,
+
+			cx = geometry.vertices[ indexC ].x,
+			cy = geometry.vertices[ indexC ].y;
+
+		return [
+			new THREE.Vector2( ax, ay ),
+			new THREE.Vector2( bx, by ),
+			new THREE.Vector2( cx, cy )
+		];
+
+	},
+
+	generateBottomUV: function( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ) {
+
+		return this.generateTopUV( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC );
+
+	},
+
+	generateSideWallUV: function( geometry, extrudedShape, wallContour, extrudeOptions,
+	                              indexA, indexB, indexC, indexD, stepIndex, stepsLength,
+	                              contourIndex1, contourIndex2 ) {
+
+		var ax = geometry.vertices[ indexA ].x,
+			ay = geometry.vertices[ indexA ].y,
+			az = geometry.vertices[ indexA ].z,
+
+			bx = geometry.vertices[ indexB ].x,
+			by = geometry.vertices[ indexB ].y,
+			bz = geometry.vertices[ indexB ].z,
+
+			cx = geometry.vertices[ indexC ].x,
+			cy = geometry.vertices[ indexC ].y,
+			cz = geometry.vertices[ indexC ].z,
+
+			dx = geometry.vertices[ indexD ].x,
+			dy = geometry.vertices[ indexD ].y,
+			dz = geometry.vertices[ indexD ].z;
+
+		if ( Math.abs( ay - by ) < 0.01 ) {
+			return [
+				new THREE.Vector2( ax, 1 - az ),
+				new THREE.Vector2( bx, 1 - bz ),
+				new THREE.Vector2( cx, 1 - cz ),
+				new THREE.Vector2( dx, 1 - dz )
+			];
+		} else {
+			return [
+				new THREE.Vector2( ay, 1 - az ),
+				new THREE.Vector2( by, 1 - bz ),
+				new THREE.Vector2( cy, 1 - cz ),
+				new THREE.Vector2( dy, 1 - dz )
+			];
+		}
+	}
+};
+
+THREE.ExtrudeGeometry.__v1 = new THREE.Vector2();
+THREE.ExtrudeGeometry.__v2 = new THREE.Vector2();
+THREE.ExtrudeGeometry.__v3 = new THREE.Vector2();
+THREE.ExtrudeGeometry.__v4 = new THREE.Vector2();
+THREE.ExtrudeGeometry.__v5 = new THREE.Vector2();
+THREE.ExtrudeGeometry.__v6 = new THREE.Vector2();
+/**
+ * @author jonobr1 / http://jonobr1.com
+ *
+ * Creates a one-sided polygonal geometry from a path shape. Similar to
+ * ExtrudeGeometry.
+ *
+ * parameters = {
+ *
+ *	curveSegments: <int>, // number of points on the curves. NOT USED AT THE MOMENT.
+ *
+ *	material: <int> // material index for front and back faces
+ *	uvGenerator: <Object> // object that provides UV generator functions
+ *
+ * }
+ **/
+
+THREE.ShapeGeometry = function ( shapes, options ) {
+
+	THREE.Geometry.call( this );
+
+	if ( shapes instanceof Array === false ) shapes = [ shapes ];
+
+	this.shapebb = shapes[ shapes.length - 1 ].getBoundingBox();
+
+	this.addShapeList( shapes, options );
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+
+};
+
+THREE.ShapeGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * Add an array of shapes to THREE.ShapeGeometry.
+ */
+THREE.ShapeGeometry.prototype.addShapeList = function ( shapes, options ) {
+
+	for ( var i = 0, l = shapes.length; i < l; i++ ) {
+
+		this.addShape( shapes[ i ], options );
+
+	}
+
+	return this;
+
+};
+
+/**
+ * Adds a shape to THREE.ShapeGeometry, based on THREE.ExtrudeGeometry.
+ */
+THREE.ShapeGeometry.prototype.addShape = function ( shape, options ) {
+
+	if ( options === undefined ) options = {};
+	var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;
+
+	var material = options.material;
+	var uvgen = options.UVGenerator === undefined ? THREE.ExtrudeGeometry.WorldUVGenerator : options.UVGenerator;
+
+	var shapebb = this.shapebb;
+
+	//
+
+	var i, l, hole, s;
+
+	var shapesOffset = this.vertices.length;
+	var shapePoints = shape.extractPoints( curveSegments );
+
+	var vertices = shapePoints.shape;
+	var holes = shapePoints.holes;
+
+	var reverse = !THREE.Shape.Utils.isClockWise( vertices );
+
+	if ( reverse ) {
+
+		vertices = vertices.reverse();
+
+		// Maybe we should also check if holes are in the opposite direction, just to be safe...
+
+		for ( i = 0, l = holes.length; i < l; i++ ) {
+
+			hole = holes[ i ];
+
+			if ( THREE.Shape.Utils.isClockWise( hole ) ) {
+
+				holes[ i ] = hole.reverse();
+
+			}
+
+		}
+
+		reverse = false;
+
+	}
+
+	var faces = THREE.Shape.Utils.triangulateShape( vertices, holes );
+
+	// Vertices
+
+	var contour = vertices;
+
+	for ( i = 0, l = holes.length; i < l; i++ ) {
+
+		hole = holes[ i ];
+		vertices = vertices.concat( hole );
+
+	}
+
+	//
+
+	var vert, vlen = vertices.length;
+	var face, flen = faces.length;
+	var cont, clen = contour.length;
+
+	for ( i = 0; i < vlen; i++ ) {
+
+		vert = vertices[ i ];
+
+		this.vertices.push( new THREE.Vector3( vert.x, vert.y, 0 ) );
+
+	}
+
+	for ( i = 0; i < flen; i++ ) {
+
+		face = faces[ i ];
+
+		var a = face[ 0 ] + shapesOffset;
+		var b = face[ 1 ] + shapesOffset;
+		var c = face[ 2 ] + shapesOffset;
+
+		this.faces.push( new THREE.Face3( a, b, c, null, null, material ) );
+		this.faceVertexUvs[ 0 ].push( uvgen.generateBottomUV( this, shape, options, a, b, c ) );
+
+	}
+
+};
+/**
+ * @author astrodud / http://astrodud.isgreat.org/
+ * @author zz85 / https://github.com/zz85
+ * @author bhouston / http://exocortex.com
+ */
+
+// points - to create a closed torus, one must use a set of points 
+//    like so: [ a, b, c, d, a ], see first is the same as last.
+// segments - the number of circumference segments to create
+// phiStart - the starting radian
+// phiLength - the radian (0 to 2*PI) range of the lathed section
+//    2*pi is a closed lathe, less than 2PI is a portion.
+THREE.LatheGeometry = function ( points, segments, phiStart, phiLength ) {
+
+	THREE.Geometry.call( this );
+
+	segments = segments || 12;
+	phiStart = phiStart || 0;
+	phiLength = phiLength || 2 * Math.PI;
+
+	var inversePointLength = 1.0 / ( points.length - 1 );
+	var inverseSegments = 1.0 / segments;
+
+	for ( var i = 0, il = segments; i <= il; i ++ ) {
+
+		var phi = phiStart + i * inverseSegments * phiLength;
+
+		var c = Math.cos( phi ),
+			s = Math.sin( phi );
+
+		for ( var j = 0, jl = points.length; j < jl; j ++ ) {
+
+			var pt = points[ j ];
+
+			var vertex = new THREE.Vector3();
+
+			vertex.x = c * pt.x - s * pt.y;
+			vertex.y = s * pt.x + c * pt.y;
+			vertex.z = pt.z;
+
+			this.vertices.push( vertex );
+
+		}
+
+	}
+
+	var np = points.length;
+
+	for ( var i = 0, il = segments; i < il; i ++ ) {
+
+		for ( var j = 0, jl = points.length - 1; j < jl; j ++ ) {
+
+			var base = j + np * i;
+			var a = base;
+			var b = base + np;
+			var c = base + 1 + np;
+			var d = base + 1;
+
+			this.faces.push( new THREE.Face4( a, b, c, d ) );
+
+			var u0 = i * inverseSegments;
+			var v0 = j * inversePointLength;
+			var u1 = u0 + inverseSegments;
+			var v1 = v0 + inversePointLength;
+
+			this.faceVertexUvs[ 0 ].push( [
+
+				new THREE.Vector2( u0, v0 ), 
+				new THREE.Vector2( u1, v0 ),
+				new THREE.Vector2( u1, v1 ),
+				new THREE.Vector2( u0, v1 )
+
+			] );
+
+		}
+
+	}
+
+	this.mergeVertices();
+	this.computeCentroids();
+	this.computeFaceNormals();
+	this.computeVertexNormals();
+
+};
+
+THREE.LatheGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Plane.as
+ */
+
+THREE.PlaneGeometry = function ( width, height, widthSegments, heightSegments ) {
+
+	THREE.Geometry.call( this );
+
+	this.width = width;
+	this.height = height;
+
+	this.widthSegments = widthSegments || 1;
+	this.heightSegments = heightSegments || 1;
+
+	var ix, iz;
+	var width_half = width / 2;
+	var height_half = height / 2;
+
+	var gridX = this.widthSegments;
+	var gridZ = this.heightSegments;
+
+	var gridX1 = gridX + 1;
+	var gridZ1 = gridZ + 1;
+
+	var segment_width = this.width / gridX;
+	var segment_height = this.height / gridZ;
+
+	var normal = new THREE.Vector3( 0, 0, 1 );
+
+	for ( iz = 0; iz < gridZ1; iz ++ ) {
+
+		for ( ix = 0; ix < gridX1; ix ++ ) {
+
+			var x = ix * segment_width - width_half;
+			var y = iz * segment_height - height_half;
+
+			this.vertices.push( new THREE.Vector3( x, - y, 0 ) );
+
+		}
+
+	}
+
+	for ( iz = 0; iz < gridZ; iz ++ ) {
+
+		for ( ix = 0; ix < gridX; ix ++ ) {
+
+			var a = ix + gridX1 * iz;
+			var b = ix + gridX1 * ( iz + 1 );
+			var c = ( ix + 1 ) + gridX1 * ( iz + 1 );
+			var d = ( ix + 1 ) + gridX1 * iz;
+
+			var face = new THREE.Face4( a, b, c, d );
+			face.normal.copy( normal );
+			face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone(), normal.clone() );
+
+			this.faces.push( face );
+			this.faceVertexUvs[ 0 ].push( [
+				new THREE.Vector2( ix / gridX, 1 - iz / gridZ ),
+				new THREE.Vector2( ix / gridX, 1 - ( iz + 1 ) / gridZ ),
+				new THREE.Vector2( ( ix + 1 ) / gridX, 1 - ( iz + 1 ) / gridZ ),
+				new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iz / gridZ )
+			] );
+
+		}
+
+	}
+
+	this.computeCentroids();
+
+};
+
+THREE.PlaneGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author Kaleb Murphy
+ */
+
+THREE.RingGeometry = function ( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) {
+
+	THREE.Geometry.call( this );
+
+	innerRadius = innerRadius || 0;
+	outerRadius = outerRadius || 50;
+
+	thetaStart = thetaStart !== undefined ? thetaStart : 0;
+	thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;
+
+	thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8;
+	phiSegments = phiSegments !== undefined ? Math.max( 3, phiSegments ) : 8;
+
+	var i, o, uvs = [], radius = innerRadius, radiusStep = ( ( outerRadius - innerRadius ) / phiSegments );
+
+	for ( i = 0; i <= phiSegments; i ++ ) { // concentric circles inside ring
+
+		for ( o = 0; o <= thetaSegments; o ++ ) { // number of segments per circle
+
+			var vertex = new THREE.Vector3();
+			var segment = thetaStart + o / thetaSegments * thetaLength;
+
+			vertex.x = radius * Math.cos( segment );
+			vertex.y = radius * Math.sin( segment );
+
+			this.vertices.push( vertex );
+			uvs.push( new THREE.Vector2( ( vertex.x / radius + 1 ) / 2, - ( vertex.y / radius + 1 ) / 2 + 1 ) );
+		}
+
+		radius += radiusStep;
+
+	}
+
+	var n = new THREE.Vector3( 0, 0, 1 );
+
+	for ( i = 0; i < phiSegments; i ++ ) { // concentric circles inside ring
+
+		var thetaSegment = i * thetaSegments;
+
+		for ( o = 0; o <= thetaSegments; o ++ ) { // number of segments per circle
+
+			var segment = o + thetaSegment;
+
+			var v1 = segment + i;
+			var v2 = segment + thetaSegments + i;
+			var v3 = segment + thetaSegments + 1 + i;
+
+			this.faces.push( new THREE.Face3( v1, v2, v3, [ n, n, n ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ], uvs[ v2 ], uvs[ v3 ] ]);
+
+			v1 = segment + i;
+			v2 = segment + thetaSegments + 1 + i;
+			v3 = segment + 1 + i;
+
+			this.faces.push( new THREE.Face3( v1, v2, v3, [ n, n, n ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ], uvs[ v2 ], uvs[ v3 ] ]);
+
+		}
+	}
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+
+	this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+};
+
+THREE.RingGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.SphereGeometry = function ( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) {
+
+	THREE.Geometry.call( this );
+
+	this.radius = radius = radius || 50;
+
+	this.widthSegments = widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 );
+	this.heightSegments = heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 );
+
+	this.phiStart = phiStart = phiStart !== undefined ? phiStart : 0;
+	this.phiLength = phiLength = phiLength !== undefined ? phiLength : Math.PI * 2;
+
+	this.thetaStart = thetaStart = thetaStart !== undefined ? thetaStart : 0;
+	this.thetaLength = thetaLength = thetaLength !== undefined ? thetaLength : Math.PI;
+
+	var x, y, vertices = [], uvs = [];
+
+	for ( y = 0; y <= heightSegments; y ++ ) {
+
+		var verticesRow = [];
+		var uvsRow = [];
+
+		for ( x = 0; x <= widthSegments; x ++ ) {
+
+			var u = x / widthSegments;
+			var v = y / heightSegments;
+
+			var vertex = new THREE.Vector3();
+			vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
+			vertex.y = radius * Math.cos( thetaStart + v * thetaLength );
+			vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
+
+			this.vertices.push( vertex );
+
+			verticesRow.push( this.vertices.length - 1 );
+			uvsRow.push( new THREE.Vector2( u, 1 - v ) );
+
+		}
+
+		vertices.push( verticesRow );
+		uvs.push( uvsRow );
+
+	}
+
+	for ( y = 0; y < this.heightSegments; y ++ ) {
+
+		for ( x = 0; x < this.widthSegments; x ++ ) {
+
+			var v1 = vertices[ y ][ x + 1 ];
+			var v2 = vertices[ y ][ x ];
+			var v3 = vertices[ y + 1 ][ x ];
+			var v4 = vertices[ y + 1 ][ x + 1 ];
+
+			var n1 = this.vertices[ v1 ].clone().normalize();
+			var n2 = this.vertices[ v2 ].clone().normalize();
+			var n3 = this.vertices[ v3 ].clone().normalize();
+			var n4 = this.vertices[ v4 ].clone().normalize();
+
+			var uv1 = uvs[ y ][ x + 1 ].clone();
+			var uv2 = uvs[ y ][ x ].clone();
+			var uv3 = uvs[ y + 1 ][ x ].clone();
+			var uv4 = uvs[ y + 1 ][ x + 1 ].clone();
+
+			if ( Math.abs( this.vertices[ v1 ].y ) === this.radius ) {
+
+				this.faces.push( new THREE.Face3( v1, v3, v4, [ n1, n3, n4 ] ) );
+				this.faceVertexUvs[ 0 ].push( [ uv1, uv3, uv4 ] );
+
+			} else if ( Math.abs( this.vertices[ v3 ].y ) === this.radius ) {
+
+				this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) );
+				this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] );
+
+			} else {
+
+				this.faces.push( new THREE.Face4( v1, v2, v3, v4, [ n1, n2, n3, n4 ] ) );
+				this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3, uv4 ] );
+
+			}
+
+		}
+
+	}
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+
+	this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+};
+
+THREE.SphereGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * For creating 3D text geometry in three.js
+ *
+ * Text = 3D Text
+ *
+ * parameters = {
+ *  size: 			<float>, 	// size of the text
+ *  height: 		<float>, 	// thickness to extrude text
+ *  curveSegments: 	<int>,		// number of points on the curves
+ *
+ *  font: 			<string>,		// font name
+ *  weight: 		<string>,		// font weight (normal, bold)
+ *  style: 			<string>,		// font style  (normal, italics)
+ *
+ *  bevelEnabled:	<bool>,			// turn on bevel
+ *  bevelThickness: <float>, 		// how deep into text bevel goes
+ *  bevelSize:		<float>, 		// how far from text outline is bevel
+ *  }
+ *
+ */
+
+/*	Usage Examples
+
+	// TextGeometry wrapper
+
+	var text3d = new TextGeometry( text, options );
+
+	// Complete manner
+
+	var textShapes = THREE.FontUtils.generateShapes( text, options );
+	var text3d = new ExtrudeGeometry( textShapes, options );
+
+*/
+
+
+THREE.TextGeometry = function ( text, parameters ) {
+
+	parameters = parameters || {};
+
+	var textShapes = THREE.FontUtils.generateShapes( text, parameters );
+
+	// translate parameters to ExtrudeGeometry API
+
+	parameters.amount = parameters.height !== undefined ? parameters.height : 50;
+
+	// defaults
+
+	if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10;
+	if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8;
+	if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false;
+
+	THREE.ExtrudeGeometry.call( this, textShapes, parameters );
+
+};
+
+THREE.TextGeometry.prototype = Object.create( THREE.ExtrudeGeometry.prototype );
+/**
+ * @author oosmoxiecode
+ * @author mrdoob / http://mrdoob.com/
+ * based on http://code.google.com/p/away3d/source/browse/trunk/fp10/Away3DLite/src/away3dlite/primitives/Torus.as?r=2888
+ */
+
+THREE.TorusGeometry = function ( radius, tube, radialSegments, tubularSegments, arc ) {
+
+	THREE.Geometry.call( this );
+
+	var scope = this;
+
+	this.radius = radius || 100;
+	this.tube = tube || 40;
+	this.radialSegments = radialSegments || 8;
+	this.tubularSegments = tubularSegments || 6;
+	this.arc = arc || Math.PI * 2;
+
+	var center = new THREE.Vector3(), uvs = [], normals = [];
+
+	for ( var j = 0; j <= this.radialSegments; j ++ ) {
+
+		for ( var i = 0; i <= this.tubularSegments; i ++ ) {
+
+			var u = i / this.tubularSegments * this.arc;
+			var v = j / this.radialSegments * Math.PI * 2;
+
+			center.x = this.radius * Math.cos( u );
+			center.y = this.radius * Math.sin( u );
+
+			var vertex = new THREE.Vector3();
+			vertex.x = ( this.radius + this.tube * Math.cos( v ) ) * Math.cos( u );
+			vertex.y = ( this.radius + this.tube * Math.cos( v ) ) * Math.sin( u );
+			vertex.z = this.tube * Math.sin( v );
+
+			this.vertices.push( vertex );
+
+			uvs.push( new THREE.Vector2( i / this.tubularSegments, j / this.radialSegments ) );
+			normals.push( vertex.clone().sub( center ).normalize() );
+
+		}
+	}
+
+
+	for ( var j = 1; j <= this.radialSegments; j ++ ) {
+
+		for ( var i = 1; i <= this.tubularSegments; i ++ ) {
+
+			var a = ( this.tubularSegments + 1 ) * j + i - 1;
+			var b = ( this.tubularSegments + 1 ) * ( j - 1 ) + i - 1;
+			var c = ( this.tubularSegments + 1 ) * ( j - 1 ) + i;
+			var d = ( this.tubularSegments + 1 ) * j + i;
+
+			var face = new THREE.Face4( a, b, c, d, [ normals[ a ], normals[ b ], normals[ c ], normals[ d ] ] );
+			face.normal.add( normals[ a ] );
+			face.normal.add( normals[ b ] );
+			face.normal.add( normals[ c ] );
+			face.normal.add( normals[ d ] );
+			face.normal.normalize();
+
+			this.faces.push( face );
+
+			this.faceVertexUvs[ 0 ].push( [ uvs[ a ].clone(), uvs[ b ].clone(), uvs[ c ].clone(), uvs[ d ].clone() ] );
+		}
+
+	}
+
+	this.computeCentroids();
+
+};
+
+THREE.TorusGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author oosmoxiecode
+ * based on http://code.google.com/p/away3d/source/browse/trunk/fp10/Away3D/src/away3d/primitives/TorusKnot.as?spec=svn2473&r=2473
+ */
+
+THREE.TorusKnotGeometry = function ( radius, tube, radialSegments, tubularSegments, p, q, heightScale ) {
+
+	THREE.Geometry.call( this );
+
+	var scope = this;
+
+	this.radius = radius || 100;
+	this.tube = tube || 40;
+	this.radialSegments = radialSegments || 64;
+	this.tubularSegments = tubularSegments || 8;
+	this.p = p || 2;
+	this.q = q || 3;
+	this.heightScale = heightScale || 1;
+	this.grid = new Array( this.radialSegments );
+
+	var tang = new THREE.Vector3();
+	var n = new THREE.Vector3();
+	var bitan = new THREE.Vector3();
+
+	for ( var i = 0; i < this.radialSegments; ++ i ) {
+
+		this.grid[ i ] = new Array( this.tubularSegments );
+
+		for ( var j = 0; j < this.tubularSegments; ++ j ) {
+
+			var u = i / this.radialSegments * 2 * this.p * Math.PI;
+			var v = j / this.tubularSegments * 2 * Math.PI;
+			var p1 = getPos( u, v, this.q, this.p, this.radius, this.heightScale );
+			var p2 = getPos( u + 0.01, v, this.q, this.p, this.radius, this.heightScale );
+			var cx, cy;
+
+			tang.subVectors( p2, p1 );
+			n.addVectors( p2, p1 );
+
+			bitan.crossVectors( tang, n );
+			n.crossVectors( bitan, tang );
+			bitan.normalize();
+			n.normalize();
+
+			cx = - this.tube * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
+			cy = this.tube * Math.sin( v );
+
+			p1.x += cx * n.x + cy * bitan.x;
+			p1.y += cx * n.y + cy * bitan.y;
+			p1.z += cx * n.z + cy * bitan.z;
+
+			this.grid[ i ][ j ] = vert( p1.x, p1.y, p1.z );
+
+		}
+
+	}
+
+	for ( var i = 0; i < this.radialSegments; ++ i ) {
+
+		for ( var j = 0; j < this.tubularSegments; ++ j ) {
+
+			var ip = ( i + 1 ) % this.radialSegments;
+			var jp = ( j + 1 ) % this.tubularSegments;
+
+			var a = this.grid[ i ][ j ];
+			var b = this.grid[ ip ][ j ];
+			var c = this.grid[ ip ][ jp ];
+			var d = this.grid[ i ][ jp ];
+
+			var uva = new THREE.Vector2( i / this.radialSegments, j / this.tubularSegments );
+			var uvb = new THREE.Vector2( ( i + 1 ) / this.radialSegments, j / this.tubularSegments );
+			var uvc = new THREE.Vector2( ( i + 1 ) / this.radialSegments, ( j + 1 ) / this.tubularSegments );
+			var uvd = new THREE.Vector2( i / this.radialSegments, ( j + 1 ) / this.tubularSegments );
+
+			this.faces.push( new THREE.Face4( a, b, c, d ) );
+			this.faceVertexUvs[ 0 ].push( [ uva,uvb,uvc, uvd ] );
+
+		}
+	}
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+	this.computeVertexNormals();
+
+	function vert( x, y, z ) {
+
+		return scope.vertices.push( new THREE.Vector3( x, y, z ) ) - 1;
+
+	}
+
+	function getPos( u, v, in_q, in_p, radius, heightScale ) {
+
+		var cu = Math.cos( u );
+		var cv = Math.cos( v );
+		var su = Math.sin( u );
+		var quOverP = in_q / in_p * u;
+		var cs = Math.cos( quOverP );
+
+		var tx = radius * ( 2 + cs ) * 0.5 * cu;
+		var ty = radius * ( 2 + cs ) * su * 0.5;
+		var tz = heightScale * radius * Math.sin( quOverP ) * 0.5;
+
+		return new THREE.Vector3( tx, ty, tz );
+
+	}
+
+};
+
+THREE.TorusKnotGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author WestLangley / https://github.com/WestLangley
+ * @author zz85 / https://github.com/zz85
+ * @author miningold / https://github.com/miningold
+ *
+ * Modified from the TorusKnotGeometry by @oosmoxiecode
+ *
+ * Creates a tube which extrudes along a 3d spline
+ *
+ * Uses parallel transport frames as described in
+ * http://www.cs.indiana.edu/pub/techreports/TR425.pdf
+ */
+
+THREE.TubeGeometry = function( path, segments, radius, radiusSegments, closed, debug ) {
+
+	THREE.Geometry.call( this );
+
+	this.path = path;
+	this.segments = segments || 64;
+	this.radius = radius || 1;
+	this.radiusSegments = radiusSegments || 8;
+	this.closed = closed || false;
+
+	if ( debug ) this.debug = new THREE.Object3D();
+
+	this.grid = [];
+
+	var scope = this,
+
+		tangent,
+		normal,
+		binormal,
+
+		numpoints = this.segments + 1,
+
+		x, y, z,
+		tx, ty, tz,
+		u, v,
+
+		cx, cy,
+		pos, pos2 = new THREE.Vector3(),
+		i, j,
+		ip, jp,
+		a, b, c, d,
+		uva, uvb, uvc, uvd;
+
+	var frames = new THREE.TubeGeometry.FrenetFrames( this.path, this.segments, this.closed ),
+		tangents = frames.tangents,
+		normals = frames.normals,
+		binormals = frames.binormals;
+
+	// proxy internals
+	this.tangents = tangents;
+	this.normals = normals;
+	this.binormals = binormals;
+
+	function vert( x, y, z ) {
+
+		return scope.vertices.push( new THREE.Vector3( x, y, z ) ) - 1;
+
+	}
+
+
+	// consruct the grid
+
+	for ( i = 0; i < numpoints; i++ ) {
+
+		this.grid[ i ] = [];
+
+		u = i / ( numpoints - 1 );
+
+		pos = path.getPointAt( u );
+
+		tangent = tangents[ i ];
+		normal = normals[ i ];
+		binormal = binormals[ i ];
+
+		if ( this.debug ) {
+
+			this.debug.add( new THREE.ArrowHelper(tangent, pos, radius, 0x0000ff ) );
+			this.debug.add( new THREE.ArrowHelper(normal, pos, radius, 0xff0000 ) );
+			this.debug.add( new THREE.ArrowHelper(binormal, pos, radius, 0x00ff00 ) );
+
+		}
+
+		for ( j = 0; j < this.radiusSegments; j++ ) {
+
+			v = j / this.radiusSegments * 2 * Math.PI;
+
+			cx = -this.radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
+			cy = this.radius * Math.sin( v );
+
+			pos2.copy( pos );
+			pos2.x += cx * normal.x + cy * binormal.x;
+			pos2.y += cx * normal.y + cy * binormal.y;
+			pos2.z += cx * normal.z + cy * binormal.z;
+
+			this.grid[ i ][ j ] = vert( pos2.x, pos2.y, pos2.z );
+
+		}
+	}
+
+
+	// construct the mesh
+
+	for ( i = 0; i < this.segments; i++ ) {
+
+		for ( j = 0; j < this.radiusSegments; j++ ) {
+
+			ip = ( this.closed ) ? (i + 1) % this.segments : i + 1;
+			jp = (j + 1) % this.radiusSegments;
+
+			a = this.grid[ i ][ j ];		// *** NOT NECESSARILY PLANAR ! ***
+			b = this.grid[ ip ][ j ];
+			c = this.grid[ ip ][ jp ];
+			d = this.grid[ i ][ jp ];
+
+			uva = new THREE.Vector2( i / this.segments, j / this.radiusSegments );
+			uvb = new THREE.Vector2( ( i + 1 ) / this.segments, j / this.radiusSegments );
+			uvc = new THREE.Vector2( ( i + 1 ) / this.segments, ( j + 1 ) / this.radiusSegments );
+			uvd = new THREE.Vector2( i / this.segments, ( j + 1 ) / this.radiusSegments );
+
+			this.faces.push( new THREE.Face4( a, b, c, d ) );
+			this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvc, uvd ] );
+
+		}
+	}
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+	this.computeVertexNormals();
+
+};
+
+THREE.TubeGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+
+// For computing of Frenet frames, exposing the tangents, normals and binormals the spline
+THREE.TubeGeometry.FrenetFrames = function(path, segments, closed) {
+
+	var	tangent = new THREE.Vector3(),
+		normal = new THREE.Vector3(),
+		binormal = new THREE.Vector3(),
+
+		tangents = [],
+		normals = [],
+		binormals = [],
+
+		vec = new THREE.Vector3(),
+		mat = new THREE.Matrix4(),
+
+		numpoints = segments + 1,
+		theta,
+		epsilon = 0.0001,
+		smallest,
+
+		tx, ty, tz,
+		i, u, v;
+
+
+	// expose internals
+	this.tangents = tangents;
+	this.normals = normals;
+	this.binormals = binormals;
+
+	// compute the tangent vectors for each segment on the path
+
+	for ( i = 0; i < numpoints; i++ ) {
+
+		u = i / ( numpoints - 1 );
+
+		tangents[ i ] = path.getTangentAt( u );
+		tangents[ i ].normalize();
+
+	}
+
+	initialNormal3();
+
+	function initialNormal1(lastBinormal) {
+		// fixed start binormal. Has dangers of 0 vectors
+		normals[ 0 ] = new THREE.Vector3();
+		binormals[ 0 ] = new THREE.Vector3();
+		if (lastBinormal===undefined) lastBinormal = new THREE.Vector3( 0, 0, 1 );
+		normals[ 0 ].crossVectors( lastBinormal, tangents[ 0 ] ).normalize();
+		binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ).normalize();
+	}
+
+	function initialNormal2() {
+
+		// This uses the Frenet-Serret formula for deriving binormal
+		var t2 = path.getTangentAt( epsilon );
+
+		normals[ 0 ] = new THREE.Vector3().subVectors( t2, tangents[ 0 ] ).normalize();
+		binormals[ 0 ] = new THREE.Vector3().crossVectors( tangents[ 0 ], normals[ 0 ] );
+
+		normals[ 0 ].crossVectors( binormals[ 0 ], tangents[ 0 ] ).normalize(); // last binormal x tangent
+		binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ).normalize();
+
+	}
+
+	function initialNormal3() {
+		// select an initial normal vector perpenicular to the first tangent vector,
+		// and in the direction of the smallest tangent xyz component
+
+		normals[ 0 ] = new THREE.Vector3();
+		binormals[ 0 ] = new THREE.Vector3();
+		smallest = Number.MAX_VALUE;
+		tx = Math.abs( tangents[ 0 ].x );
+		ty = Math.abs( tangents[ 0 ].y );
+		tz = Math.abs( tangents[ 0 ].z );
+
+		if ( tx <= smallest ) {
+			smallest = tx;
+			normal.set( 1, 0, 0 );
+		}
+
+		if ( ty <= smallest ) {
+			smallest = ty;
+			normal.set( 0, 1, 0 );
+		}
+
+		if ( tz <= smallest ) {
+			normal.set( 0, 0, 1 );
+		}
+
+		vec.crossVectors( tangents[ 0 ], normal ).normalize();
+
+		normals[ 0 ].crossVectors( tangents[ 0 ], vec );
+		binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );
+	}
+
+
+	// compute the slowly-varying normal and binormal vectors for each segment on the path
+
+	for ( i = 1; i < numpoints; i++ ) {
+
+		normals[ i ] = normals[ i-1 ].clone();
+
+		binormals[ i ] = binormals[ i-1 ].clone();
+
+		vec.crossVectors( tangents[ i-1 ], tangents[ i ] );
+
+		if ( vec.length() > epsilon ) {
+
+			vec.normalize();
+
+			theta = Math.acos( tangents[ i-1 ].dot( tangents[ i ] ) );
+
+			normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );
+
+		}
+
+		binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+
+	}
+
+
+	// if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
+
+	if ( closed ) {
+
+		theta = Math.acos( normals[ 0 ].dot( normals[ numpoints-1 ] ) );
+		theta /= ( numpoints - 1 );
+
+		if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ numpoints-1 ] ) ) > 0 ) {
+
+			theta = -theta;
+
+		}
+
+		for ( i = 1; i < numpoints; i++ ) {
+
+			// twist a little...
+			normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );
+			binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+
+		}
+
+	}
+};
+/**
+ * @author clockworkgeek / https://github.com/clockworkgeek
+ * @author timothypratley / https://github.com/timothypratley
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.PolyhedronGeometry = function ( vertices, faces, radius, detail ) {
+
+	THREE.Geometry.call( this );
+
+	radius = radius || 1;
+	detail = detail || 0;
+
+	var that = this;
+
+	for ( var i = 0, l = vertices.length; i < l; i ++ ) {
+
+		prepare( new THREE.Vector3( vertices[ i ][ 0 ], vertices[ i ][ 1 ], vertices[ i ][ 2 ] ) );
+
+	}
+
+	var midpoints = [], p = this.vertices;
+
+	var f = [];
+	for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+		var v1 = p[ faces[ i ][ 0 ] ];
+		var v2 = p[ faces[ i ][ 1 ] ];
+		var v3 = p[ faces[ i ][ 2 ] ];
+
+		f[ i ] = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ] );
+
+	}
+
+	for ( var i = 0, l = f.length; i < l; i ++ ) {
+
+		subdivide(f[ i ], detail);
+
+	}
+
+
+	// Handle case when face straddles the seam
+
+	for ( var i = 0, l = this.faceVertexUvs[ 0 ].length; i < l; i ++ ) {
+
+		var uvs = this.faceVertexUvs[ 0 ][ i ];
+
+		var x0 = uvs[ 0 ].x;
+		var x1 = uvs[ 1 ].x;
+		var x2 = uvs[ 2 ].x;
+
+		var max = Math.max( x0, Math.max( x1, x2 ) );
+		var min = Math.min( x0, Math.min( x1, x2 ) );
+
+		if ( max > 0.9 && min < 0.1 ) { // 0.9 is somewhat arbitrary
+
+			if ( x0 < 0.2 ) uvs[ 0 ].x += 1;
+			if ( x1 < 0.2 ) uvs[ 1 ].x += 1;
+			if ( x2 < 0.2 ) uvs[ 2 ].x += 1;
+
+		}
+
+	}
+
+
+	// Merge vertices
+
+	this.mergeVertices();
+
+
+	// Apply radius
+
+	for ( var i = 0, l = this.vertices.length; i < l; i ++ ) {
+
+		this.vertices[ i ].multiplyScalar( radius );
+
+	}
+
+
+	// Project vector onto sphere's surface
+
+	function prepare( vector ) {
+
+		var vertex = vector.normalize().clone();
+		vertex.index = that.vertices.push( vertex ) - 1;
+
+		// Texture coords are equivalent to map coords, calculate angle and convert to fraction of a circle.
+
+		var u = azimuth( vector ) / 2 / Math.PI + 0.5;
+		var v = inclination( vector ) / Math.PI + 0.5;
+		vertex.uv = new THREE.Vector2( u, 1 - v );
+
+		return vertex;
+
+	}
+
+
+	// Approximate a curved face with recursively sub-divided triangles.
+
+	function make( v1, v2, v3 ) {
+
+		var face = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ] );
+		face.centroid.add( v1 ).add( v2 ).add( v3 ).divideScalar( 3 );
+		face.normal.copy( face.centroid ).normalize();
+		that.faces.push( face );
+
+		var azi = azimuth( face.centroid );
+
+		that.faceVertexUvs[ 0 ].push( [
+			correctUV( v1.uv, v1, azi ),
+			correctUV( v2.uv, v2, azi ),
+			correctUV( v3.uv, v3, azi )
+		] );
+
+	}
+
+
+	// Analytically subdivide a face to the required detail level.
+
+	function subdivide(face, detail ) {
+
+		var cols = Math.pow(2, detail);
+		var cells = Math.pow(4, detail);
+		var a = prepare( that.vertices[ face.a ] );
+		var b = prepare( that.vertices[ face.b ] );
+		var c = prepare( that.vertices[ face.c ] );
+		var v = [];
+
+		// Construct all of the vertices for this subdivision.
+
+		for ( var i = 0 ; i <= cols; i ++ ) {
+
+			v[ i ] = [];
+
+			var aj = prepare( a.clone().lerp( c, i / cols ) );
+			var bj = prepare( b.clone().lerp( c, i / cols ) );
+			var rows = cols - i;
+
+			for ( var j = 0; j <= rows; j ++) {
+
+				if ( j == 0 && i == cols ) {
+
+					v[ i ][ j ] = aj;
+
+				} else {
+
+					v[ i ][ j ] = prepare( aj.clone().lerp( bj, j / rows ) );
+
+				}
+
+			}
+
+		}
+
+		// Construct all of the faces.
+
+		for ( var i = 0; i < cols ; i ++ ) {
+
+			for ( var j = 0; j < 2 * (cols - i) - 1; j ++ ) {
+
+				var k = Math.floor( j / 2 );
+
+				if ( j % 2 == 0 ) {
+
+					make(
+						v[ i ][ k + 1],
+						v[ i + 1 ][ k ],
+						v[ i ][ k ]
+					);
+
+				} else {
+
+					make(
+						v[ i ][ k + 1 ],
+						v[ i + 1][ k + 1],
+						v[ i + 1 ][ k ]
+					);
+
+				}
+
+			}
+
+		}
+
+	}
+
+
+	// Angle around the Y axis, counter-clockwise when looking from above.
+
+	function azimuth( vector ) {
+
+		return Math.atan2( vector.z, -vector.x );
+
+	}
+
+
+	// Angle above the XZ plane.
+
+	function inclination( vector ) {
+
+		return Math.atan2( -vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) );
+
+	}
+
+
+	// Texture fixing helper. Spheres have some odd behaviours.
+
+	function correctUV( uv, vector, azimuth ) {
+
+		if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) uv = new THREE.Vector2( uv.x - 1, uv.y );
+		if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) uv = new THREE.Vector2( azimuth / 2 / Math.PI + 0.5, uv.y );
+		return uv.clone();
+
+	}
+
+	this.computeCentroids();
+
+	this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+};
+
+THREE.PolyhedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author timothypratley / https://github.com/timothypratley
+ */
+
+THREE.IcosahedronGeometry = function ( radius, detail ) {
+
+	this.radius = radius;
+	this.detail = detail;
+
+	var t = ( 1 + Math.sqrt( 5 ) ) / 2;
+
+	var vertices = [
+		[ -1,  t,  0 ], [  1, t, 0 ], [ -1, -t,  0 ], [  1, -t,  0 ],
+		[  0, -1,  t ], [  0, 1, t ], [  0, -1, -t ], [  0,  1, -t ],
+		[  t,  0, -1 ], [  t, 0, 1 ], [ -t,  0, -1 ], [ -t,  0,  1 ]
+	];
+
+	var faces = [
+		[ 0, 11,  5 ], [ 0,  5,  1 ], [  0,  1,  7 ], [  0,  7, 10 ], [  0, 10, 11 ],
+		[ 1,  5,  9 ], [ 5, 11,  4 ], [ 11, 10,  2 ], [ 10,  7,  6 ], [  7,  1,  8 ],
+		[ 3,  9,  4 ], [ 3,  4,  2 ], [  3,  2,  6 ], [  3,  6,  8 ], [  3,  8,  9 ],
+		[ 4,  9,  5 ], [ 2,  4, 11 ], [  6,  2, 10 ], [  8,  6,  7 ], [  9,  8,  1 ]
+	];
+
+	THREE.PolyhedronGeometry.call( this, vertices, faces, radius, detail );
+
+};
+
+THREE.IcosahedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author timothypratley / https://github.com/timothypratley
+ */
+
+THREE.OctahedronGeometry = function ( radius, detail ) {
+
+	var vertices = [
+		[ 1, 0, 0 ], [ -1, 0, 0 ], [ 0, 1, 0 ], [ 0, -1, 0 ], [ 0, 0, 1 ], [ 0, 0, -1 ]
+	];
+
+	var faces = [
+		[ 0, 2, 4 ], [ 0, 4, 3 ], [ 0, 3, 5 ], [ 0, 5, 2 ], [ 1, 2, 5 ], [ 1, 5, 3 ], [ 1, 3, 4 ], [ 1, 4, 2 ]
+	];
+
+	THREE.PolyhedronGeometry.call( this, vertices, faces, radius, detail );
+};
+
+THREE.OctahedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author timothypratley / https://github.com/timothypratley
+ */
+
+THREE.TetrahedronGeometry = function ( radius, detail ) {
+
+	var vertices = [
+		[ 1,  1,  1 ], [ -1, -1, 1 ], [ -1, 1, -1 ], [ 1, -1, -1 ]
+	];
+
+	var faces = [
+		[ 2, 1, 0 ], [ 0, 3, 2 ], [ 1, 3, 0 ], [ 2, 3, 1 ]
+	];
+
+	THREE.PolyhedronGeometry.call( this, vertices, faces, radius, detail );
+
+};
+
+THREE.TetrahedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author zz85 / https://github.com/zz85
+ * Parametric Surfaces Geometry
+ * based on the brilliant article by @prideout http://prideout.net/blog/?p=44
+ *
+ * new THREE.ParametricGeometry( parametricFunction, uSegments, ySegements, useTris );
+ *
+ */
+
+THREE.ParametricGeometry = function ( func, slices, stacks, useTris ) {
+
+	THREE.Geometry.call( this );
+
+	var verts = this.vertices;
+	var faces = this.faces;
+	var uvs = this.faceVertexUvs[ 0 ];
+
+	useTris = (useTris === undefined) ? false : useTris;
+
+	var i, il, j, p;
+	var u, v;
+
+	var stackCount = stacks + 1;
+	var sliceCount = slices + 1;
+
+	for ( i = 0; i <= stacks; i ++ ) {
+
+		v = i / stacks;
+
+		for ( j = 0; j <= slices; j ++ ) {
+
+			u = j / slices;
+
+			p = func( u, v );
+			verts.push( p );
+
+		}
+	}
+
+	var a, b, c, d;
+	var uva, uvb, uvc, uvd;
+
+	for ( i = 0; i < stacks; i ++ ) {
+
+		for ( j = 0; j < slices; j ++ ) {
+
+			a = i * sliceCount + j;
+			b = i * sliceCount + j + 1;
+			c = (i + 1) * sliceCount + j;
+			d = (i + 1) * sliceCount + j + 1;
+
+			uva = new THREE.Vector2( j / slices, i / stacks );
+			uvb = new THREE.Vector2( ( j + 1 ) / slices, i / stacks );
+			uvc = new THREE.Vector2( j / slices, ( i + 1 ) / stacks );
+			uvd = new THREE.Vector2( ( j + 1 ) / slices, ( i + 1 ) / stacks );
+
+			if ( useTris ) {
+
+				faces.push( new THREE.Face3( a, b, c ) );
+				faces.push( new THREE.Face3( b, d, c ) );
+
+				uvs.push( [ uva, uvb, uvc ] );
+				uvs.push( [ uvb, uvd, uvc ] );
+
+			} else {
+
+				faces.push( new THREE.Face4( a, b, d, c ) );
+				uvs.push( [ uva, uvb, uvd, uvc ] );
+
+			}
+
+		}
+
+	}
+
+	// console.log(this);
+
+	// magic bullet
+	// var diff = this.mergeVertices();
+	// console.log('removed ', diff, ' vertices by merging');
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+	this.computeVertexNormals();
+
+};
+
+THREE.ParametricGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author qiao / https://github.com/qiao
+ * @fileoverview This is a convex hull generator using the incremental method. 
+ * The complexity is O(n^2) where n is the number of vertices.
+ * O(nlogn) algorithms do exist, but they are much more complicated.
+ *
+ * Benchmark: 
+ *
+ *  Platform: CPU: P7350 @2.00GHz Engine: V8
+ *
+ *  Num Vertices	Time(ms)
+ *
+ *     10           1
+ *     20           3
+ *     30           19
+ *     40           48
+ *     50           107
+ */
+
+THREE.ConvexGeometry = function( vertices ) {
+
+	THREE.Geometry.call( this );
+
+	var faces = [ [ 0, 1, 2 ], [ 0, 2, 1 ] ]; 
+
+	for ( var i = 3; i < vertices.length; i++ ) {
+
+		addPoint( i );
+
+	}
+
+
+	function addPoint( vertexId ) {
+
+		var vertex = vertices[ vertexId ].clone();
+
+		var mag = vertex.length();
+		vertex.x += mag * randomOffset();
+		vertex.y += mag * randomOffset();
+		vertex.z += mag * randomOffset();
+
+		var hole = [];
+
+		for ( var f = 0; f < faces.length; ) {
+
+			var face = faces[ f ];
+
+			// for each face, if the vertex can see it,
+			// then we try to add the face's edges into the hole.
+			if ( visible( face, vertex ) ) {
+
+				for ( var e = 0; e < 3; e++ ) {
+
+					var edge = [ face[ e ], face[ ( e + 1 ) % 3 ] ];
+					var boundary = true;
+
+					// remove duplicated edges.
+					for ( var h = 0; h < hole.length; h++ ) {
+
+						if ( equalEdge( hole[ h ], edge ) ) {
+
+							hole[ h ] = hole[ hole.length - 1 ];
+							hole.pop();
+							boundary = false;
+							break;
+
+						}
+
+					}
+
+					if ( boundary ) {
+
+						hole.push( edge );
+
+					}
+
+				}
+
+				// remove faces[ f ]
+				faces[ f ] = faces[ faces.length - 1 ];
+				faces.pop();
+
+			} else { // not visible
+
+				f++;
+
+			}
+		}
+
+		// construct the new faces formed by the edges of the hole and the vertex
+		for ( var h = 0; h < hole.length; h++ ) {
+
+			faces.push( [ 
+				hole[ h ][ 0 ],
+				hole[ h ][ 1 ],
+				vertexId
+			] );
+
+		}
+	}
+
+	/**
+	 * Whether the face is visible from the vertex
+	 */
+	function visible( face, vertex ) {
+
+		var va = vertices[ face[ 0 ] ];
+		var vb = vertices[ face[ 1 ] ];
+		var vc = vertices[ face[ 2 ] ];
+
+		var n = normal( va, vb, vc );
+
+		// distance from face to origin
+		var dist = n.dot( va );
+
+		return n.dot( vertex ) >= dist; 
+
+	}
+
+	/**
+	 * Face normal
+	 */
+	function normal( va, vb, vc ) {
+
+		var cb = new THREE.Vector3();
+		var ab = new THREE.Vector3();
+
+		cb.subVectors( vc, vb );
+		ab.subVectors( va, vb );
+		cb.cross( ab );
+
+		cb.normalize();
+
+		return cb;
+
+	}
+
+	/**
+	 * Detect whether two edges are equal.
+	 * Note that when constructing the convex hull, two same edges can only
+	 * be of the negative direction.
+	 */
+	function equalEdge( ea, eb ) {
+
+		return ea[ 0 ] === eb[ 1 ] && ea[ 1 ] === eb[ 0 ]; 
+
+	}
+
+	/**
+	 * Create a random offset between -1e-6 and 1e-6.
+	 */
+	function randomOffset() {
+
+		return ( Math.random() - 0.5 ) * 2 * 1e-6;
+
+	}
+
+
+	/**
+	 * XXX: Not sure if this is the correct approach. Need someone to review.
+	 */
+	function vertexUv( vertex ) {
+
+		var mag = vertex.length();
+		return new THREE.Vector2( vertex.x / mag, vertex.y / mag );
+
+	}
+
+	// Push vertices into `this.vertices`, skipping those inside the hull
+	var id = 0;
+	var newId = new Array( vertices.length ); // map from old vertex id to new id
+
+	for ( var i = 0; i < faces.length; i++ ) {
+
+		 var face = faces[ i ];
+
+		 for ( var j = 0; j < 3; j++ ) {
+
+				if ( newId[ face[ j ] ] === undefined ) {
+
+						newId[ face[ j ] ] = id++;
+						this.vertices.push( vertices[ face[ j ] ] );
+
+				}
+
+				face[ j ] = newId[ face[ j ] ];
+
+		 }
+
+	}
+
+	// Convert faces into instances of THREE.Face3
+	for ( var i = 0; i < faces.length; i++ ) {
+
+		this.faces.push( new THREE.Face3( 
+				faces[ i ][ 0 ],
+				faces[ i ][ 1 ],
+				faces[ i ][ 2 ]
+		) );
+
+	}
+
+	// Compute UVs
+	for ( var i = 0; i < this.faces.length; i++ ) {
+
+		var face = this.faces[ i ];
+
+		this.faceVertexUvs[ 0 ].push( [
+			vertexUv( this.vertices[ face.a ] ),
+			vertexUv( this.vertices[ face.b ] ),
+			vertexUv( this.vertices[ face.c ])
+		] );
+
+	}
+
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+	this.computeVertexNormals();
+
+};
+
+THREE.ConvexGeometry.prototype = Object.create( THREE.Geometry.prototype );
+/**
+ * @author sroucheray / http://sroucheray.org/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.AxisHelper = function ( size ) {
+
+	size = size || 1;
+
+	var geometry = new THREE.Geometry();
+
+	geometry.vertices.push(
+		new THREE.Vector3(), new THREE.Vector3( size, 0, 0 ),
+		new THREE.Vector3(), new THREE.Vector3( 0, size, 0 ),
+		new THREE.Vector3(), new THREE.Vector3( 0, 0, size )
+	);
+
+	geometry.colors.push(
+		new THREE.Color( 0xff0000 ), new THREE.Color( 0xffaa00 ),
+		new THREE.Color( 0x00ff00 ), new THREE.Color( 0xaaff00 ),
+		new THREE.Color( 0x0000ff ), new THREE.Color( 0x00aaff )
+	);
+
+	var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );
+
+	THREE.Line.call( this, geometry, material, THREE.LinePieces );
+
+};
+
+THREE.AxisHelper.prototype = Object.create( THREE.Line.prototype );
+/**
+ * @author WestLangley / http://github.com/WestLangley
+ * @author zz85 / http://github.com/zz85
+ * @author bhouston / http://exocortex.com
+ *
+ * Creates an arrow for visualizing directions
+ *
+ * Parameters:
+ *  dir - Vector3
+ *  origin - Vector3
+ *  length - Number
+ *  hex - color in hex value
+ */
+
+THREE.ArrowHelper = function ( dir, origin, length, hex ) {
+
+	// dir is assumed to be normalized
+
+	THREE.Object3D.call( this );
+
+	if ( hex === undefined ) hex = 0xffff00;
+	if ( length === undefined ) length = 1;
+
+	this.position = origin;
+
+	this.useQuaternion = true;
+
+	var lineGeometry = new THREE.Geometry();
+	lineGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) );
+	lineGeometry.vertices.push( new THREE.Vector3( 0, 1, 0 ) );
+
+	this.line = new THREE.Line( lineGeometry, new THREE.LineBasicMaterial( { color: hex } ) );
+	this.line.matrixAutoUpdate = false;
+	this.add( this.line );
+
+	var coneGeometry = new THREE.CylinderGeometry( 0, 0.05, 0.25, 5, 1 );
+	coneGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, 0.875, 0 ) );
+
+	this.cone = new THREE.Mesh( coneGeometry, new THREE.MeshBasicMaterial( { color: hex } ) );
+	this.cone.matrixAutoUpdate = false;
+	this.add( this.cone );
+
+	this.setDirection( dir );
+	this.setLength( length );
+
+};
+
+THREE.ArrowHelper.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.ArrowHelper.prototype.setDirection = function () {
+
+	var axis = new THREE.Vector3();
+	var radians;
+
+	return function ( dir ) {
+
+		// dir is assumed to be normalized
+
+		if ( dir.y > 0.999 ) {
+
+			this.quaternion.set( 0, 0, 0, 1 );
+
+		} else if ( dir.y < - 0.999 ) {
+
+			this.quaternion.set( 1, 0, 0, 0 );
+
+		} else {
+
+			axis.set( dir.z, 0, - dir.x ).normalize();
+
+			radians = Math.acos( dir.y );
+
+			this.quaternion.setFromAxisAngle( axis, radians );
+
+		}
+
+	};
+
+}();
+
+THREE.ArrowHelper.prototype.setLength = function ( length ) {
+
+	this.scale.set( length, length, length );
+
+};
+
+THREE.ArrowHelper.prototype.setColor = function ( hex ) {
+
+	this.line.material.color.setHex( hex );
+	this.cone.material.color.setHex( hex );
+
+};
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.BoxHelper = function ( size ) {
+
+	size = size || 1;
+
+	var geometry = new THREE.Geometry();
+
+	//   5____4
+	// 1/___0/|
+	// | 6__|_7
+	// 2/___3/
+
+	var vertices = [
+		new THREE.Vector3(   size,   size,   size ),
+		new THREE.Vector3( - size,   size,   size ),
+		new THREE.Vector3( - size, - size,   size ),
+		new THREE.Vector3(   size, - size,   size ),
+
+		new THREE.Vector3(   size,   size, - size ),
+		new THREE.Vector3( - size,   size, - size ),
+		new THREE.Vector3( - size, - size, - size ),
+		new THREE.Vector3(   size, - size, - size )
+	];
+
+	// TODO: Wouldn't be nice if Line had .segments?
+
+	geometry.vertices.push(
+		vertices[ 0 ], vertices[ 1 ],
+		vertices[ 1 ], vertices[ 2 ],
+		vertices[ 2 ], vertices[ 3 ],
+		vertices[ 3 ], vertices[ 0 ],
+
+		vertices[ 4 ], vertices[ 5 ],
+		vertices[ 5 ], vertices[ 6 ],
+		vertices[ 6 ], vertices[ 7 ],
+		vertices[ 7 ], vertices[ 4 ],
+
+		vertices[ 0 ], vertices[ 4 ],
+		vertices[ 1 ], vertices[ 5 ],
+		vertices[ 2 ], vertices[ 6 ],
+		vertices[ 3 ], vertices[ 7 ]
+	);
+
+	this.vertices = vertices;
+
+	THREE.Line.call( this, geometry, new THREE.LineBasicMaterial(), THREE.LinePieces );
+
+};
+
+THREE.BoxHelper.prototype = Object.create( THREE.Line.prototype );
+
+THREE.BoxHelper.prototype.update = function ( object ) {
+
+	var geometry = object.geometry;
+
+	if ( geometry.boundingBox === null ) {
+
+		geometry.computeBoundingBox();
+
+	}
+
+	var min = geometry.boundingBox.min;
+	var max = geometry.boundingBox.max;
+	var vertices = this.vertices;
+
+	vertices[ 0 ].set( max.x, max.y, max.z );
+	vertices[ 1 ].set( min.x, max.y, max.z );
+	vertices[ 2 ].set( min.x, min.y, max.z );
+	vertices[ 3 ].set( max.x, min.y, max.z );
+	vertices[ 4 ].set( max.x, max.y, min.z );
+	vertices[ 5 ].set( min.x, max.y, min.z );
+	vertices[ 6 ].set( min.x, min.y, min.z );
+	vertices[ 7 ].set( max.x, min.y, min.z );
+
+	this.geometry.computeBoundingSphere();
+	this.geometry.verticesNeedUpdate = true;
+
+	this.matrixAutoUpdate = false;
+	this.matrixWorld = object.matrixWorld;
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ *	- shows frustum, line of sight and up of the camera
+ *	- suitable for fast updates
+ * 	- based on frustum visualization in lightgl.js shadowmap example
+ *		http://evanw.github.com/lightgl.js/tests/shadowmap.html
+ */
+
+THREE.CameraHelper = function ( camera ) {
+
+	THREE.Line.call( this );
+
+	var geometry = new THREE.Geometry();
+	var material = new THREE.LineBasicMaterial( { color: 0xffffff, vertexColors: THREE.FaceColors } );
+
+	var pointMap = {};
+
+	// colors
+
+	var hexFrustum = 0xffaa00;
+	var hexCone = 0xff0000;
+	var hexUp = 0x00aaff;
+	var hexTarget = 0xffffff;
+	var hexCross = 0x333333;
+
+	// near
+
+	addLine( "n1", "n2", hexFrustum );
+	addLine( "n2", "n4", hexFrustum );
+	addLine( "n4", "n3", hexFrustum );
+	addLine( "n3", "n1", hexFrustum );
+
+	// far
+
+	addLine( "f1", "f2", hexFrustum );
+	addLine( "f2", "f4", hexFrustum );
+	addLine( "f4", "f3", hexFrustum );
+	addLine( "f3", "f1", hexFrustum );
+
+	// sides
+
+	addLine( "n1", "f1", hexFrustum );
+	addLine( "n2", "f2", hexFrustum );
+	addLine( "n3", "f3", hexFrustum );
+	addLine( "n4", "f4", hexFrustum );
+
+	// cone
+
+	addLine( "p", "n1", hexCone );
+	addLine( "p", "n2", hexCone );
+	addLine( "p", "n3", hexCone );
+	addLine( "p", "n4", hexCone );
+
+	// up
+
+	addLine( "u1", "u2", hexUp );
+	addLine( "u2", "u3", hexUp );
+	addLine( "u3", "u1", hexUp );
+
+	// target
+
+	addLine( "c", "t", hexTarget );
+	addLine( "p", "c", hexCross );
+
+	// cross
+
+	addLine( "cn1", "cn2", hexCross );
+	addLine( "cn3", "cn4", hexCross );
+
+	addLine( "cf1", "cf2", hexCross );
+	addLine( "cf3", "cf4", hexCross );
+
+	function addLine( a, b, hex ) {
+
+		addPoint( a, hex );
+		addPoint( b, hex );
+
+	}
+
+	function addPoint( id, hex ) {
+
+		geometry.vertices.push( new THREE.Vector3() );
+		geometry.colors.push( new THREE.Color( hex ) );
+
+		if ( pointMap[ id ] === undefined ) {
+
+			pointMap[ id ] = [];
+
+		}
+
+		pointMap[ id ].push( geometry.vertices.length - 1 );
+
+	}
+
+	THREE.Line.call( this, geometry, material, THREE.LinePieces );
+
+	this.camera = camera;
+	this.matrixWorld = camera.matrixWorld;
+	this.matrixAutoUpdate = false;
+
+	this.pointMap = pointMap;
+
+	this.update();
+
+};
+
+THREE.CameraHelper.prototype = Object.create( THREE.Line.prototype );
+
+THREE.CameraHelper.prototype.update = function () {
+
+	var vector = new THREE.Vector3();
+	var camera = new THREE.Camera();
+	var projector = new THREE.Projector();
+
+	return function () {
+
+		var scope = this;
+
+		var w = 1, h = 1;
+
+		// we need just camera projection matrix
+		// world matrix must be identity
+
+		camera.projectionMatrix.copy( this.camera.projectionMatrix );
+
+		// center / target
+
+		setPoint( "c", 0, 0, -1 );
+		setPoint( "t", 0, 0,  1 );
+
+		// near
+
+		setPoint( "n1", -w, -h, -1 );
+		setPoint( "n2",  w, -h, -1 );
+		setPoint( "n3", -w,  h, -1 );
+		setPoint( "n4",  w,  h, -1 );
+
+		// far
+
+		setPoint( "f1", -w, -h, 1 );
+		setPoint( "f2",  w, -h, 1 );
+		setPoint( "f3", -w,  h, 1 );
+		setPoint( "f4",  w,  h, 1 );
+
+		// up
+
+		setPoint( "u1",  w * 0.7, h * 1.1, -1 );
+		setPoint( "u2", -w * 0.7, h * 1.1, -1 );
+		setPoint( "u3",        0, h * 2,   -1 );
+
+		// cross
+
+		setPoint( "cf1", -w,  0, 1 );
+		setPoint( "cf2",  w,  0, 1 );
+		setPoint( "cf3",  0, -h, 1 );
+		setPoint( "cf4",  0,  h, 1 );
+
+		setPoint( "cn1", -w,  0, -1 );
+		setPoint( "cn2",  w,  0, -1 );
+		setPoint( "cn3",  0, -h, -1 );
+		setPoint( "cn4",  0,  h, -1 );
+
+		function setPoint( point, x, y, z ) {
+
+			vector.set( x, y, z );
+			projector.unprojectVector( vector, camera );
+
+			var points = scope.pointMap[ point ];
+
+			if ( points !== undefined ) {
+
+				for ( var i = 0, il = points.length; i < il; i ++ ) {
+
+					scope.geometry.vertices[ points[ i ] ].copy( vector );
+
+				}
+
+			}
+
+		}
+
+		this.geometry.verticesNeedUpdate = true;
+
+	};
+
+}();
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.DirectionalLightHelper = function ( light, sphereSize ) {
+
+	THREE.Object3D.call( this );
+
+	this.matrixAutoUpdate = false;
+
+	this.light = light;
+
+	var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 );
+	var material = new THREE.MeshBasicMaterial( { fog: false, wireframe: true } );
+	material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+	this.lightSphere = new THREE.Mesh( geometry, material );
+	this.lightSphere.matrixWorld = this.light.matrixWorld;
+	this.lightSphere.matrixAutoUpdate = false;
+	this.add( this.lightSphere );
+
+	/*
+	this.targetSphere = new THREE.Mesh( geometry, material );
+	this.targetSphere.position = this.light.target.position;
+	this.add( this.targetSphere );
+	*/
+
+	geometry = new THREE.Geometry();
+	geometry.vertices.push( this.light.position );
+	geometry.vertices.push( this.light.target.position );
+	geometry.computeLineDistances();
+
+	material = new THREE.LineDashedMaterial( { dashSize: 4, gapSize: 4, opacity: 0.75, transparent: true, fog: false } );
+	material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+	this.targetLine = new THREE.Line( geometry, material );
+	this.add( this.targetLine );
+
+}
+
+THREE.DirectionalLightHelper.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.DirectionalLightHelper.prototype.update = function () {
+
+	this.lightSphere.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+	this.targetLine.geometry.computeLineDistances();
+	this.targetLine.geometry.verticesNeedUpdate = true;
+	this.targetLine.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.GridHelper = function ( size, step ) {
+
+	var geometry = new THREE.Geometry();
+	var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );
+	var color1 = new THREE.Color( 0x444444 ), color2 = new THREE.Color( 0x888888 );
+
+	for ( var i = - size; i <= size; i += step ) {
+
+		geometry.vertices.push( new THREE.Vector3( -size, 0, i ) );
+		geometry.vertices.push( new THREE.Vector3(  size, 0, i ) );
+
+		geometry.vertices.push( new THREE.Vector3( i, 0, -size ) );
+		geometry.vertices.push( new THREE.Vector3( i, 0,  size ) );
+
+		var color = i === 0 ? color1 : color2;
+
+		geometry.colors.push( color, color, color, color );
+
+	}
+
+	THREE.Line.call( this, geometry, material, THREE.LinePieces );
+
+};
+
+THREE.GridHelper.prototype = Object.create( THREE.Line.prototype );
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.HemisphereLightHelper = function ( light, sphereSize, arrowLength, domeSize ) {
+
+	THREE.Object3D.call( this );
+
+	this.light = light;
+
+	var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 );
+	geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
+
+	for ( var i = 0, il = 8; i < il; i ++ ) {
+
+		geometry.faces[ i ].materialIndex = i < 4 ? 0 : 1;
+
+	}
+
+	var materialSky = new THREE.MeshBasicMaterial( { fog: false, wireframe: true } );
+	materialSky.color.copy( light.color ).multiplyScalar( light.intensity );
+
+	var materialGround = new THREE.MeshBasicMaterial( { fog: false, wireframe: true } );
+	materialGround.color.copy( light.groundColor ).multiplyScalar( light.intensity );
+
+	this.lightSphere = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial( [ materialSky, materialGround ] ) );
+	this.lightSphere.position = light.position;
+	this.lightSphere.lookAt( new THREE.Vector3() );
+	this.add( this.lightSphere );
+
+};
+
+THREE.HemisphereLightHelper.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.HemisphereLightHelper.prototype.update = function () {
+
+	this.lightSphere.lookAt( new THREE.Vector3() );
+
+	this.lightSphere.material.materials[ 0 ].color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+	this.lightSphere.material.materials[ 1 ].color.copy( this.light.groundColor ).multiplyScalar( this.light.intensity );
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.PointLightHelper = function ( light, sphereSize ) {
+
+	THREE.Object3D.call( this );
+
+	this.matrixAutoUpdate = false;
+
+	this.light = light;
+
+	var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 );
+	var material = new THREE.MeshBasicMaterial( { fog: false, wireframe: true } );
+	material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+	this.lightSphere = new THREE.Mesh( geometry, material );
+	this.lightSphere.matrixWorld = this.light.matrixWorld;
+	this.lightSphere.matrixAutoUpdate = false;
+	this.add( this.lightSphere );
+
+	/*
+	var distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 );
+	var distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } );
+
+	this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial );
+	this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial );
+
+	var d = light.distance;
+
+	if ( d === 0.0 ) {
+
+		this.lightDistance.visible = false;
+
+	} else {
+
+		this.lightDistance.scale.set( d, d, d );
+
+	}
+
+	this.add( this.lightDistance );
+	*/
+
+};
+
+THREE.PointLightHelper.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.PointLightHelper.prototype.update = function () {
+
+	this.lightSphere.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+	/*
+	this.lightDistance.material.color.copy( this.color );
+
+	var d = this.light.distance;
+
+	if ( d === 0.0 ) {
+
+		this.lightDistance.visible = false;
+
+	} else {
+
+		this.lightDistance.visible = true;
+		this.lightDistance.scale.set( d, d, d );
+
+	}
+	*/
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.SpotLightHelper = function ( light, sphereSize ) {
+
+	THREE.Object3D.call( this );
+
+	this.matrixAutoUpdate = false;
+
+	this.light = light;
+
+	var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 );
+	var material = new THREE.MeshBasicMaterial( { fog: false, wireframe: true } );
+	material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+	this.lightSphere = new THREE.Mesh( geometry, material );
+	this.lightSphere.matrixWorld = this.light.matrixWorld;
+	this.lightSphere.matrixAutoUpdate = false;
+	this.add( this.lightSphere );
+
+	geometry = new THREE.CylinderGeometry( 0.0001, 1, 1, 8, 1, true );
+	geometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, -0.5, 0 ) );
+	geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
+
+	material = new THREE.MeshBasicMaterial( { fog: false, wireframe: true, opacity: 0.3, transparent: true } );
+	material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+	this.lightCone = new THREE.Mesh( geometry, material );
+	this.lightCone.position = this.light.position;
+
+	var coneLength = light.distance ? light.distance : 10000;
+	var coneWidth = coneLength * Math.tan( light.angle );
+
+	this.lightCone.scale.set( coneWidth, coneWidth, coneLength );
+	this.lightCone.lookAt( this.light.target.position );
+
+	this.add( this.lightCone );
+
+};
+
+THREE.SpotLightHelper.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.SpotLightHelper.prototype.update = function () {
+
+	var coneLength = this.light.distance ? this.light.distance : 10000;
+	var coneWidth = coneLength * Math.tan( this.light.angle );
+
+	this.lightCone.scale.set( coneWidth, coneWidth, coneLength );
+	this.lightCone.lookAt( this.light.target.position );
+
+	this.lightSphere.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+	this.lightCone.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.ImmediateRenderObject = function () {
+
+	THREE.Object3D.call( this );
+
+	this.render = function ( renderCallback ) { };
+
+};
+
+THREE.ImmediateRenderObject.prototype = Object.create( THREE.Object3D.prototype );
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.LensFlare = function ( texture, size, distance, blending, color ) {
+
+	THREE.Object3D.call( this );
+
+	this.lensFlares = [];
+
+	this.positionScreen = new THREE.Vector3();
+	this.customUpdateCallback = undefined;
+
+	if( texture !== undefined ) {
+
+		this.add( texture, size, distance, blending, color );
+
+	}
+
+};
+
+THREE.LensFlare.prototype = Object.create( THREE.Object3D.prototype );
+
+
+/*
+ * Add: adds another flare
+ */
+
+THREE.LensFlare.prototype.add = function ( texture, size, distance, blending, color, opacity ) {
+
+	if( size === undefined ) size = -1;
+	if( distance === undefined ) distance = 0;
+	if( opacity === undefined ) opacity = 1;
+	if( color === undefined ) color = new THREE.Color( 0xffffff );
+	if( blending === undefined ) blending = THREE.NormalBlending;
+
+	distance = Math.min( distance, Math.max( 0, distance ) );
+
+	this.lensFlares.push( { texture: texture, 			// THREE.Texture
+		                    size: size, 				// size in pixels (-1 = use texture.width)
+		                    distance: distance, 		// distance (0-1) from light source (0=at light source)
+		                    x: 0, y: 0, z: 0,			// screen position (-1 => 1) z = 0 is ontop z = 1 is back
+		                    scale: 1, 					// scale
+		                    rotation: 1, 				// rotation
+		                    opacity: opacity,			// opacity
+							color: color,				// color
+		                    blending: blending } );		// blending
+
+};
+
+
+/*
+ * Update lens flares update positions on all flares based on the screen position
+ * Set myLensFlare.customUpdateCallback to alter the flares in your project specific way.
+ */
+
+THREE.LensFlare.prototype.updateLensFlares = function () {
+
+	var f, fl = this.lensFlares.length;
+	var flare;
+	var vecX = -this.positionScreen.x * 2;
+	var vecY = -this.positionScreen.y * 2;
+
+	for( f = 0; f < fl; f ++ ) {
+
+		flare = this.lensFlares[ f ];
+
+		flare.x = this.positionScreen.x + vecX * flare.distance;
+		flare.y = this.positionScreen.y + vecY * flare.distance;
+
+		flare.wantedRotation = flare.x * Math.PI * 0.25;
+		flare.rotation += ( flare.wantedRotation - flare.rotation ) * 0.25;
+
+	}
+
+};
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.MorphBlendMesh = function( geometry, material ) {
+
+	THREE.Mesh.call( this, geometry, material );
+
+	this.animationsMap = {};
+	this.animationsList = [];
+
+	// prepare default animation
+	// (all frames played together in 1 second)
+
+	var numFrames = this.geometry.morphTargets.length;
+
+	var name = "__default";
+
+	var startFrame = 0;
+	var endFrame = numFrames - 1;
+
+	var fps = numFrames / 1;
+
+	this.createAnimation( name, startFrame, endFrame, fps );
+	this.setAnimationWeight( name, 1 );
+
+};
+
+THREE.MorphBlendMesh.prototype = Object.create( THREE.Mesh.prototype );
+
+THREE.MorphBlendMesh.prototype.createAnimation = function ( name, start, end, fps ) {
+
+	var animation = {
+
+		startFrame: start,
+		endFrame: end,
+
+		length: end - start + 1,
+
+		fps: fps,
+		duration: ( end - start ) / fps,
+
+		lastFrame: 0,
+		currentFrame: 0,
+
+		active: false,
+
+		time: 0,
+		direction: 1,
+		weight: 1,
+
+		directionBackwards: false,
+		mirroredLoop: false
+
+	};
+
+	this.animationsMap[ name ] = animation;
+	this.animationsList.push( animation );
+
+};
+
+THREE.MorphBlendMesh.prototype.autoCreateAnimations = function ( fps ) {
+
+	var pattern = /([a-z]+)(\d+)/;
+
+	var firstAnimation, frameRanges = {};
+
+	var geometry = this.geometry;
+
+	for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) {
+
+		var morph = geometry.morphTargets[ i ];
+		var chunks = morph.name.match( pattern );
+
+		if ( chunks && chunks.length > 1 ) {
+
+			var name = chunks[ 1 ];
+			var num = chunks[ 2 ];
+
+			if ( ! frameRanges[ name ] ) frameRanges[ name ] = { start: Infinity, end: -Infinity };
+
+			var range = frameRanges[ name ];
+
+			if ( i < range.start ) range.start = i;
+			if ( i > range.end ) range.end = i;
+
+			if ( ! firstAnimation ) firstAnimation = name;
+
+		}
+
+	}
+
+	for ( var name in frameRanges ) {
+
+		var range = frameRanges[ name ];
+		this.createAnimation( name, range.start, range.end, fps );
+
+	}
+
+	this.firstAnimation = firstAnimation;
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationDirectionForward = function ( name ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.direction = 1;
+		animation.directionBackwards = false;
+
+	}
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward = function ( name ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.direction = -1;
+		animation.directionBackwards = true;
+
+	}
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationFPS = function ( name, fps ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.fps = fps;
+		animation.duration = ( animation.end - animation.start ) / animation.fps;
+
+	}
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationDuration = function ( name, duration ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.duration = duration;
+		animation.fps = ( animation.end - animation.start ) / animation.duration;
+
+	}
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationWeight = function ( name, weight ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.weight = weight;
+
+	}
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationTime = function ( name, time ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.time = time;
+
+	}
+
+};
+
+THREE.MorphBlendMesh.prototype.getAnimationTime = function ( name ) {
+
+	var time = 0;
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		time = animation.time;
+
+	}
+
+	return time;
+
+};
+
+THREE.MorphBlendMesh.prototype.getAnimationDuration = function ( name ) {
+
+	var duration = -1;
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		duration = animation.duration;
+
+	}
+
+	return duration;
+
+};
+
+THREE.MorphBlendMesh.prototype.playAnimation = function ( name ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.time = 0;
+		animation.active = true;
+
+	} else {
+
+		console.warn( "animation[" + name + "] undefined" );
+
+	}
+
+};
+
+THREE.MorphBlendMesh.prototype.stopAnimation = function ( name ) {
+
+	var animation = this.animationsMap[ name ];
+
+	if ( animation ) {
+
+		animation.active = false;
+
+	}
+
+};
+
+THREE.MorphBlendMesh.prototype.update = function ( delta ) {
+
+	for ( var i = 0, il = this.animationsList.length; i < il; i ++ ) {
+
+		var animation = this.animationsList[ i ];
+
+		if ( ! animation.active ) continue;
+
+		var frameTime = animation.duration / animation.length;
+
+		animation.time += animation.direction * delta;
+
+		if ( animation.mirroredLoop ) {
+
+			if ( animation.time > animation.duration || animation.time < 0 ) {
+
+				animation.direction *= -1;
+
+				if ( animation.time > animation.duration ) {
+
+					animation.time = animation.duration;
+					animation.directionBackwards = true;
+
+				}
+
+				if ( animation.time < 0 ) {
+
+					animation.time = 0;
+					animation.directionBackwards = false;
+
+				}
+
+			}
+
+		} else {
+
+			animation.time = animation.time % animation.duration;
+
+			if ( animation.time < 0 ) animation.time += animation.duration;
+
+		}
+
+		var keyframe = animation.startFrame + THREE.Math.clamp( Math.floor( animation.time / frameTime ), 0, animation.length - 1 );
+		var weight = animation.weight;
+
+		if ( keyframe !== animation.currentFrame ) {
+
+			this.morphTargetInfluences[ animation.lastFrame ] = 0;
+			this.morphTargetInfluences[ animation.currentFrame ] = 1 * weight;
+
+			this.morphTargetInfluences[ keyframe ] = 0;
+
+			animation.lastFrame = animation.currentFrame;
+			animation.currentFrame = keyframe;
+
+		}
+
+		var mix = ( animation.time % frameTime ) / frameTime;
+
+		if ( animation.directionBackwards ) mix = 1 - mix;
+
+		this.morphTargetInfluences[ animation.currentFrame ] = mix * weight;
+		this.morphTargetInfluences[ animation.lastFrame ] = ( 1 - mix ) * weight;
+
+	}
+
+};
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.LensFlarePlugin = function () {
+
+	var _gl, _renderer, _precision, _lensFlare = {};
+
+	this.init = function ( renderer ) {
+
+		_gl = renderer.context;
+		_renderer = renderer;
+
+		_precision = renderer.getPrecision();
+
+		_lensFlare.vertices = new Float32Array( 8 + 8 );
+		_lensFlare.faces = new Uint16Array( 6 );
+
+		var i = 0;
+		_lensFlare.vertices[ i++ ] = -1; _lensFlare.vertices[ i++ ] = -1;	// vertex
+		_lensFlare.vertices[ i++ ] = 0;  _lensFlare.vertices[ i++ ] = 0;	// uv... etc.
+
+		_lensFlare.vertices[ i++ ] = 1;  _lensFlare.vertices[ i++ ] = -1;
+		_lensFlare.vertices[ i++ ] = 1;  _lensFlare.vertices[ i++ ] = 0;
+
+		_lensFlare.vertices[ i++ ] = 1;  _lensFlare.vertices[ i++ ] = 1;
+		_lensFlare.vertices[ i++ ] = 1;  _lensFlare.vertices[ i++ ] = 1;
+
+		_lensFlare.vertices[ i++ ] = -1; _lensFlare.vertices[ i++ ] = 1;
+		_lensFlare.vertices[ i++ ] = 0;  _lensFlare.vertices[ i++ ] = 1;
+
+		i = 0;
+		_lensFlare.faces[ i++ ] = 0; _lensFlare.faces[ i++ ] = 1; _lensFlare.faces[ i++ ] = 2;
+		_lensFlare.faces[ i++ ] = 0; _lensFlare.faces[ i++ ] = 2; _lensFlare.faces[ i++ ] = 3;
+
+		// buffers
+
+		_lensFlare.vertexBuffer     = _gl.createBuffer();
+		_lensFlare.elementBuffer    = _gl.createBuffer();
+
+		_gl.bindBuffer( _gl.ARRAY_BUFFER, _lensFlare.vertexBuffer );
+		_gl.bufferData( _gl.ARRAY_BUFFER, _lensFlare.vertices, _gl.STATIC_DRAW );
+
+		_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.elementBuffer );
+		_gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.faces, _gl.STATIC_DRAW );
+
+		// textures
+
+		_lensFlare.tempTexture      = _gl.createTexture();
+		_lensFlare.occlusionTexture = _gl.createTexture();
+
+		_gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture );
+		_gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGB, 16, 16, 0, _gl.RGB, _gl.UNSIGNED_BYTE, null );
+		_gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE );
+		_gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE );
+		_gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, _gl.NEAREST );
+		_gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.NEAREST );
+
+		_gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.occlusionTexture );
+		_gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, 16, 16, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, null );
+		_gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE );
+		_gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE );
+		_gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, _gl.NEAREST );
+		_gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.NEAREST );
+
+		if ( _gl.getParameter( _gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ) <= 0 ) {
+
+			_lensFlare.hasVertexTexture = false;
+			_lensFlare.program = createProgram( THREE.ShaderFlares[ "lensFlare" ], _precision );
+
+		} else {
+
+			_lensFlare.hasVertexTexture = true;
+			_lensFlare.program = createProgram( THREE.ShaderFlares[ "lensFlareVertexTexture" ], _precision );
+
+		}
+
+		_lensFlare.attributes = {};
+		_lensFlare.uniforms = {};
+
+		_lensFlare.attributes.vertex       = _gl.getAttribLocation ( _lensFlare.program, "position" );
+		_lensFlare.attributes.uv           = _gl.getAttribLocation ( _lensFlare.program, "uv" );
+
+		_lensFlare.uniforms.renderType     = _gl.getUniformLocation( _lensFlare.program, "renderType" );
+		_lensFlare.uniforms.map            = _gl.getUniformLocation( _lensFlare.program, "map" );
+		_lensFlare.uniforms.occlusionMap   = _gl.getUniformLocation( _lensFlare.program, "occlusionMap" );
+		_lensFlare.uniforms.opacity        = _gl.getUniformLocation( _lensFlare.program, "opacity" );
+		_lensFlare.uniforms.color          = _gl.getUniformLocation( _lensFlare.program, "color" );
+		_lensFlare.uniforms.scale          = _gl.getUniformLocation( _lensFlare.program, "scale" );
+		_lensFlare.uniforms.rotation       = _gl.getUniformLocation( _lensFlare.program, "rotation" );
+		_lensFlare.uniforms.screenPosition = _gl.getUniformLocation( _lensFlare.program, "screenPosition" );
+
+	};
+
+
+	/*
+	 * Render lens flares
+	 * Method: renders 16x16 0xff00ff-colored points scattered over the light source area,
+	 *         reads these back and calculates occlusion.
+	 *         Then _lensFlare.update_lensFlares() is called to re-position and
+	 *         update transparency of flares. Then they are rendered.
+	 *
+	 */
+
+	this.render = function ( scene, camera, viewportWidth, viewportHeight ) {
+
+		var flares = scene.__webglFlares,
+			nFlares = flares.length;
+
+		if ( ! nFlares ) return;
+
+		var tempPosition = new THREE.Vector3();
+
+		var invAspect = viewportHeight / viewportWidth,
+			halfViewportWidth = viewportWidth * 0.5,
+			halfViewportHeight = viewportHeight * 0.5;
+
+		var size = 16 / viewportHeight,
+			scale = new THREE.Vector2( size * invAspect, size );
+
+		var screenPosition = new THREE.Vector3( 1, 1, 0 ),
+			screenPositionPixels = new THREE.Vector2( 1, 1 );
+
+		var uniforms = _lensFlare.uniforms,
+			attributes = _lensFlare.attributes;
+
+		// set _lensFlare program and reset blending
+
+		_gl.useProgram( _lensFlare.program );
+
+		_gl.enableVertexAttribArray( _lensFlare.attributes.vertex );
+		_gl.enableVertexAttribArray( _lensFlare.attributes.uv );
+
+		// loop through all lens flares to update their occlusion and positions
+		// setup gl and common used attribs/unforms
+
+		_gl.uniform1i( uniforms.occlusionMap, 0 );
+		_gl.uniform1i( uniforms.map, 1 );
+
+		_gl.bindBuffer( _gl.ARRAY_BUFFER, _lensFlare.vertexBuffer );
+		_gl.vertexAttribPointer( attributes.vertex, 2, _gl.FLOAT, false, 2 * 8, 0 );
+		_gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 2 * 8, 8 );
+
+		_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.elementBuffer );
+
+		_gl.disable( _gl.CULL_FACE );
+		_gl.depthMask( false );
+
+		var i, j, jl, flare, sprite;
+
+		for ( i = 0; i < nFlares; i ++ ) {
+
+			size = 16 / viewportHeight;
+			scale.set( size * invAspect, size );
+
+			// calc object screen position
+
+			flare = flares[ i ];
+
+			tempPosition.set( flare.matrixWorld.elements[12], flare.matrixWorld.elements[13], flare.matrixWorld.elements[14] );
+
+			tempPosition.applyMatrix4( camera.matrixWorldInverse );
+			tempPosition.applyProjection( camera.projectionMatrix );
+
+			// setup arrays for gl programs
+
+			screenPosition.copy( tempPosition )
+
+			screenPositionPixels.x = screenPosition.x * halfViewportWidth + halfViewportWidth;
+			screenPositionPixels.y = screenPosition.y * halfViewportHeight + halfViewportHeight;
+
+			// screen cull
+
+			if ( _lensFlare.hasVertexTexture || (
+				screenPositionPixels.x > 0 &&
+				screenPositionPixels.x < viewportWidth &&
+				screenPositionPixels.y > 0 &&
+				screenPositionPixels.y < viewportHeight ) ) {
+
+				// save current RGB to temp texture
+
+				_gl.activeTexture( _gl.TEXTURE1 );
+				_gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture );
+				_gl.copyTexImage2D( _gl.TEXTURE_2D, 0, _gl.RGB, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0 );
+
+
+				// render pink quad
+
+				_gl.uniform1i( uniforms.renderType, 0 );
+				_gl.uniform2f( uniforms.scale, scale.x, scale.y );
+				_gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z );
+
+				_gl.disable( _gl.BLEND );
+				_gl.enable( _gl.DEPTH_TEST );
+
+				_gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 );
+
+
+				// copy result to occlusionMap
+
+				_gl.activeTexture( _gl.TEXTURE0 );
+				_gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.occlusionTexture );
+				_gl.copyTexImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0 );
+
+
+				// restore graphics
+
+				_gl.uniform1i( uniforms.renderType, 1 );
+				_gl.disable( _gl.DEPTH_TEST );
+
+				_gl.activeTexture( _gl.TEXTURE1 );
+				_gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture );
+				_gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 );
+
+
+				// update object positions
+
+				flare.positionScreen.copy( screenPosition )
+
+				if ( flare.customUpdateCallback ) {
+
+					flare.customUpdateCallback( flare );
+
+				} else {
+
+					flare.updateLensFlares();
+
+				}
+
+				// render flares
+
+				_gl.uniform1i( uniforms.renderType, 2 );
+				_gl.enable( _gl.BLEND );
+
+				for ( j = 0, jl = flare.lensFlares.length; j < jl; j ++ ) {
+
+					sprite = flare.lensFlares[ j ];
+
+					if ( sprite.opacity > 0.001 && sprite.scale > 0.001 ) {
+
+						screenPosition.x = sprite.x;
+						screenPosition.y = sprite.y;
+						screenPosition.z = sprite.z;
+
+						size = sprite.size * sprite.scale / viewportHeight;
+
+						scale.x = size * invAspect;
+						scale.y = size;
+
+						_gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z );
+						_gl.uniform2f( uniforms.scale, scale.x, scale.y );
+						_gl.uniform1f( uniforms.rotation, sprite.rotation );
+
+						_gl.uniform1f( uniforms.opacity, sprite.opacity );
+						_gl.uniform3f( uniforms.color, sprite.color.r, sprite.color.g, sprite.color.b );
+
+						_renderer.setBlending( sprite.blending, sprite.blendEquation, sprite.blendSrc, sprite.blendDst );
+						_renderer.setTexture( sprite.texture, 1 );
+
+						_gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 );
+
+					}
+
+				}
+
+			}
+
+		}
+
+		// restore gl
+
+		_gl.enable( _gl.CULL_FACE );
+		_gl.enable( _gl.DEPTH_TEST );
+		_gl.depthMask( true );
+
+	};
+
+	function createProgram ( shader, precision ) {
+
+		var program = _gl.createProgram();
+
+		var fragmentShader = _gl.createShader( _gl.FRAGMENT_SHADER );
+		var vertexShader = _gl.createShader( _gl.VERTEX_SHADER );
+
+		var prefix = "precision " + precision + " float;\n";
+
+		_gl.shaderSource( fragmentShader, prefix + shader.fragmentShader );
+		_gl.shaderSource( vertexShader, prefix + shader.vertexShader );
+
+		_gl.compileShader( fragmentShader );
+		_gl.compileShader( vertexShader );
+
+		_gl.attachShader( program, fragmentShader );
+		_gl.attachShader( program, vertexShader );
+
+		_gl.linkProgram( program );
+
+		return program;
+
+	};
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.ShadowMapPlugin = function () {
+
+	var _gl,
+	_renderer,
+	_depthMaterial, _depthMaterialMorph, _depthMaterialSkin, _depthMaterialMorphSkin,
+
+	_frustum = new THREE.Frustum(),
+	_projScreenMatrix = new THREE.Matrix4(),
+
+	_min = new THREE.Vector3(),
+	_max = new THREE.Vector3(),
+
+	_matrixPosition = new THREE.Vector3();
+
+	this.init = function ( renderer ) {
+
+		_gl = renderer.context;
+		_renderer = renderer;
+
+		var depthShader = THREE.ShaderLib[ "depthRGBA" ];
+		var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms );
+
+		_depthMaterial = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms } );
+		_depthMaterialMorph = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true } );
+		_depthMaterialSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, skinning: true } );
+		_depthMaterialMorphSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true, skinning: true } );
+
+		_depthMaterial._shadowPass = true;
+		_depthMaterialMorph._shadowPass = true;
+		_depthMaterialSkin._shadowPass = true;
+		_depthMaterialMorphSkin._shadowPass = true;
+
+	};
+
+	this.render = function ( scene, camera ) {
+
+		if ( ! ( _renderer.shadowMapEnabled && _renderer.shadowMapAutoUpdate ) ) return;
+
+		this.update( scene, camera );
+
+	};
+
+	this.update = function ( scene, camera ) {
+
+		var i, il, j, jl, n,
+
+		shadowMap, shadowMatrix, shadowCamera,
+		program, buffer, material,
+		webglObject, object, light,
+		renderList,
+
+		lights = [],
+		k = 0,
+
+		fog = null;
+
+		// set GL state for depth map
+
+		_gl.clearColor( 1, 1, 1, 1 );
+		_gl.disable( _gl.BLEND );
+
+		_gl.enable( _gl.CULL_FACE );
+		_gl.frontFace( _gl.CCW );
+
+		if ( _renderer.shadowMapCullFace === THREE.CullFaceFront ) {
+
+			_gl.cullFace( _gl.FRONT );
+
+		} else {
+
+			_gl.cullFace( _gl.BACK );
+
+		}
+
+		_renderer.setDepthTest( true );
+
+		// preprocess lights
+		// 	- skip lights that are not casting shadows
+		//	- create virtual lights for cascaded shadow maps
+
+		for ( i = 0, il = scene.__lights.length; i < il; i ++ ) {
+
+			light = scene.__lights[ i ];
+
+			if ( ! light.castShadow ) continue;
+
+			if ( ( light instanceof THREE.DirectionalLight ) && light.shadowCascade ) {
+
+				for ( n = 0; n < light.shadowCascadeCount; n ++ ) {
+
+					var virtualLight;
+
+					if ( ! light.shadowCascadeArray[ n ] ) {
+
+						virtualLight = createVirtualLight( light, n );
+						virtualLight.originalCamera = camera;
+
+						var gyro = new THREE.Gyroscope();
+						gyro.position = light.shadowCascadeOffset;
+
+						gyro.add( virtualLight );
+						gyro.add( virtualLight.target );
+
+						camera.add( gyro );
+
+						light.shadowCascadeArray[ n ] = virtualLight;
+
+						console.log( "Created virtualLight", virtualLight );
+
+					} else {
+
+						virtualLight = light.shadowCascadeArray[ n ];
+
+					}
+
+					updateVirtualLight( light, n );
+
+					lights[ k ] = virtualLight;
+					k ++;
+
+				}
+
+			} else {
+
+				lights[ k ] = light;
+				k ++;
+
+			}
+
+		}
+
+		// render depth map
+
+		for ( i = 0, il = lights.length; i < il; i ++ ) {
+
+			light = lights[ i ];
+
+			if ( ! light.shadowMap ) {
+
+				var shadowFilter = THREE.LinearFilter;
+
+				if ( _renderer.shadowMapType === THREE.PCFSoftShadowMap ) {
+
+					shadowFilter = THREE.NearestFilter;
+
+				}
+
+				var pars = { minFilter: shadowFilter, magFilter: shadowFilter, format: THREE.RGBAFormat };
+
+				light.shadowMap = new THREE.WebGLRenderTarget( light.shadowMapWidth, light.shadowMapHeight, pars );
+				light.shadowMapSize = new THREE.Vector2( light.shadowMapWidth, light.shadowMapHeight );
+
+				light.shadowMatrix = new THREE.Matrix4();
+
+			}
+
+			if ( ! light.shadowCamera ) {
+
+				if ( light instanceof THREE.SpotLight ) {
+
+					light.shadowCamera = new THREE.PerspectiveCamera( light.shadowCameraFov, light.shadowMapWidth / light.shadowMapHeight, light.shadowCameraNear, light.shadowCameraFar );
+
+				} else if ( light instanceof THREE.DirectionalLight ) {
+
+					light.shadowCamera = new THREE.OrthographicCamera( light.shadowCameraLeft, light.shadowCameraRight, light.shadowCameraTop, light.shadowCameraBottom, light.shadowCameraNear, light.shadowCameraFar );
+
+				} else {
+
+					console.error( "Unsupported light type for shadow" );
+					continue;
+
+				}
+
+				scene.add( light.shadowCamera );
+
+				if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+			}
+
+			if ( light.shadowCameraVisible && ! light.cameraHelper ) {
+
+				light.cameraHelper = new THREE.CameraHelper( light.shadowCamera );
+				light.shadowCamera.add( light.cameraHelper );
+
+			}
+
+			if ( light.isVirtual && virtualLight.originalCamera == camera ) {
+
+				updateShadowCamera( camera, light );
+
+			}
+
+			shadowMap = light.shadowMap;
+			shadowMatrix = light.shadowMatrix;
+			shadowCamera = light.shadowCamera;
+
+			shadowCamera.position.getPositionFromMatrix( light.matrixWorld );
+			_matrixPosition.getPositionFromMatrix( light.target.matrixWorld );
+			shadowCamera.lookAt( _matrixPosition );
+			shadowCamera.updateMatrixWorld();
+
+			shadowCamera.matrixWorldInverse.getInverse( shadowCamera.matrixWorld );
+
+			if ( light.cameraHelper ) light.cameraHelper.visible = light.shadowCameraVisible;
+			if ( light.shadowCameraVisible ) light.cameraHelper.update();
+
+			// compute shadow matrix
+
+			shadowMatrix.set( 0.5, 0.0, 0.0, 0.5,
+							  0.0, 0.5, 0.0, 0.5,
+							  0.0, 0.0, 0.5, 0.5,
+							  0.0, 0.0, 0.0, 1.0 );
+
+			shadowMatrix.multiply( shadowCamera.projectionMatrix );
+			shadowMatrix.multiply( shadowCamera.matrixWorldInverse );
+
+			// update camera matrices and frustum
+
+			_projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
+			_frustum.setFromMatrix( _projScreenMatrix );
+
+			// render shadow map
+
+			_renderer.setRenderTarget( shadowMap );
+			_renderer.clear();
+
+			// set object matrices & frustum culling
+
+			renderList = scene.__webglObjects;
+
+			for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+				webglObject = renderList[ j ];
+				object = webglObject.object;
+
+				webglObject.render = false;
+
+				if ( object.visible && object.castShadow ) {
+
+					if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) {
+
+						object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
+
+						webglObject.render = true;
+
+					}
+
+				}
+
+			}
+
+			// render regular objects
+
+			var objectMaterial, useMorphing, useSkinning;
+
+			for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+				webglObject = renderList[ j ];
+
+				if ( webglObject.render ) {
+
+					object = webglObject.object;
+					buffer = webglObject.buffer;
+
+					// culling is overriden globally for all objects
+					// while rendering depth map
+
+					// need to deal with MeshFaceMaterial somehow
+					// in that case just use the first of material.materials for now
+					// (proper solution would require to break objects by materials
+					//  similarly to regular rendering and then set corresponding
+					//  depth materials per each chunk instead of just once per object)
+
+					objectMaterial = getObjectMaterial( object );
+
+					useMorphing = object.geometry.morphTargets.length > 0 && objectMaterial.morphTargets;
+					useSkinning = object instanceof THREE.SkinnedMesh && objectMaterial.skinning;
+
+					if ( object.customDepthMaterial ) {
+
+						material = object.customDepthMaterial;
+
+					} else if ( useSkinning ) {
+
+						material = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin;
+
+					} else if ( useMorphing ) {
+
+						material = _depthMaterialMorph;
+
+					} else {
+
+						material = _depthMaterial;
+
+					}
+
+					if ( buffer instanceof THREE.BufferGeometry ) {
+
+						_renderer.renderBufferDirect( shadowCamera, scene.__lights, fog, material, buffer, object );
+
+					} else {
+
+						_renderer.renderBuffer( shadowCamera, scene.__lights, fog, material, buffer, object );
+
+					}
+
+				}
+
+			}
+
+			// set matrices and render immediate objects
+
+			renderList = scene.__webglObjectsImmediate;
+
+			for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+				webglObject = renderList[ j ];
+				object = webglObject.object;
+
+				if ( object.visible && object.castShadow ) {
+
+					object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
+
+					_renderer.renderImmediateObject( shadowCamera, scene.__lights, fog, _depthMaterial, object );
+
+				}
+
+			}
+
+		}
+
+		// restore GL state
+
+		var clearColor = _renderer.getClearColor(),
+		clearAlpha = _renderer.getClearAlpha();
+
+		_gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearAlpha );
+		_gl.enable( _gl.BLEND );
+
+		if ( _renderer.shadowMapCullFace === THREE.CullFaceFront ) {
+
+			_gl.cullFace( _gl.BACK );
+
+		}
+
+	};
+
+	function createVirtualLight( light, cascade ) {
+
+		var virtualLight = new THREE.DirectionalLight();
+
+		virtualLight.isVirtual = true;
+
+		virtualLight.onlyShadow = true;
+		virtualLight.castShadow = true;
+
+		virtualLight.shadowCameraNear = light.shadowCameraNear;
+		virtualLight.shadowCameraFar = light.shadowCameraFar;
+
+		virtualLight.shadowCameraLeft = light.shadowCameraLeft;
+		virtualLight.shadowCameraRight = light.shadowCameraRight;
+		virtualLight.shadowCameraBottom = light.shadowCameraBottom;
+		virtualLight.shadowCameraTop = light.shadowCameraTop;
+
+		virtualLight.shadowCameraVisible = light.shadowCameraVisible;
+
+		virtualLight.shadowDarkness = light.shadowDarkness;
+
+		virtualLight.shadowBias = light.shadowCascadeBias[ cascade ];
+		virtualLight.shadowMapWidth = light.shadowCascadeWidth[ cascade ];
+		virtualLight.shadowMapHeight = light.shadowCascadeHeight[ cascade ];
+
+		virtualLight.pointsWorld = [];
+		virtualLight.pointsFrustum = [];
+
+		var pointsWorld = virtualLight.pointsWorld,
+			pointsFrustum = virtualLight.pointsFrustum;
+
+		for ( var i = 0; i < 8; i ++ ) {
+
+			pointsWorld[ i ] = new THREE.Vector3();
+			pointsFrustum[ i ] = new THREE.Vector3();
+
+		}
+
+		var nearZ = light.shadowCascadeNearZ[ cascade ];
+		var farZ = light.shadowCascadeFarZ[ cascade ];
+
+		pointsFrustum[ 0 ].set( -1, -1, nearZ );
+		pointsFrustum[ 1 ].set(  1, -1, nearZ );
+		pointsFrustum[ 2 ].set( -1,  1, nearZ );
+		pointsFrustum[ 3 ].set(  1,  1, nearZ );
+
+		pointsFrustum[ 4 ].set( -1, -1, farZ );
+		pointsFrustum[ 5 ].set(  1, -1, farZ );
+		pointsFrustum[ 6 ].set( -1,  1, farZ );
+		pointsFrustum[ 7 ].set(  1,  1, farZ );
+
+		return virtualLight;
+
+	}
+
+	// Synchronize virtual light with the original light
+
+	function updateVirtualLight( light, cascade ) {
+
+		var virtualLight = light.shadowCascadeArray[ cascade ];
+
+		virtualLight.position.copy( light.position );
+		virtualLight.target.position.copy( light.target.position );
+		virtualLight.lookAt( virtualLight.target );
+
+		virtualLight.shadowCameraVisible = light.shadowCameraVisible;
+		virtualLight.shadowDarkness = light.shadowDarkness;
+
+		virtualLight.shadowBias = light.shadowCascadeBias[ cascade ];
+
+		var nearZ = light.shadowCascadeNearZ[ cascade ];
+		var farZ = light.shadowCascadeFarZ[ cascade ];
+
+		var pointsFrustum = virtualLight.pointsFrustum;
+
+		pointsFrustum[ 0 ].z = nearZ;
+		pointsFrustum[ 1 ].z = nearZ;
+		pointsFrustum[ 2 ].z = nearZ;
+		pointsFrustum[ 3 ].z = nearZ;
+
+		pointsFrustum[ 4 ].z = farZ;
+		pointsFrustum[ 5 ].z = farZ;
+		pointsFrustum[ 6 ].z = farZ;
+		pointsFrustum[ 7 ].z = farZ;
+
+	}
+
+	// Fit shadow camera's ortho frustum to camera frustum
+
+	function updateShadowCamera( camera, light ) {
+
+		var shadowCamera = light.shadowCamera,
+			pointsFrustum = light.pointsFrustum,
+			pointsWorld = light.pointsWorld;
+
+		_min.set( Infinity, Infinity, Infinity );
+		_max.set( -Infinity, -Infinity, -Infinity );
+
+		for ( var i = 0; i < 8; i ++ ) {
+
+			var p = pointsWorld[ i ];
+
+			p.copy( pointsFrustum[ i ] );
+			THREE.ShadowMapPlugin.__projector.unprojectVector( p, camera );
+
+			p.applyMatrix4( shadowCamera.matrixWorldInverse );
+
+			if ( p.x < _min.x ) _min.x = p.x;
+			if ( p.x > _max.x ) _max.x = p.x;
+
+			if ( p.y < _min.y ) _min.y = p.y;
+			if ( p.y > _max.y ) _max.y = p.y;
+
+			if ( p.z < _min.z ) _min.z = p.z;
+			if ( p.z > _max.z ) _max.z = p.z;
+
+		}
+
+		shadowCamera.left = _min.x;
+		shadowCamera.right = _max.x;
+		shadowCamera.top = _max.y;
+		shadowCamera.bottom = _min.y;
+
+		// can't really fit near/far
+		//shadowCamera.near = _min.z;
+		//shadowCamera.far = _max.z;
+
+		shadowCamera.updateProjectionMatrix();
+
+	}
+
+	// For the moment just ignore objects that have multiple materials with different animation methods
+	// Only the first material will be taken into account for deciding which depth material to use for shadow maps
+
+	function getObjectMaterial( object ) {
+
+		return object.material instanceof THREE.MeshFaceMaterial
+			? object.material.materials[ 0 ]
+			: object.material;
+
+	};
+
+};
+
+THREE.ShadowMapPlugin.__projector = new THREE.Projector();
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SpritePlugin = function () {
+
+	var _gl, _renderer, _precision, _sprite = {};
+
+	this.init = function ( renderer ) {
+
+		_gl = renderer.context;
+		_renderer = renderer;
+
+		_precision = renderer.getPrecision();
+
+		_sprite.vertices = new Float32Array( 8 + 8 );
+		_sprite.faces    = new Uint16Array( 6 );
+
+		var i = 0;
+
+		_sprite.vertices[ i++ ] = -1; _sprite.vertices[ i++ ] = -1;	// vertex 0
+		_sprite.vertices[ i++ ] = 0;  _sprite.vertices[ i++ ] = 0;	// uv 0
+
+		_sprite.vertices[ i++ ] = 1;  _sprite.vertices[ i++ ] = -1;	// vertex 1
+		_sprite.vertices[ i++ ] = 1;  _sprite.vertices[ i++ ] = 0;	// uv 1
+
+		_sprite.vertices[ i++ ] = 1;  _sprite.vertices[ i++ ] = 1;	// vertex 2
+		_sprite.vertices[ i++ ] = 1;  _sprite.vertices[ i++ ] = 1;	// uv 2
+
+		_sprite.vertices[ i++ ] = -1; _sprite.vertices[ i++ ] = 1;	// vertex 3
+		_sprite.vertices[ i++ ] = 0;  _sprite.vertices[ i++ ] = 1;	// uv 3
+
+		i = 0;
+
+		_sprite.faces[ i++ ] = 0; _sprite.faces[ i++ ] = 1; _sprite.faces[ i++ ] = 2;
+		_sprite.faces[ i++ ] = 0; _sprite.faces[ i++ ] = 2; _sprite.faces[ i++ ] = 3;
+
+		_sprite.vertexBuffer  = _gl.createBuffer();
+		_sprite.elementBuffer = _gl.createBuffer();
+
+		_gl.bindBuffer( _gl.ARRAY_BUFFER, _sprite.vertexBuffer );
+		_gl.bufferData( _gl.ARRAY_BUFFER, _sprite.vertices, _gl.STATIC_DRAW );
+
+		_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _sprite.elementBuffer );
+		_gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, _sprite.faces, _gl.STATIC_DRAW );
+
+		_sprite.program = createProgram( THREE.ShaderSprite[ "sprite" ], _precision );
+
+		_sprite.attributes = {};
+		_sprite.uniforms = {};
+
+		_sprite.attributes.position           = _gl.getAttribLocation ( _sprite.program, "position" );
+		_sprite.attributes.uv                 = _gl.getAttribLocation ( _sprite.program, "uv" );
+
+		_sprite.uniforms.uvOffset             = _gl.getUniformLocation( _sprite.program, "uvOffset" );
+		_sprite.uniforms.uvScale              = _gl.getUniformLocation( _sprite.program, "uvScale" );
+
+		_sprite.uniforms.rotation             = _gl.getUniformLocation( _sprite.program, "rotation" );
+		_sprite.uniforms.scale                = _gl.getUniformLocation( _sprite.program, "scale" );
+		_sprite.uniforms.alignment            = _gl.getUniformLocation( _sprite.program, "alignment" );
+
+		_sprite.uniforms.color                = _gl.getUniformLocation( _sprite.program, "color" );
+		_sprite.uniforms.map                  = _gl.getUniformLocation( _sprite.program, "map" );
+		_sprite.uniforms.opacity              = _gl.getUniformLocation( _sprite.program, "opacity" );
+
+		_sprite.uniforms.useScreenCoordinates = _gl.getUniformLocation( _sprite.program, "useScreenCoordinates" );
+		_sprite.uniforms.sizeAttenuation   	  = _gl.getUniformLocation( _sprite.program, "sizeAttenuation" );
+		_sprite.uniforms.screenPosition    	  = _gl.getUniformLocation( _sprite.program, "screenPosition" );
+		_sprite.uniforms.modelViewMatrix      = _gl.getUniformLocation( _sprite.program, "modelViewMatrix" );
+		_sprite.uniforms.projectionMatrix     = _gl.getUniformLocation( _sprite.program, "projectionMatrix" );
+
+		_sprite.uniforms.fogType 		  	  = _gl.getUniformLocation( _sprite.program, "fogType" );
+		_sprite.uniforms.fogDensity 		  = _gl.getUniformLocation( _sprite.program, "fogDensity" );
+		_sprite.uniforms.fogNear 		  	  = _gl.getUniformLocation( _sprite.program, "fogNear" );
+		_sprite.uniforms.fogFar 		  	  = _gl.getUniformLocation( _sprite.program, "fogFar" );
+		_sprite.uniforms.fogColor 		  	  = _gl.getUniformLocation( _sprite.program, "fogColor" );
+
+		_sprite.uniforms.alphaTest 		  	  = _gl.getUniformLocation( _sprite.program, "alphaTest" );
+
+	};
+
+	this.render = function ( scene, camera, viewportWidth, viewportHeight ) {
+
+		var sprites = scene.__webglSprites,
+			nSprites = sprites.length;
+
+		if ( ! nSprites ) return;
+
+		var attributes = _sprite.attributes,
+			uniforms = _sprite.uniforms;
+
+		var invAspect = viewportHeight / viewportWidth;
+
+		var halfViewportWidth = viewportWidth * 0.5,
+			halfViewportHeight = viewportHeight * 0.5;
+
+		// setup gl
+
+		_gl.useProgram( _sprite.program );
+
+		_gl.enableVertexAttribArray( attributes.position );
+		_gl.enableVertexAttribArray( attributes.uv );
+
+		_gl.disable( _gl.CULL_FACE );
+		_gl.enable( _gl.BLEND );
+
+		_gl.bindBuffer( _gl.ARRAY_BUFFER, _sprite.vertexBuffer );
+		_gl.vertexAttribPointer( attributes.position, 2, _gl.FLOAT, false, 2 * 8, 0 );
+		_gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 2 * 8, 8 );
+
+		_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _sprite.elementBuffer );
+
+		_gl.uniformMatrix4fv( uniforms.projectionMatrix, false, camera.projectionMatrix.elements );
+
+		_gl.activeTexture( _gl.TEXTURE0 );
+		_gl.uniform1i( uniforms.map, 0 );
+
+		var oldFogType = 0;
+		var sceneFogType = 0;
+		var fog = scene.fog;
+
+		if ( fog ) {
+
+			_gl.uniform3f( uniforms.fogColor, fog.color.r, fog.color.g, fog.color.b );
+
+			if ( fog instanceof THREE.Fog ) {
+
+				_gl.uniform1f( uniforms.fogNear, fog.near );
+				_gl.uniform1f( uniforms.fogFar, fog.far );
+
+				_gl.uniform1i( uniforms.fogType, 1 );
+				oldFogType = 1;
+				sceneFogType = 1;
+
+			} else if ( fog instanceof THREE.FogExp2 ) {
+
+				_gl.uniform1f( uniforms.fogDensity, fog.density );
+
+				_gl.uniform1i( uniforms.fogType, 2 );
+				oldFogType = 2;
+				sceneFogType = 2;
+
+			}
+
+		} else {
+
+			_gl.uniform1i( uniforms.fogType, 0 );
+			oldFogType = 0;
+			sceneFogType = 0;
+
+		}
+
+
+		// update positions and sort
+
+		var i, sprite, material, screenPosition, size, fogType, scale = [];
+
+		for( i = 0; i < nSprites; i ++ ) {
+
+			sprite = sprites[ i ];
+			material = sprite.material;
+
+			if ( ! sprite.visible || material.opacity === 0 ) continue;
+
+			if ( ! material.useScreenCoordinates ) {
+
+				sprite._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld );
+				sprite.z = - sprite._modelViewMatrix.elements[ 14 ];
+
+			} else {
+
+				sprite.z = - sprite.position.z;
+
+			}
+
+		}
+
+		sprites.sort( painterSortStable );
+
+		// render all sprites
+
+		for( i = 0; i < nSprites; i ++ ) {
+
+			sprite = sprites[ i ];
+			material = sprite.material;
+
+			if ( ! sprite.visible || material.opacity === 0 ) continue;
+
+			if ( material.map && material.map.image && material.map.image.width ) {
+
+				_gl.uniform1f( uniforms.alphaTest, material.alphaTest );
+
+				if ( material.useScreenCoordinates === true ) {
+
+					_gl.uniform1i( uniforms.useScreenCoordinates, 1 );
+					_gl.uniform3f(
+						uniforms.screenPosition,
+						( ( sprite.position.x * _renderer.devicePixelRatio ) - halfViewportWidth  ) / halfViewportWidth,
+						( halfViewportHeight - ( sprite.position.y * _renderer.devicePixelRatio ) ) / halfViewportHeight,
+						Math.max( 0, Math.min( 1, sprite.position.z ) )
+					);
+
+					scale[ 0 ] = _renderer.devicePixelRatio;
+					scale[ 1 ] = _renderer.devicePixelRatio;
+
+				} else {
+
+					_gl.uniform1i( uniforms.useScreenCoordinates, 0 );
+					_gl.uniform1i( uniforms.sizeAttenuation, material.sizeAttenuation ? 1 : 0 );
+					_gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite._modelViewMatrix.elements );
+
+					scale[ 0 ] = 1;
+					scale[ 1 ] = 1;
+
+				}
+
+				if ( scene.fog && material.fog ) {
+
+					fogType = sceneFogType;
+
+				} else {
+
+					fogType = 0;
+
+				}
+
+				if ( oldFogType !== fogType ) {
+
+					_gl.uniform1i( uniforms.fogType, fogType );
+					oldFogType = fogType;
+
+				}
+
+				size = 1 / ( material.scaleByViewport ? viewportHeight : 1 );
+
+				scale[ 0 ] *= size * invAspect * sprite.scale.x
+				scale[ 1 ] *= size * sprite.scale.y;
+
+				_gl.uniform2f( uniforms.uvScale, material.uvScale.x, material.uvScale.y );
+				_gl.uniform2f( uniforms.uvOffset, material.uvOffset.x, material.uvOffset.y );
+				_gl.uniform2f( uniforms.alignment, material.alignment.x, material.alignment.y );
+
+				_gl.uniform1f( uniforms.opacity, material.opacity );
+				_gl.uniform3f( uniforms.color, material.color.r, material.color.g, material.color.b );
+
+				_gl.uniform1f( uniforms.rotation, sprite.rotation );
+				_gl.uniform2fv( uniforms.scale, scale );
+
+				_renderer.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+				_renderer.setDepthTest( material.depthTest );
+				_renderer.setDepthWrite( material.depthWrite );
+				_renderer.setTexture( material.map, 0 );
+
+				_gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 );
+
+			}
+
+		}
+
+		// restore gl
+
+		_gl.enable( _gl.CULL_FACE );
+
+	};
+
+	function createProgram ( shader, precision ) {
+
+		var program = _gl.createProgram();
+
+		var fragmentShader = _gl.createShader( _gl.FRAGMENT_SHADER );
+		var vertexShader = _gl.createShader( _gl.VERTEX_SHADER );
+
+		var prefix = "precision " + precision + " float;\n";
+
+		_gl.shaderSource( fragmentShader, prefix + shader.fragmentShader );
+		_gl.shaderSource( vertexShader, prefix + shader.vertexShader );
+
+		_gl.compileShader( fragmentShader );
+		_gl.compileShader( vertexShader );
+
+		_gl.attachShader( program, fragmentShader );
+		_gl.attachShader( program, vertexShader );
+
+		_gl.linkProgram( program );
+
+		return program;
+
+	};
+
+	function painterSortStable ( a, b ) {
+
+		if ( a.z !== b.z ) {
+
+			return b.z - a.z;
+
+		} else {
+
+			return b.id - a.id;
+
+		}
+
+	};
+
+};
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.DepthPassPlugin = function () {
+
+	this.enabled = false;
+	this.renderTarget = null;
+
+	var _gl,
+	_renderer,
+	_depthMaterial, _depthMaterialMorph, _depthMaterialSkin, _depthMaterialMorphSkin,
+
+	_frustum = new THREE.Frustum(),
+	_projScreenMatrix = new THREE.Matrix4();
+
+	this.init = function ( renderer ) {
+
+		_gl = renderer.context;
+		_renderer = renderer;
+
+		var depthShader = THREE.ShaderLib[ "depthRGBA" ];
+		var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms );
+
+		_depthMaterial = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms } );
+		_depthMaterialMorph = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true } );
+		_depthMaterialSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, skinning: true } );
+		_depthMaterialMorphSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true, skinning: true } );
+
+		_depthMaterial._shadowPass = true;
+		_depthMaterialMorph._shadowPass = true;
+		_depthMaterialSkin._shadowPass = true;
+		_depthMaterialMorphSkin._shadowPass = true;
+
+	};
+
+	this.render = function ( scene, camera ) {
+
+		if ( ! this.enabled ) return;
+
+		this.update( scene, camera );
+
+	};
+
+	this.update = function ( scene, camera ) {
+
+		var i, il, j, jl, n,
+
+		program, buffer, material,
+		webglObject, object, light,
+		renderList,
+
+		fog = null;
+
+		// set GL state for depth map
+
+		_gl.clearColor( 1, 1, 1, 1 );
+		_gl.disable( _gl.BLEND );
+
+		_renderer.setDepthTest( true );
+
+		// update scene
+
+		if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+		// update camera matrices and frustum
+
+		camera.matrixWorldInverse.getInverse( camera.matrixWorld );
+
+		_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+		_frustum.setFromMatrix( _projScreenMatrix );
+
+		// render depth map
+
+		_renderer.setRenderTarget( this.renderTarget );
+		_renderer.clear();
+
+		// set object matrices & frustum culling
+
+		renderList = scene.__webglObjects;
+
+		for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+			webglObject = renderList[ j ];
+			object = webglObject.object;
+
+			webglObject.render = false;
+
+			if ( object.visible ) {
+
+				if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) {
+
+					object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+
+					webglObject.render = true;
+
+				}
+
+			}
+
+		}
+
+		// render regular objects
+
+		var objectMaterial, useMorphing, useSkinning;
+
+		for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+			webglObject = renderList[ j ];
+
+			if ( webglObject.render ) {
+
+				object = webglObject.object;
+				buffer = webglObject.buffer;
+
+				// todo: create proper depth material for particles
+
+				if ( object instanceof THREE.ParticleSystem && !object.customDepthMaterial ) continue;
+
+				objectMaterial = getObjectMaterial( object );
+
+				if ( objectMaterial ) _renderer.setMaterialFaces( object.material );
+
+				useMorphing = object.geometry.morphTargets.length > 0 && objectMaterial.morphTargets;
+				useSkinning = object instanceof THREE.SkinnedMesh && objectMaterial.skinning;
+
+				if ( object.customDepthMaterial ) {
+
+					material = object.customDepthMaterial;
+
+				} else if ( useSkinning ) {
+
+					material = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin;
+
+				} else if ( useMorphing ) {
+
+					material = _depthMaterialMorph;
+
+				} else {
+
+					material = _depthMaterial;
+
+				}
+
+				if ( buffer instanceof THREE.BufferGeometry ) {
+
+					_renderer.renderBufferDirect( camera, scene.__lights, fog, material, buffer, object );
+
+				} else {
+
+					_renderer.renderBuffer( camera, scene.__lights, fog, material, buffer, object );
+
+				}
+
+			}
+
+		}
+
+		// set matrices and render immediate objects
+
+		renderList = scene.__webglObjectsImmediate;
+
+		for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+			webglObject = renderList[ j ];
+			object = webglObject.object;
+
+			if ( object.visible ) {
+
+				object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+
+				_renderer.renderImmediateObject( camera, scene.__lights, fog, _depthMaterial, object );
+
+			}
+
+		}
+
+		// restore GL state
+
+		var clearColor = _renderer.getClearColor(),
+		clearAlpha = _renderer.getClearAlpha();
+
+		_gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearAlpha );
+		_gl.enable( _gl.BLEND );
+
+	};
+
+	// For the moment just ignore objects that have multiple materials with different animation methods
+	// Only the first material will be taken into account for deciding which depth material to use
+
+	function getObjectMaterial( object ) {
+
+		return object.material instanceof THREE.MeshFaceMaterial
+			? object.material.materials[ 0 ]
+			: object.material;
+
+	};
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ *
+ */
+
+THREE.ShaderFlares = {
+
+	'lensFlareVertexTexture': {
+
+		vertexShader: [
+
+			"uniform lowp int renderType;",
+
+			"uniform vec3 screenPosition;",
+			"uniform vec2 scale;",
+			"uniform float rotation;",
+
+			"uniform sampler2D occlusionMap;",
+
+			"attribute vec2 position;",
+			"attribute vec2 uv;",
+
+			"varying vec2 vUV;",
+			"varying float vVisibility;",
+
+			"void main() {",
+
+				"vUV = uv;",
+
+				"vec2 pos = position;",
+
+				"if( renderType == 2 ) {",
+
+					"vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) ) +",
+									  "texture2D( occlusionMap, vec2( 0.5, 0.1 ) ) +",
+									  "texture2D( occlusionMap, vec2( 0.9, 0.1 ) ) +",
+									  "texture2D( occlusionMap, vec2( 0.9, 0.5 ) ) +",
+									  "texture2D( occlusionMap, vec2( 0.9, 0.9 ) ) +",
+									  "texture2D( occlusionMap, vec2( 0.5, 0.9 ) ) +",
+									  "texture2D( occlusionMap, vec2( 0.1, 0.9 ) ) +",
+									  "texture2D( occlusionMap, vec2( 0.1, 0.5 ) ) +",
+									  "texture2D( occlusionMap, vec2( 0.5, 0.5 ) );",
+
+					"vVisibility = (       visibility.r / 9.0 ) *",
+								  "( 1.0 - visibility.g / 9.0 ) *",
+								  "(       visibility.b / 9.0 ) *",
+								  "( 1.0 - visibility.a / 9.0 );",
+
+					"pos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;",
+					"pos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;",
+
+				"}",
+
+				"gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );",
+
+			"}"
+
+		].join( "\n" ),
+
+		fragmentShader: [
+
+			"uniform lowp int renderType;",
+
+			"uniform sampler2D map;",
+			"uniform float opacity;",
+			"uniform vec3 color;",
+
+			"varying vec2 vUV;",
+			"varying float vVisibility;",
+
+			"void main() {",
+
+				// pink square
+
+				"if( renderType == 0 ) {",
+
+					"gl_FragColor = vec4( 1.0, 0.0, 1.0, 0.0 );",
+
+				// restore
+
+				"} else if( renderType == 1 ) {",
+
+					"gl_FragColor = texture2D( map, vUV );",
+
+				// flare
+
+				"} else {",
+
+					"vec4 texture = texture2D( map, vUV );",
+					"texture.a *= opacity * vVisibility;",
+					"gl_FragColor = texture;",
+					"gl_FragColor.rgb *= color;",
+
+				"}",
+
+			"}"
+		].join( "\n" )
+
+	},
+
+
+	'lensFlare': {
+
+		vertexShader: [
+
+			"uniform lowp int renderType;",
+
+			"uniform vec3 screenPosition;",
+			"uniform vec2 scale;",
+			"uniform float rotation;",
+
+			"attribute vec2 position;",
+			"attribute vec2 uv;",
+
+			"varying vec2 vUV;",
+
+			"void main() {",
+
+				"vUV = uv;",
+
+				"vec2 pos = position;",
+
+				"if( renderType == 2 ) {",
+
+					"pos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;",
+					"pos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;",
+
+				"}",
+
+				"gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );",
+
+			"}"
+
+		].join( "\n" ),
+
+		fragmentShader: [
+
+			"precision mediump float;",
+
+			"uniform lowp int renderType;",
+
+			"uniform sampler2D map;",
+			"uniform sampler2D occlusionMap;",
+			"uniform float opacity;",
+			"uniform vec3 color;",
+
+			"varying vec2 vUV;",
+
+			"void main() {",
+
+				// pink square
+
+				"if( renderType == 0 ) {",
+
+					"gl_FragColor = vec4( texture2D( map, vUV ).rgb, 0.0 );",
+
+				// restore
+
+				"} else if( renderType == 1 ) {",
+
+					"gl_FragColor = texture2D( map, vUV );",
+
+				// flare
+
+				"} else {",
+
+					"float visibility = texture2D( occlusionMap, vec2( 0.5, 0.1 ) ).a +",
+									   "texture2D( occlusionMap, vec2( 0.9, 0.5 ) ).a +",
+									   "texture2D( occlusionMap, vec2( 0.5, 0.9 ) ).a +",
+									   "texture2D( occlusionMap, vec2( 0.1, 0.5 ) ).a;",
+
+					"visibility = ( 1.0 - visibility / 4.0 );",
+
+					"vec4 texture = texture2D( map, vUV );",
+					"texture.a *= opacity * visibility;",
+					"gl_FragColor = texture;",
+					"gl_FragColor.rgb *= color;",
+
+				"}",
+
+			"}"
+
+		].join( "\n" )
+
+	}
+
+};
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ */
+
+THREE.ShaderSprite = {
+
+	'sprite': {
+
+		vertexShader: [
+
+			"uniform int useScreenCoordinates;",
+			"uniform int sizeAttenuation;",
+			"uniform vec3 screenPosition;",
+			"uniform mat4 modelViewMatrix;",
+			"uniform mat4 projectionMatrix;",
+			"uniform float rotation;",
+			"uniform vec2 scale;",
+			"uniform vec2 alignment;",
+			"uniform vec2 uvOffset;",
+			"uniform vec2 uvScale;",
+
+			"attribute vec2 position;",
+			"attribute vec2 uv;",
+
+			"varying vec2 vUV;",
+
+			"void main() {",
+
+				"vUV = uvOffset + uv * uvScale;",
+
+				"vec2 alignedPosition = position + alignment;",
+
+				"vec2 rotatedPosition;",
+				"rotatedPosition.x = ( cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y ) * scale.x;",
+				"rotatedPosition.y = ( sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y ) * scale.y;",
+
+				"vec4 finalPosition;",
+
+				"if( useScreenCoordinates != 0 ) {",
+
+					"finalPosition = vec4( screenPosition.xy + rotatedPosition, screenPosition.z, 1.0 );",
+
+				"} else {",
+
+					"finalPosition = projectionMatrix * modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );",
+					"finalPosition.xy += rotatedPosition * ( sizeAttenuation == 1 ? 1.0 : finalPosition.z );",
+
+				"}",
+
+				"gl_Position = finalPosition;",
+
+			"}"
+
+		].join( "\n" ),
+
+		fragmentShader: [
+
+			"uniform vec3 color;",
+			"uniform sampler2D map;",
+			"uniform float opacity;",
+
+			"uniform int fogType;",
+			"uniform vec3 fogColor;",
+			"uniform float fogDensity;",
+			"uniform float fogNear;",
+			"uniform float fogFar;",
+			"uniform float alphaTest;",
+
+			"varying vec2 vUV;",
+
+			"void main() {",
+
+				"vec4 texture = texture2D( map, vUV );",
+
+				"if ( texture.a < alphaTest ) discard;",
+
+				"gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );",
+
+				"if ( fogType > 0 ) {",
+
+					"float depth = gl_FragCoord.z / gl_FragCoord.w;",
+					"float fogFactor = 0.0;",
+
+					"if ( fogType == 1 ) {",
+
+						"fogFactor = smoothstep( fogNear, fogFar, depth );",
+
+					"} else {",
+
+						"const float LOG2 = 1.442695;",
+						"float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );",
+						"fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );",
+
+					"}",
+
+					"gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );",
+
+				"}",
+
+			"}"
+
+		].join( "\n" )
+
+	}
+
+};
+
+/**
+ * @author Eberhard Graether / http://egraether.com/
+ */
+
+THREE.TrackballControls = function ( object, domElement ) {
+
+	var _this = this;
+	var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 };
+
+	this.object = object;
+	this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+	// API
+
+	this.enabled = true;
+
+	this.screen = { width: 0, height: 0, offsetLeft: 0, offsetTop: 0 };
+	this.radius = ( this.screen.width + this.screen.height ) / 4;
+
+	this.rotateSpeed = 1.0;
+	this.zoomSpeed = 1.2;
+	this.panSpeed = 0.3;
+
+	this.noRotate = false;
+	this.noZoom = false;
+	this.noPan = false;
+
+	this.staticMoving = false;
+	this.dynamicDampingFactor = 0.2;
+
+	this.minDistance = 0;
+	this.maxDistance = Infinity;
+
+	this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
+
+	// internals
+
+	this.target = new THREE.Vector3();
+
+	var lastPosition = new THREE.Vector3();
+
+	var _state = STATE.NONE,
+	_prevState = STATE.NONE,
+
+	_eye = new THREE.Vector3(),
+
+	_rotateStart = new THREE.Vector3(),
+	_rotateEnd = new THREE.Vector3(),
+
+	_zoomStart = new THREE.Vector2(),
+	_zoomEnd = new THREE.Vector2(),
+
+	_touchZoomDistanceStart = 0,
+	_touchZoomDistanceEnd = 0,
+
+	_panStart = new THREE.Vector2(),
+	_panEnd = new THREE.Vector2();
+
+	// for reset
+
+	this.target0 = this.target.clone();
+	this.position0 = this.object.position.clone();
+	this.up0 = this.object.up.clone();
+
+	// events
+
+	var changeEvent = { type: 'change' };
+
+
+	// methods
+
+	this.handleResize = function () {
+
+		this.screen.width = window.innerWidth;
+		this.screen.height = window.innerHeight;
+
+		this.screen.offsetLeft = 0;
+		this.screen.offsetTop = 0;
+
+		this.radius = ( this.screen.width + this.screen.height ) / 4;
+
+	};
+
+	this.handleEvent = function ( event ) {
+
+		if ( typeof this[ event.type ] == 'function' ) {
+
+			this[ event.type ]( event );
+
+		}
+
+	};
+
+	this.getMouseOnScreen = function ( clientX, clientY ) {
+
+		return new THREE.Vector2(
+			( clientX - _this.screen.offsetLeft ) / _this.radius * 0.5,
+			( clientY - _this.screen.offsetTop ) / _this.radius * 0.5
+		);
+
+	};
+
+	this.getMouseProjectionOnBall = function ( clientX, clientY ) {
+
+		var mouseOnBall = new THREE.Vector3(
+			( clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft ) / _this.radius,
+			( _this.screen.height * 0.5 + _this.screen.offsetTop - clientY ) / _this.radius,
+			0.0
+		);
+
+		var length = mouseOnBall.length();
+
+		if ( length > 1.0 ) {
+
+			mouseOnBall.normalize();
+
+		} else {
+
+			mouseOnBall.z = Math.sqrt( 1.0 - length * length );
+
+		}
+
+		_eye.copy( _this.object.position ).sub( _this.target );
+
+		var projection = _this.object.up.clone().setLength( mouseOnBall.y );
+		projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) );
+		projection.add( _eye.setLength( mouseOnBall.z ) );
+
+		return projection;
+
+	};
+
+	this.rotateCamera = function () {
+
+		var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
+
+		if ( angle ) {
+
+			var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(),
+				quaternion = new THREE.Quaternion();
+
+			angle *= _this.rotateSpeed;
+
+			quaternion.setFromAxisAngle( axis, -angle );
+
+			_eye.applyQuaternion( quaternion );
+			_this.object.up.applyQuaternion( quaternion );
+
+			_rotateEnd.applyQuaternion( quaternion );
+
+			if ( _this.staticMoving ) {
+
+				_rotateStart.copy( _rotateEnd );
+
+			} else {
+
+				quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
+				_rotateStart.applyQuaternion( quaternion );
+
+			}
+
+		}
+
+	};
+
+	this.zoomCamera = function () {
+
+		if ( _state === STATE.TOUCH_ZOOM ) {
+
+			var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
+			_touchZoomDistanceStart = _touchZoomDistanceEnd;
+			_eye.multiplyScalar( factor );
+
+		} else {
+
+			var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
+
+			if ( factor !== 1.0 && factor > 0.0 ) {
+
+				_eye.multiplyScalar( factor );
+
+				if ( _this.staticMoving ) {
+
+					_zoomStart.copy( _zoomEnd );
+
+				} else {
+
+					_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
+
+				}
+
+			}
+
+		}
+
+	};
+
+	this.panCamera = function () {
+
+		var mouseChange = _panEnd.clone().sub( _panStart );
+
+		if ( mouseChange.lengthSq() ) {
+
+			mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
+
+			var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x );
+			pan.add( _this.object.up.clone().setLength( mouseChange.y ) );
+
+			_this.object.position.add( pan );
+			_this.target.add( pan );
+
+			if ( _this.staticMoving ) {
+
+				_panStart = _panEnd;
+
+			} else {
+
+				_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
+
+			}
+
+		}
+
+	};
+
+	this.checkDistances = function () {
+
+		if ( !_this.noZoom || !_this.noPan ) {
+
+			if ( _this.object.position.lengthSq() > _this.maxDistance * _this.maxDistance ) {
+
+				_this.object.position.setLength( _this.maxDistance );
+
+			}
+
+			if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
+
+				_this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
+
+			}
+
+		}
+
+	};
+
+	this.update = function () {
+
+		_eye.subVectors( _this.object.position, _this.target );
+
+		if ( !_this.noRotate ) {
+
+			_this.rotateCamera();
+
+		}
+
+		if ( !_this.noZoom ) {
+
+			_this.zoomCamera();
+
+		}
+
+		if ( !_this.noPan ) {
+
+			_this.panCamera();
+
+		}
+
+		_this.object.position.addVectors( _this.target, _eye );
+
+		_this.checkDistances();
+
+		_this.object.lookAt( _this.target );
+
+		if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) {
+
+			_this.dispatchEvent( changeEvent );
+
+			lastPosition.copy( _this.object.position );
+
+		}
+
+	};
+
+	this.reset = function () {
+
+		_state = STATE.NONE;
+		_prevState = STATE.NONE;
+
+		_this.target.copy( _this.target0 );
+		_this.object.position.copy( _this.position0 );
+		_this.object.up.copy( _this.up0 );
+
+		_eye.subVectors( _this.object.position, _this.target );
+
+		_this.object.lookAt( _this.target );
+
+		_this.dispatchEvent( changeEvent );
+
+		lastPosition.copy( _this.object.position );
+
+	};
+
+	// listeners
+
+	function keydown( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		window.removeEventListener( 'keydown', keydown );
+
+		_prevState = _state;
+
+		if ( _state !== STATE.NONE ) {
+
+			return;
+
+		} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {
+
+			_state = STATE.ROTATE;
+
+		} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {
+
+			_state = STATE.ZOOM;
+
+		} else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {
+
+			_state = STATE.PAN;
+
+		}
+
+	}
+
+	function keyup( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		_state = _prevState;
+
+		window.addEventListener( 'keydown', keydown, false );
+
+	}
+
+	function mousedown( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( _state === STATE.NONE ) {
+
+			_state = event.button;
+
+		}
+
+		if ( _state === STATE.ROTATE && !_this.noRotate ) {
+
+			_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
+
+		} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
+
+			_zoomStart = _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
+
+		} else if ( _state === STATE.PAN && !_this.noPan ) {
+
+			_panStart = _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
+
+		}
+
+		document.addEventListener( 'mousemove', mousemove, false );
+		document.addEventListener( 'mouseup', mouseup, false );
+
+	}
+
+	function mousemove( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( _state === STATE.ROTATE && !_this.noRotate ) {
+
+			_rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
+
+		} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
+
+			_zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
+
+		} else if ( _state === STATE.PAN && !_this.noPan ) {
+
+			_panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
+
+		}
+
+	}
+
+	function mouseup( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		_state = STATE.NONE;
+
+		document.removeEventListener( 'mousemove', mousemove );
+		document.removeEventListener( 'mouseup', mouseup );
+
+	}
+
+	function mousewheel( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		var delta = 0;
+
+		if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
+
+			delta = event.wheelDelta / 40;
+
+		} else if ( event.detail ) { // Firefox
+
+			delta = - event.detail / 3;
+
+		}
+
+		_zoomStart.y += delta * 0.01;
+
+	}
+
+	function touchstart( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				_state = STATE.TOUCH_ROTATE;
+				_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				break;
+
+			case 2:
+				_state = STATE.TOUCH_ZOOM;
+				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+				_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
+				break;
+
+			case 3:
+				_state = STATE.TOUCH_PAN;
+				_panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				break;
+
+			default:
+				_state = STATE.NONE;
+
+		}
+
+	}
+
+	function touchmove( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				_rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				break;
+
+			case 2:
+				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+				_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy )
+				break;
+
+			case 3:
+				_panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				break;
+
+			default:
+				_state = STATE.NONE;
+
+		}
+
+	}
+
+	function touchend( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				break;
+
+			case 2:
+				_touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
+				break;
+
+			case 3:
+				_panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				break;
+
+		}
+
+		_state = STATE.NONE;
+
+	}
+
+	this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
+
+	this.domElement.addEventListener( 'mousedown', mousedown, false );
+
+	this.domElement.addEventListener( 'mousewheel', mousewheel, false );
+	this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
+
+	this.domElement.addEventListener( 'touchstart', touchstart, false );
+	this.domElement.addEventListener( 'touchend', touchend, false );
+	this.domElement.addEventListener( 'touchmove', touchmove, false );
+
+	window.addEventListener( 'keydown', keydown, false );
+	window.addEventListener( 'keyup', keyup, false );
+
+	this.handleResize();
+
+};
+
+THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+
+/**
+ * @author bhouston / http://exocortex.com/
+ * @author zz85 / http://github.com/zz85
+ */
+
+THREE.ColorConverter = {
+
+	setHSV: function ( color, h, s, v ) {
+
+		// https://gist.github.com/xpansive/1337890#file-index-js
+		return color.setHSL( h, ( s * v ) / ( ( h = ( 2 - s ) * v ) < 1 ? h : ( 2 - h ) ), h * 0.5 );
+
+	},
+
+	getHSV: function( color ) {
+
+		var hsl = color.getHSL();
+
+		// based on https://gist.github.com/xpansive/1337890#file-index-js
+		hsl.s *= ( hsl.l < 0.5 ) ? hsl.l : ( 1 - hsl.l );
+
+		return {
+			h: hsl.h,
+			s: 2 * hsl.s / ( hsl.l + hsl.s ),
+			v: hsl.l + hsl.s
+		};
+	},
+
+	// where c, m, y, k is between 0 and 1
+	
+	setCMYK: function ( color, c, m, y, k ) {
+
+		var r = ( 1 - c ) * ( 1 - k );
+		var g = ( 1 - m ) * ( 1 - k );
+		var b = ( 1 - y ) * ( 1 - k );
+
+		return color.setRGB( r, g, b );
+
+	},
+
+	getCMYK: function ( color ) {
+
+		var r = color.r;
+		var g = color.g;
+		var b = color.b;
+		var k = 1 - Math.max(r, g, b);
+		var c = ( 1 - r - k ) / ( 1 - k );
+		var m = ( 1 - g - k ) / ( 1 - k );
+		var y = ( 1 - b - k ) / ( 1 - k );
+
+		return {
+			c: c, m: m, y: y, k: k
+		};
+
+	}
+
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.SVGRenderer = function () {
+
+	console.log( 'THREE.SVGRenderer', THREE.REVISION );
+
+	var _this = this,
+	_renderData, _elements, _lights,
+	_projector = new THREE.Projector(),
+	_svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
+	_svgWidth, _svgHeight, _svgWidthHalf, _svgHeightHalf,
+
+	_v1, _v2, _v3, _v4,
+
+	_clipBox = new THREE.Box2(),
+	_elemBox = new THREE.Box2(),
+
+	_color = new THREE.Color(),
+	_diffuseColor = new THREE.Color(),
+	_ambientLight = new THREE.Color(),
+	_directionalLights = new THREE.Color(),
+	_pointLights = new THREE.Color(),
+
+	_w, // z-buffer to w-buffer
+	_vector3 = new THREE.Vector3(), // Needed for PointLight
+
+	_svgPathPool = [], _svgCirclePool = [], _svgLinePool = [],
+	_svgNode, _pathCount, _circleCount, _lineCount,
+	_quality = 1;
+
+	this.domElement = _svg;
+
+	this.autoClear = true;
+	this.sortObjects = true;
+	this.sortElements = true;
+
+	this.info = {
+
+		render: {
+
+			vertices: 0,
+			faces: 0
+
+		}
+
+	}
+
+	this.setQuality = function( quality ) {
+
+		switch(quality) {
+
+			case "high": _quality = 1; break;
+			case "low": _quality = 0; break;
+
+		}
+
+	};
+
+	// WebGLRenderer compatibility
+
+	this.supportsVertexTextures = function () {};
+	this.setFaceCulling = function () {};
+
+	this.setClearColor = function ( color, alpha ) {
+
+		// TODO
+
+	};
+
+	this.setSize = function( width, height ) {
+
+		_svgWidth = width; _svgHeight = height;
+		_svgWidthHalf = _svgWidth / 2; _svgHeightHalf = _svgHeight / 2;
+
+		_svg.setAttribute( 'viewBox', ( - _svgWidthHalf ) + ' ' + ( - _svgHeightHalf ) + ' ' + _svgWidth + ' ' + _svgHeight );
+		_svg.setAttribute( 'width', _svgWidth );
+		_svg.setAttribute( 'height', _svgHeight );
+
+		_clipBox.min.set( - _svgWidthHalf, - _svgHeightHalf );
+		_clipBox.max.set( _svgWidthHalf, _svgHeightHalf );
+
+	};
+
+	this.clear = function () {
+
+		_pathCount = 0;
+		_circleCount = 0;
+		_lineCount = 0;
+
+		while ( _svg.childNodes.length > 0 ) {
+
+			_svg.removeChild( _svg.childNodes[ 0 ] );
+
+		}
+
+	};
+
+	this.render = function ( scene, camera ) {
+
+		if ( camera instanceof THREE.Camera === false ) {
+
+			console.error( 'THREE.SVGRenderer.render: camera is not an instance of THREE.Camera.' );
+			return;
+
+		}
+
+		if ( this.autoClear === true ) this.clear();
+
+		_this.info.render.vertices = 0;
+		_this.info.render.faces = 0;
+
+		_renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements );
+		_elements = _renderData.elements;
+		_lights = _renderData.lights;
+
+		calculateLights( _lights );
+
+		for ( var e = 0, el = _elements.length; e < el; e ++ ) {
+
+			var element = _elements[ e ];
+			var material = element.material;
+
+			if ( material === undefined || material.visible === false ) continue;
+
+			_elemBox.makeEmpty();
+
+			if ( element instanceof THREE.RenderableParticle ) {
+
+				_v1 = element;
+				_v1.x *= _svgWidthHalf; _v1.y *= -_svgHeightHalf;
+
+				renderParticle( _v1, element, material );
+
+			} else if ( element instanceof THREE.RenderableLine ) {
+
+				_v1 = element.v1; _v2 = element.v2;
+
+				_v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
+				_v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
+
+				_elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] );
+
+				if ( _clipBox.isIntersectionBox( _elemBox ) === true ) {
+
+					renderLine( _v1, _v2, element, material );
+
+				}
+
+			} else if ( element instanceof THREE.RenderableFace3 ) {
+
+				_v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
+
+				if ( _v1.positionScreen.z < -1 || _v1.positionScreen.z > 1 ) continue;
+				if ( _v2.positionScreen.z < -1 || _v2.positionScreen.z > 1 ) continue;
+				if ( _v3.positionScreen.z < -1 || _v3.positionScreen.z > 1 ) continue;
+
+				_v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
+				_v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
+				_v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf;
+
+				_elemBox.setFromPoints( [
+					_v1.positionScreen,
+					_v2.positionScreen,
+					_v3.positionScreen
+				] );
+
+				if ( _clipBox.isIntersectionBox( _elemBox ) === true ) {
+
+					renderFace3( _v1, _v2, _v3, element, material );
+
+				}
+
+			} else if ( element instanceof THREE.RenderableFace4 ) {
+
+				_v1 = element.v1; _v2 = element.v2; _v3 = element.v3; _v4 = element.v4;
+
+				if ( _v1.positionScreen.z < -1 || _v1.positionScreen.z > 1 ) continue;
+				if ( _v2.positionScreen.z < -1 || _v2.positionScreen.z > 1 ) continue;
+				if ( _v3.positionScreen.z < -1 || _v3.positionScreen.z > 1 ) continue;
+				if ( _v4.positionScreen.z < -1 || _v4.positionScreen.z > 1 ) continue;
+
+				_v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= -_svgHeightHalf;
+				_v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= -_svgHeightHalf;
+				_v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= -_svgHeightHalf;
+				_v4.positionScreen.x *= _svgWidthHalf; _v4.positionScreen.y *= -_svgHeightHalf;
+
+				_elemBox.setFromPoints( [
+					_v1.positionScreen,
+					_v2.positionScreen,
+					_v3.positionScreen,
+					_v4.positionScreen
+				] );
+
+				if ( _clipBox.isIntersectionBox( _elemBox ) === true ) {
+
+					renderFace4( _v1, _v2, _v3, _v4, element, material );
+
+				}
+
+			}
+
+		}
+
+	};
+
+	function calculateLights( lights ) {
+
+		_ambientLight.setRGB( 0, 0, 0 );
+		_directionalLights.setRGB( 0, 0, 0 );
+		_pointLights.setRGB( 0, 0, 0 );
+
+		for ( var l = 0, ll = lights.length; l < ll; l++ ) {
+
+			var light = lights[ l ];
+			var lightColor = light.color;
+
+			if ( light instanceof THREE.AmbientLight ) {
+
+				_ambientLight.r += lightColor.r;
+				_ambientLight.g += lightColor.g;
+				_ambientLight.b += lightColor.b;
+
+			} else if ( light instanceof THREE.DirectionalLight ) {
+
+				_directionalLights.r += lightColor.r;
+				_directionalLights.g += lightColor.g;
+				_directionalLights.b += lightColor.b;
+
+			} else if ( light instanceof THREE.PointLight ) {
+
+				_pointLights.r += lightColor.r;
+				_pointLights.g += lightColor.g;
+				_pointLights.b += lightColor.b;
+
+			}
+
+		}
+
+	}
+
+	function calculateLight( lights, position, normal, color ) {
+
+		for ( var l = 0, ll = lights.length; l < ll; l ++ ) {
+
+			var light = lights[ l ];
+			var lightColor = light.color;
+
+			if ( light instanceof THREE.DirectionalLight ) {
+
+				var lightPosition = _vector3.getPositionFromMatrix( light.matrixWorld ).normalize();
+
+				var amount = normal.dot( lightPosition );
+
+				if ( amount <= 0 ) continue;
+
+				amount *= light.intensity;
+
+				color.r += lightColor.r * amount;
+				color.g += lightColor.g * amount;
+				color.b += lightColor.b * amount;
+
+			} else if ( light instanceof THREE.PointLight ) {
+
+				var lightPosition = _vector3.getPositionFromMatrix( light.matrixWorld );
+
+				var amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() );
+
+				if ( amount <= 0 ) continue;
+
+				amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 );
+
+				if ( amount == 0 ) continue;
+
+				amount *= light.intensity;
+
+				color.r += lightColor.r * amount;
+				color.g += lightColor.g * amount;
+				color.b += lightColor.b * amount;
+
+			}
+
+		}
+
+	}
+
+	function renderParticle( v1, element, material ) {
+
+		/*
+		_svgNode = getCircleNode( _circleCount++ );
+		_svgNode.setAttribute( 'cx', v1.x );
+		_svgNode.setAttribute( 'cy', v1.y );
+		_svgNode.setAttribute( 'r', element.scale.x * _svgWidthHalf );
+
+		if ( material instanceof THREE.ParticleCircleMaterial ) {
+
+			_color.r = _ambientLight.r + _directionalLights.r + _pointLights.r;
+			_color.g = _ambientLight.g + _directionalLights.g + _pointLights.g;
+			_color.b = _ambientLight.b + _directionalLights.b + _pointLights.b;
+
+			_color.r = material.color.r * _color.r;
+			_color.g = material.color.g * _color.g;
+			_color.b = material.color.b * _color.b;
+
+			_color.updateStyleString();
+
+			_svgNode.setAttribute( 'style', 'fill: ' + _color.__styleString );
+
+		}
+
+		_svg.appendChild( _svgNode );
+		*/
+
+	}
+
+	function renderLine ( v1, v2, element, material ) {
+
+		_svgNode = getLineNode( _lineCount ++ );
+
+		_svgNode.setAttribute( 'x1', v1.positionScreen.x );
+		_svgNode.setAttribute( 'y1', v1.positionScreen.y );
+		_svgNode.setAttribute( 'x2', v2.positionScreen.x );
+		_svgNode.setAttribute( 'y2', v2.positionScreen.y );
+
+		if ( material instanceof THREE.LineBasicMaterial ) {
+
+			_svgNode.setAttribute( 'style', 'fill: none; stroke: ' + material.color.getStyle() + '; stroke-width: ' + material.linewidth + '; stroke-opacity: ' + material.opacity + '; stroke-linecap: ' + material.linecap + '; stroke-linejoin: ' + material.linejoin );
+
+			_svg.appendChild( _svgNode );
+
+		}
+
+	}
+
+	function renderFace3( v1, v2, v3, element, material ) {
+
+		_this.info.render.vertices += 3;
+		_this.info.render.faces ++;
+
+		_svgNode = getPathNode( _pathCount ++ );
+		_svgNode.setAttribute( 'd', 'M ' + v1.positionScreen.x + ' ' + v1.positionScreen.y + ' L ' + v2.positionScreen.x + ' ' + v2.positionScreen.y + ' L ' + v3.positionScreen.x + ',' + v3.positionScreen.y + 'z' );
+
+		if ( material instanceof THREE.MeshBasicMaterial ) {
+
+			_color.copy( material.color );
+
+			if ( material.vertexColors === THREE.FaceColors ) {
+
+				_color.multiply( element.color );
+
+			}
+
+		} else if ( material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) {
+
+			_diffuseColor.copy( material.color );
+
+			if ( material.vertexColors === THREE.FaceColors ) {
+
+				_diffuseColor.multiply( element.color );
+
+			}
+
+			_color.copy( _ambientLight );
+
+			calculateLight( _lights, element.centroidModel, element.normalModel, _color );
+
+			_color.multiply( _diffuseColor ).add( material.emissive );
+
+		} else if ( material instanceof THREE.MeshDepthMaterial ) {
+
+			_w = 1 - ( material.__2near / (material.__farPlusNear - element.z * material.__farMinusNear) );
+			_color.setRGB( _w, _w, _w );
+
+		} else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+			var normal = element.normalModelView;
+
+			_color.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+		}
+
+		if ( material.wireframe ) {
+
+			_svgNode.setAttribute( 'style', 'fill: none; stroke: ' + _color.getStyle() + '; stroke-width: ' + material.wireframeLinewidth + '; stroke-opacity: ' + material.opacity + '; stroke-linecap: ' + material.wireframeLinecap + '; stroke-linejoin: ' + material.wireframeLinejoin );
+
+		} else {
+
+			_svgNode.setAttribute( 'style', 'fill: ' + _color.getStyle() + '; fill-opacity: ' + material.opacity );
+
+		}
+
+		_svg.appendChild( _svgNode );
+
+	}
+
+	function renderFace4( v1, v2, v3, v4, element, material ) {
+
+		_this.info.render.vertices += 4;
+		_this.info.render.faces ++;
+
+		_svgNode = getPathNode( _pathCount ++ );
+		_svgNode.setAttribute( 'd', 'M ' + v1.positionScreen.x + ' ' + v1.positionScreen.y + ' L ' + v2.positionScreen.x + ' ' + v2.positionScreen.y + ' L ' + v3.positionScreen.x + ',' + v3.positionScreen.y + ' L ' + v4.positionScreen.x + ',' + v4.positionScreen.y + 'z' );
+
+		if ( material instanceof THREE.MeshBasicMaterial ) {
+
+			_color.copy( material.color );
+
+			if ( material.vertexColors === THREE.FaceColors ) {
+
+				_color.multiply( element.color );
+
+			}
+
+		} else if ( material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) {
+
+			_diffuseColor.copy( material.color );
+
+			if ( material.vertexColors === THREE.FaceColors ) {
+
+				_diffuseColor.multiply( element.color );
+
+			}
+
+			_color.copy( _ambientLight );
+
+			calculateLight( _lights, element.centroidModel, element.normalModel, _color );
+
+			_color.multiply( _diffuseColor ).add( material.emissive );
+
+		} else if ( material instanceof THREE.MeshDepthMaterial ) {
+
+			_w = 1 - ( material.__2near / (material.__farPlusNear - element.z * material.__farMinusNear) );
+			_color.setRGB( _w, _w, _w );
+
+		} else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+			var normal = element.normalModelView;
+
+			_color.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+		}
+
+		if ( material.wireframe ) {
+
+			_svgNode.setAttribute( 'style', 'fill: none; stroke: ' + _color.getStyle() + '; stroke-width: ' + material.wireframeLinewidth + '; stroke-opacity: ' + material.opacity + '; stroke-linecap: ' + material.wireframeLinecap + '; stroke-linejoin: ' + material.wireframeLinejoin );
+
+		} else {
+
+			_svgNode.setAttribute( 'style', 'fill: ' + _color.getStyle() + '; fill-opacity: ' + material.opacity );
+
+		}
+
+		_svg.appendChild( _svgNode );
+
+	}
+
+	function getLineNode( id ) {
+
+		if ( _svgLinePool[ id ] == null ) {
+
+			_svgLinePool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'line' );
+
+			if ( _quality == 0 ) {
+
+				_svgLinePool[ id ].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed
+
+			}
+
+			return _svgLinePool[ id ];
+
+		}
+
+		return _svgLinePool[ id ];
+
+	}
+
+	function getPathNode( id ) {
+
+		if ( _svgPathPool[ id ] == null ) {
+
+			_svgPathPool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
+
+			if ( _quality == 0 ) {
+
+				_svgPathPool[ id ].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed
+
+			}
+
+			return _svgPathPool[ id ];
+
+		}
+
+		return _svgPathPool[ id ];
+
+	}
+
+	function getCircleNode( id ) {
+
+		if ( _svgCirclePool[id] == null ) {
+
+			_svgCirclePool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'circle' );
+
+			if ( _quality == 0 ) {
+
+				_svgCirclePool[id].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed
+
+			}
+
+			return _svgCirclePool[ id ];
+
+		}
+
+		return _svgCirclePool[ id ];
+
+	}
+
+	function pad( str ) {
+
+		while ( str.length < 6 ) str = '0' + str;
+		return str;
+
+	}
+
+};
diff --git a/emperor/support_files/js/d3.parcoords.js b/emperor/support_files/js/d3.parcoords.js
new file mode 100644
index 0000000..3a75e2c
--- /dev/null
+++ b/emperor/support_files/js/d3.parcoords.js
@@ -0,0 +1,568 @@
+d3.parcoords = function(config) {
+  var __ = {
+    data: [],
+    dimensions: [],
+    types: {},
+    brushed: false,
+    mode: "default",
+    rate: 20,
+    width: 600,
+    height: 300,
+    margin: { top: 24, right: 0, bottom: 12, left: 0 },
+    color: "#069",
+    composite: "source-over",
+    alpha: 0.7
+  };
+
+  extend(__, config);
+var pc = function(selection) {
+  selection = pc.selection = d3.select(selection);
+
+  __.width = selection[0][0].clientWidth;
+  __.height = selection[0][0].clientHeight;
+
+  // canvas data layers
+  ["shadows", "marks", "foreground", "highlight"].forEach(function(layer) {
+    canvas[layer] = selection
+      .append("canvas")
+      .attr("class", layer)[0][0];
+    ctx[layer] = canvas[layer].getContext("2d");
+  });
+
+  // svg tick and brush layers
+  pc.svg = selection
+    .append("svg")
+      .attr("width", __.width)
+      .attr("height", __.height)
+    .append("svg:g")
+      .attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
+
+  return pc;
+};
+var events = d3.dispatch.apply(this,["render", "resize", "highlight", "brush"].concat(d3.keys(__))),
+    w = function() { return __.width - __.margin.right - __.margin.left; },
+    h = function() { return __.height - __.margin.top - __.margin.bottom },
+    flags = {
+      brushable: false,
+      reorderable: false,
+      axes: false,
+      interactive: false,
+      shadows: false,
+      debug: false
+    },
+    xscale = d3.scale.ordinal(),
+    yscale = {},
+    dragging = {},
+    line = d3.svg.line(),
+    axis = d3.svg.axis().orient("left").ticks(5),
+    g, // groups for axes, brushes
+    ctx = {},
+    canvas = {};
+
+// side effects for setters
+var side_effects = d3.dispatch.apply(this,d3.keys(__))
+  .on("composite", function(d) { ctx.foreground.globalCompositeOperation = d.value; })
+  .on("alpha", function(d) { ctx.foreground.globalAlpha = d.value; })
+  .on("width", function(d) { pc.resize(); })
+  .on("height", function(d) { pc.resize(); })
+  .on("margin", function(d) { pc.resize(); })
+  .on("rate", function(d) { rqueue.rate(d.value); })
+  .on("data", function(d) {
+    if (flags.shadows) paths(__.data, ctx.shadows);
+  })
+  .on("dimensions", function(d) {
+    xscale.domain(__.dimensions);
+    if (flags.interactive) pc.render().updateAxes();
+  });
+
+// expose the state of the chart
+pc.state = __;
+pc.flags = flags;
+
+// create getter/setters
+getset(pc, __, events);
+
+// expose events
+d3.rebind(pc, events, "on");
+
+// tick formatting
+d3.rebind(pc, axis, "ticks", "orient", "tickValues", "tickSubdivide", "tickSize", "tickPadding", "tickFormat");
+
+// getter/setter with event firing
+function getset(obj,state,events)  {
+  d3.keys(state).forEach(function(key) {
+    obj[key] = function(x) {
+      if (!arguments.length) return state[key];
+      var old = state[key];
+      state[key] = x;
+      side_effects[key].call(pc,{"value": x, "previous": old});
+      events[key].call(pc,{"value": x, "previous": old});
+      return obj;
+    };
+  });
+};
+
+function extend(target, source) {
+  for (key in source) {
+    target[key] = source[key];
+  }
+  return target;
+};
+pc.autoscale = function() {
+  // yscale
+  var defaultScales = {
+    "number": function(k) {
+      return d3.scale.linear()
+        .domain(d3.extent(__.data, function(d) { return +d[k]; }))
+        .range([h()+1, 1])
+    },
+    "string": function(k) {
+      return d3.scale.ordinal()
+        .domain(__.data.map(function(p) { return p[k]; }))
+        .rangePoints([h()+1, 1])
+    }
+  };
+
+  __.dimensions.forEach(function(k) {
+    yscale[k] = defaultScales[__.types[k]](k);
+  });
+
+  // hack to remove ordinal dimensions with many values
+  pc.dimensions(pc.dimensions().filter(function(p,i) {
+    var uniques = yscale[p].domain().length;
+    if (__.types[p] == "string" && (uniques > 60 || uniques < 2)) {
+      return false;
+    }
+    return true;
+  }));
+
+  // xscale
+  xscale.rangePoints([0, w()], 1);
+
+  // canvas sizes
+  pc.selection.selectAll("canvas")
+      .style("margin-top", __.margin.top + "px")
+      .style("margin-left", __.margin.left + "px")
+      .attr("width", w()+2)
+      .attr("height", h()+2)
+
+  // default styles, needs to be set when canvas width changes
+  ctx.foreground.strokeStyle = __.color;
+  ctx.foreground.lineWidth = 1.4;
+  ctx.foreground.globalCompositeOperation = __.composite;
+  ctx.foreground.globalAlpha = __.alpha;
+  ctx.highlight.lineWidth = 3;
+  ctx.shadows.strokeStyle = "#dadada";
+
+  return this;
+};
+pc.detectDimensions = function() {
+  pc.types(pc.detectDimensionTypes(__.data));
+  pc.dimensions(d3.keys(pc.types()));
+  return this;
+};
+
+// a better "typeof" from this post: http://stackoverflow.com/questions/7390426/better-way-to-get-type-of-a-javascript-variable
+pc.toType = function(v) {
+  return ({}).toString.call(v).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
+};
+
+// try to coerce to number before returning type
+pc.toTypeCoerceNumbers = function(v) {
+  if ((parseFloat(v) == v) && (v != null)) return "number";
+  return pc.toType(v);
+};
+
+// attempt to determine types of each dimension based on first row of data
+pc.detectDimensionTypes = function(data) {
+  var types = {}
+  d3.keys(data[0])
+    .forEach(function(col) {
+      types[col] = pc.toTypeCoerceNumbers(data[0][col]);
+    });
+  return types;
+};
+pc.render = function() {
+  // try to autodetect dimensions and create scales
+  if (!__.dimensions.length) pc.detectDimensions();
+  if (!(__.dimensions[0] in yscale)) pc.autoscale();
+
+  pc.render[__.mode]();
+
+  events.render.call(this);
+  return this;
+};
+
+pc.render.default = function() {
+  pc.clear('foreground');
+  if (__.brushed) {
+    __.brushed.forEach(path_foreground);
+  } else {
+    __.data.forEach(path_foreground);
+  }
+};
+
+var rqueue = d3.renderQueue(path_foreground)
+  .rate(50)
+  .clear(function() { pc.clear('foreground'); });
+
+pc.render.queue = function() {
+  if (__.brushed) {
+    rqueue(__.brushed);
+  } else {
+    rqueue(__.data);
+  }
+};
+pc.shadows = function() {
+  flags.shadows = true;
+  if (__.data.length > 0) paths(__.data, ctx.shadows);
+  return this;
+};
+
+// draw little dots on the axis line where data intersects
+pc.axisDots = function() {
+  var ctx = pc.ctx.marks;
+  ctx.globalAlpha = d3.min([1/Math.pow(data.length, 1/2), 1]);
+  __.data.forEach(function(d) {
+    __.dimensions.map(function(p,i) {
+      ctx.fillRect(position(p)-0.75,yscale[p](d[p])-0.75,1.5,1.5);
+    });
+  });
+  return this;
+};
+
+// draw single polyline
+function color_path(d, ctx) {
+  ctx.strokeStyle = d3.functor(__.color)(d);
+  ctx.beginPath();
+  __.dimensions.map(function(p,i) {
+    if (i == 0) {
+      ctx.moveTo(position(p),yscale[p](d[p]));
+    } else {
+      ctx.lineTo(position(p),yscale[p](d[p]));
+    }
+  });
+  ctx.stroke();
+};
+
+// draw many polylines of the same color
+function paths(data, ctx) {
+  ctx.clearRect(-1,-1,w()+2,h()+2);
+  ctx.beginPath();
+  data.forEach(function(d) {
+    __.dimensions.map(function(p,i) {
+      if (i == 0) {
+        ctx.moveTo(position(p),yscale[p](d[p]));
+      } else {
+        ctx.lineTo(position(p),yscale[p](d[p]));
+      }
+    });
+  });
+  ctx.stroke();
+};
+
+function path_foreground(d) {
+  return color_path(d, ctx.foreground);
+};
+
+function path_highlight(d) {
+  return color_path(d, ctx.highlight);
+};
+pc.clear = function(layer) {
+  ctx[layer].clearRect(0,0,w()+2,h()+2);
+  return this;
+};
+pc.createAxes = function() {
+  if (g) pc.removeAxes();
+
+  // Add a group element for each dimension.
+  g = pc.svg.selectAll(".dimension")
+      .data(__.dimensions, function(d) { return d; })
+    .enter().append("svg:g")
+      .attr("class", "dimension")
+      .attr("transform", function(d) { return "translate(" + xscale(d) + ")"; })
+
+  // Add an axis and title.
+  g.append("svg:g")
+      .attr("class", "axis")
+      .attr("transform", "translate(0,0)")
+      .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
+    .append("svg:text")
+      .attr({
+        "text-anchor": "middle",
+        "y": 0,
+        "transform": "translate(0,-12)",
+        "x": 0,
+        "class": "label"
+      })
+      .text(String)
+
+  flags.axes= true;
+  return this;
+};
+
+pc.removeAxes = function() {
+  g.remove();
+  return this;
+};
+
+pc.updateAxes = function() {
+  var g_data = pc.svg.selectAll(".dimension")
+      .data(__.dimensions, function(d) { return d; })
+
+  g_data.enter().append("svg:g")
+      .attr("class", "dimension")
+      .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
+      .style("opacity", 0)
+      .append("svg:g")
+      .attr("class", "axis")
+      .attr("transform", "translate(0,0)")
+      .each(function(d) { d3.select(this).call(axis.scale(yscale[d])); })
+    .append("svg:text")
+      .attr({
+        "text-anchor": "middle",
+        "y": 0,
+        "transform": "translate(0,-12)",
+        "x": 0,
+        "class": "label"
+      })
+      .text(String);
+
+  g_data.exit().remove();
+
+  g = pc.svg.selectAll(".dimension");
+
+  g.transition().duration(1100)
+    .attr("transform", function(p) { return "translate(" + position(p) + ")"; })
+    .style("opacity", 1)
+  if (flags.shadows) paths(__.data, ctx.shadows);
+  return this;
+};
+
+pc.brushable = function() {
+  if (!g) pc.createAxes();
+
+  // Add and store a brush for each axis.
+  g.append("svg:g")
+      .attr("class", "brush")
+      .each(function(d) {
+        d3.select(this).call(
+          yscale[d].brush = d3.svg.brush()
+            .y(yscale[d])
+            .on("brush", pc.brush)
+        );
+      })
+    .selectAll("rect")
+      .style("visibility", null)
+      .attr("x", -15)
+      .attr("width", 30)
+  flags.brushable = true;
+  return this;
+};
+
+// Jason Davies, http://bl.ocks.org/1341281
+pc.reorderable = function() {
+  if (!g) pc.createAxes();
+
+  g.style("cursor", "move")
+    .call(d3.behavior.drag()
+      .on("dragstart", function(d) {
+        dragging[d] = this.__origin__ = xscale(d);
+      })
+      .on("drag", function(d) {
+        dragging[d] = Math.min(w(), Math.max(0, this.__origin__ += d3.event.dx));
+        __.dimensions.sort(function(a, b) { return position(a) - position(b); });
+        xscale.domain(__.dimensions);
+        pc.render();
+        g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
+      })
+      .on("dragend", function(d) {
+        delete this.__origin__;
+        delete dragging[d];
+        d3.select(this).transition().attr("transform", "translate(" + xscale(d) + ")");
+        pc.render();
+      }));
+  flags.reorderable = true;
+  return this;
+};
+
+// pairs of adjacent dimensions
+pc.adjacent_pairs = function(arr) {
+  var ret = [];
+  for (var i = 0; i < arr.length-1; i++) {
+    ret.push([arr[i],arr[i+1]]);
+  };
+  return ret;
+};
+pc.interactive = function() {
+  flags.interactive = true;
+  return this;
+};
+
+// Get data within brushes
+pc.brush = function() {
+  __.brushed = selected();
+  events.brush.call(pc,__.brushed);
+  pc.render();
+};
+
+// expose a few objects
+pc.xscale = xscale;
+pc.yscale = yscale;
+pc.ctx = ctx;
+pc.canvas = canvas;
+pc.g = function() { return g; };
+
+// TODO
+pc.brushReset = function(dimension) {
+  yscale[dimension].brush.clear()(
+    pc.g()
+      .filter(function(p) {
+        return dimension == p;
+      })
+  )
+  return this;
+};
+
+// rescale for height, width and margins
+// TODO currently assumes chart is brushable, and destroys old brushes
+pc.resize = function() {
+  // selection size
+  pc.selection.select("svg")
+    .attr("width", __.width)
+    .attr("height", __.height)
+  pc.svg.attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");
+
+  // scales
+  pc.autoscale();
+
+  // axes, destroys old brushes. the current brush state should pass through in the future
+  if (g) pc.createAxes().brushable();
+
+  events.resize.call(this, {width: __.width, height: __.height, margin: __.margin});
+  return this;
+};
+
+// highlight an array of data
+pc.highlight = function(data) {
+  pc.clear("highlight");
+  d3.select(canvas.foreground).classed("faded", true);
+  data.forEach(path_highlight);
+  events.highlight.call(this,data);
+  return this;
+};
+
+// clear highlighting
+pc.unhighlight = function(data) {
+  pc.clear("highlight");
+  d3.select(canvas.foreground).classed("faded", false);
+  return this;
+};
+
+// calculate 2d intersection of line a->b with line c->d
+// points are objects with x and y properties
+pc.intersection =  function(a, b, c, d) {
+  return {
+    x: ((a.x * b.y - a.y * b.x) * (c.x - d.x) - (a.x - b.x) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x)),
+    y: ((a.x * b.y - a.y * b.x) * (c.y - d.y) - (a.y - b.y) * (c.x * d.y - c.y * d.x)) / ((a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x))
+  };
+};
+
+function is_brushed(p) {
+  return !yscale[p].brush.empty();
+};
+
+// data within extents
+function selected() {
+  var actives = __.dimensions.filter(is_brushed),
+      extents = actives.map(function(p) { return yscale[p].brush.extent(); });
+
+  // test if within range
+  var within = {
+    "number": function(d,p,dimension) {
+      return extents[dimension][0] <= d[p] && d[p] <= extents[dimension][1]
+    },
+    "string": function(d,p,dimension) {
+      return extents[dimension][0] <= yscale[p](d[p]) && yscale[p](d[p]) <= extents[dimension][1]
+    }
+  };
+
+  return __.data
+    .filter(function(d) {
+      return actives.every(function(p, dimension) {
+        return within[__.types[p]](d,p,dimension);
+      });
+    });
+};
+
+function position(d) {
+  var v = dragging[d];
+  return v == null ? xscale(d) : v;
+}
+  pc.toString = function() { return "Parallel Coordinates: " + __.dimensions.length + " dimensions (" + d3.keys(__.data[0]).length + " total) , " + __.data.length + " rows"; };
+  
+  pc.version = "0.1.7";
+
+  return pc;
+};
+
+d3.renderQueue = (function(func) {
+  var _queue = [],                  // data to be rendered
+      _rate = 10,                   // number of calls per frame
+      _clear = function() {},       // clearing function
+      _i = 0;                       // current iteration
+
+  var rq = function(data) {
+    if (data) rq.data(data);
+    rq.invalidate();
+    _clear();
+    rq.render();
+  };
+
+  rq.render = function() {
+    _i = 0;
+    var valid = true;
+    rq.invalidate = function() { valid = false; };
+
+    function doFrame() {
+      if (!valid) return false;
+      if (_i > _queue.length) return false;
+      var chunk = _queue.slice(_i,_i+_rate);
+      _i += _rate;
+      chunk.map(func);
+      d3.timer(doFrame);
+    }
+
+    doFrame();
+  };
+
+  rq.data = function(data) {
+    rq.invalidate();
+    _queue = data.slice(0);
+    return rq;
+  };
+
+  rq.rate = function(value) {
+    if (!arguments.length) return _rate;
+    _rate = value;
+    return rq;
+  };
+
+  rq.remaining = function() {
+    return _queue.length - _i;
+  };
+
+  // clear the canvas
+  rq.clear = function(func) {
+    if (!arguments.length) {
+      _clear();
+      return rq;
+    }
+    _clear = func;
+    return rq;
+  };
+
+  rq.invalidate = function() {};
+
+  return rq;
+});
diff --git a/emperor/support_files/js/d3.v3.min.js b/emperor/support_files/js/d3.v3.min.js
new file mode 100644
index 0000000..3ee7054
--- /dev/null
+++ b/emperor/support_files/js/d3.v3.min.js
@@ -0,0 +1,5 @@
+d3=function(){function n(n){return null!=n&&!isNaN(n)}function t(n){return n.length}function e(n){for(var t=1;n*t%1;)t*=10;return t}function r(n,t){try{for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}catch(r){n.prototype=t}}function i(){}function u(){}function a(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function o(){}function c(n){function t(){for(var t,r=e,i=-1,u=r.length;++i<u;)(t=r[i].on)&&t.apply(this,arguments);return n}var  [...]
+}function We(n){for(var t=0,e=n.length-1,r=[],i=n[0],u=n[1],a=r[0]=Ke(i,u);++t<e;)r[t]=(a+(a=Ke(i=u,u=n[t+1])))/2;return r[t]=a,r}function Qe(n){for(var t,e,r,i,u=[],a=We(n),o=-1,c=n.length-1;++o<c;)t=Ke(n[o],n[o+1]),Math.abs(t)<1e-6?a[o]=a[o+1]=0:(e=a[o]/t,r=a[o+1]/t,i=e*e+r*r,i>9&&(i=3*t/Math.sqrt(i),a[o]=i*e,a[o+1]=i*r));for(o=-1;++o<=c;)i=(n[Math.min(c,o+1)][0]-n[Math.max(0,o-1)][0])/(6*(1+a[o]*a[o])),u.push([i||0,a[o]*i||0]);return u}function nr(n){return n.length<3?Fe(n):n[0]+Ie(n, [...]
+},add:function(n){return this[ha+n]=!0,n},remove:function(n){return n=ha+n,n in this&&delete this[n]},values:function(){var n=[];return this.forEach(function(t){n.push(t)}),n},forEach:function(n){for(var t in this)t.charCodeAt(0)===ga&&n.call(this,t.substring(1))}}),ua.behavior={},ua.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r<i;)n[e=arguments[r]]=a(n,t,t[e]);return n},ua.dispatch=function(){for(var n=new o,t=-1,e=arguments.length;++t<e;)n[arguments[t]]=c(n);return n},o.pro [...]
+}),a=ua.range(n.length).sort(function(n,t){return u[n]-u[t]});return a.filter(function(n,t){return!t||u[n]-u[a[t-1]]>ja}).map(function(t){return n[t]})}),o.forEach(function(n,e){var r=n.length;if(!r)return n.push([-s,-s],[-s,s],[s,s],[s,-s]);if(!(r>2)){var i=t[e],u=n[0],a=n[1],o=i[0],c=i[1],l=u[0],f=u[1],h=a[0],g=a[1],p=Math.abs(h-l),d=g-f;if(Math.abs(d)<ja){var m=f>c?-s:s;n.push([-s,m],[s,m])}else if(ja>p){var v=l>o?-s:s;n.push([v,-s],[v,s])}else{var m=(l-o)*(g-f)>(h-l)*(f-c)?s:-s,y=Mat [...]
+return n?ua.touches(y,n)[0]:ua.mouse(y)}function f(){ua.event.keyCode==32&&(E||(m=null,k[0]-=s[1][0],k[1]-=s[1][1],E=2),l())}function h(){ua.event.keyCode==32&&2==E&&(k[0]+=s[1][0],k[1]+=s[1][1],E=0,l())}function g(){var n=i(),u=!1;v&&(n[0]+=v[0],n[1]+=v[1]),E||(ua.event.altKey?(m||(m=[(s[0][0]+s[1][0])/2,(s[0][1]+s[1][1])/2]),k[0]=s[+(n[0]<m[0])][0],k[1]=s[+(n[1]<m[1])][1]):m=null),w&&p(n,o,0)&&(e(b),u=!0),S&&p(n,c,1)&&(r(b),u=!0),u&&(t(b),x({type:"brush",mode:E?"move":"resize"}))}funct [...]
\ No newline at end of file
diff --git a/emperor/support_files/js/jquery-1.7.1.min.js b/emperor/support_files/js/jquery-1.7.1.min.js
new file mode 100644
index 0000000..198b3ff
--- /dev/null
+++ b/emperor/support_files/js/jquery-1.7.1.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.1 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement( [...]
+f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]| [...]
+{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replac [...]
\ No newline at end of file
diff --git a/emperor/support_files/js/jquery-ui-1.8.17.custom.min.js b/emperor/support_files/js/jquery-ui-1.8.17.custom.min.js
new file mode 100755
index 0000000..991cb8d
--- /dev/null
+++ b/emperor/support_files/js/jquery-ui-1.8.17.custom.min.js
@@ -0,0 +1,356 @@
+/*!
+ * jQuery UI 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI
+ */(function(a,b){function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;if(!b.href||!g||f.nodeName.toLowerCase()!=="map")return!1;h=a("img[usemap=#"+g+"]")[0];return!!h&&d(h)}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}a.ui=a.ui||{};a.ui.version||(a.extend(a. [...]
+ * jQuery UI Widget 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Widget
+ */(function(a,b){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var d=0,e;(e=b[d])!=null;d++)try{a(e).triggerHandler("remove")}catch(f){}c(b)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){try{a(this).triggerHandler("remove")}catch(b){}});return d.call(a(this),b,c)})}}a.widget=function(b,c,d){var e=b.split(".")[0],f;b=b.split(".")[1],f=e+"-"+b,d||(d=c,c=a.Widget),a.e [...]
+ * jQuery UI Mouse 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Mouse
+ *
+ * Depends:
+ *	jquery.ui.widget.js
+ */(function(a,b){var c=!1;a(document).mouseup(function(a){c=!1}),a.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var b=this;this.element.bind("mousedown."+this.widgetName,function(a){return b._mouseDown(a)}).bind("click."+this.widgetName,function(c){if(!0===a.data(c.target,b.widgetName+".preventClickEvent")){a.removeData(c.target,b.widgetName+".preventClickEvent"),c.stopImmediatePropagation();return!1}}),this.started=!1},_mouseDestroy:funct [...]
+ * jQuery UI Position 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Position
+ */(function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0 [...]
+ * jQuery UI Draggable 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Draggables
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.mouse.js
+ *	jquery.ui.widget.js
+ */(function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test [...]
+ * jQuery UI Droppable 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Droppables
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.widget.js
+ *	jquery.ui.mouse.js
+ *	jquery.ui.draggable.js
+ */(function(a,b){a.widget("ui.droppable",{widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect"},_create:function(){var b=this.options,c=b.accept;this.isover=0,this.isout=1,this.accept=a.isFunction(c)?c:function(a){return a.is(c)},this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight},a.ui.ddmanager.droppables[b.scope]=a.ui.ddmanager.droppables[b.scope]||[],a.ui.ddmanager. [...]
+ * jQuery UI Resizable 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizables
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.mouse.js
+ *	jquery.ui.widget.js
+ */(function(a,b){a.widget("ui.resizable",a.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1e3},_create:function(){var b=this,c=this.options;this.element.addClass("ui-resizable"),a.extend(this,{_aspectRatio:!!c.aspectRatio,aspectRatio:c.aspectRatio,originalElement:this.elemen [...]
+ * jQuery UI Selectable 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Selectables
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.mouse.js
+ *	jquery.ui.widget.js
+ */(function(a,b){a.widget("ui.selectable",a.ui.mouse,{options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var b=this;this.element.addClass("ui-selectable"),this.dragged=!1;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]),c.addClass("ui-selectee"),c.each(function(){var b=a(this),c=b.offset();a.data(this,"selectable-item",{element:this,$element:b,left:c.left,top:c.top,right:c.left+b.outerWidth(),bottom:c.top+b.outerHeight(),s [...]
+ * jQuery UI Sortable 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Sortables
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.mouse.js
+ *	jquery.ui.widget.js
+ */(function(a,b){a.widget("ui.sortable",a.ui.mouse,{widgetEventPrefix:"sort",options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var a=this.options;this.containerCache={},this.element.addClass("u [...]
+ * jQuery UI Accordion 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Accordion
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.widget.js
+ */(function(a,b){a.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:!0,clearStyle:!1,collapsible:!1,event:"click",fillSpace:!1,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var b=this,c=b.options;b.running=0,b.element.addClass("ui-accordion ui-widget ui-helper-reset").chi [...]
+ * jQuery UI Autocomplete 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.widget.js
+ *	jquery.ui.position.js
+ */(function(a,b){var c=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var b=this,c=this.element[0].ownerDocument,d;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!b.options.disabled&&!b.element.propAttr(" [...]
+ * jQuery UI Button 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Button
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.widget.js
+ */(function(a,b){var c,d,e,f,g="ui-button ui-widget ui-state-default ui-corner-all",h="ui-state-hover ui-state-active ",i="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",j=function(){var b=a(this).find(":ui-button");setTimeout(function(){b.button("refresh")},1)},k=function(b){var c=b.name,d=b.form,e=a([]);c&&(d?e=a(d).find("[name='"+c+"']"):e=a("[name='"+c+"']",b.ownerDocument).filter(function( [...]
+ * jQuery UI Dialog 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.widget.js
+ *  jquery.ui.button.js
+ *	jquery.ui.draggable.js
+ *	jquery.ui.mouse.js
+ *	jquery.ui.position.js
+ *	jquery.ui.resizable.js
+ */(function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},f=a.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWi [...]
+ * jQuery UI Slider 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Slider
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.mouse.js
+ *	jquery.ui.widget.js
+ */(function(a,b){var c=5;a.widget("ui.slider",a.ui.mouse,{widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var b=this,d=this.options,e=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),f="<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",g=d.values&&d.values.length||1,h=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff [...]
+ * jQuery UI Tabs 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ *	jquery.ui.widget.js
+ */(function(a,b){function f(){return++d}function e(){return++c}var c=0,d=0;a.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:!1,cookie:null,collapsible:!1,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"<div></div>",remove:null,select:null,show:null,spinner:"<em>Loading…</em>",tabTemplate:"<li><a href='#{href}'><span>#{label}</span></a></li>"},_create:function(){this._tabify(!0)},_setOption:function(a,b){if(a=="sel [...]
+ * jQuery UI Datepicker 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker
+ *
+ * Depends:
+ *	jquery.ui.core.js
+ */(function($,undefined){function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);!c.length||c.removeClass("ui-state-hover ui-d [...]
+._get(a,"showMonthAfterYear"),l='<div class="ui-datepicker-title">',m="";if(f||!i)m+='<span class="ui-datepicker-month">'+g[b]+"</span>";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'M');\" "+">";for(var p=0;p<12;p++)(!n||p>=d.getMonth())&&(!o||p<=e.getMonth())&&(m+='<option value="'+p+'"'+(p==b?' selected="selected"':"")+">"+h[p]+"</option>");m+="</select>"}k [...]
+ * jQuery UI Progressbar 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Progressbar
+ *
+ * Depends:
+ *   jquery.ui.core.js
+ *   jquery.ui.widget.js
+ */(function(a,b){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=a("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},destroy:function(){this.element.re [...]
+ * jQuery UI Effects 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/
+ */jQuery.effects||function(a,b){function l(b){if(!b||typeof b=="number"||a.fx.speeds[b])return!0;if(typeof b=="string"&&!a.effects[b])return!0;return!1}function k(b,c,d,e){typeof b=="object"&&(e=c,d=null,c=b,b=c.effect),a.isFunction(c)&&(e=c,d=null,c={});if(typeof c=="number"||a.fx.speeds[c])e=d,d=c,c={};a.isFunction(d)&&(e=d,d=null),c=c||{},d=d||c.duration,d=a.fx.off?0:typeof d=="number"?d:d in a.fx.speeds?a.fx.speeds[d]:a.fx.speeds._default,e=e||c.complete;return[b,c,d,e]}function j(a [...]
+ * jQuery UI Effects Blind 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Blind
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.blind=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"vertical";a.effects.save(c,d),c.show();var g=a.effects.createWrapper(c).css({overflow:"hidden"}),h=f=="vertical"?"height":"width",i=f=="vertical"?g.height():g.width();e=="show"&&g.css(h,0);var j={};j[h]=e=="show"?i:0,g.animate(j,b.duration,b.options.easing,function(){e=="hide"&&c.hide(), [...]
+ * jQuery UI Effects Bounce 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Bounce
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.bounce=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"effect"),f=b.options.direction||"up",g=b.options.distance||20,h=b.options.times||5,i=b.duration||250;/show|hide/.test(e)&&d.push("opacity"),a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var j=f=="up"||f=="down"?"top":"left",k=f=="up"||f=="left"?"pos":"neg",g=b.options.distance||(j=="top"?c.outerHeight({marg [...]
+ * jQuery UI Effects Clip 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Clip
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.clip=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","height","width"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"vertical";a.effects.save(c,d),c.show();var g=a.effects.createWrapper(c).css({overflow:"hidden"}),h=c[0].tagName=="IMG"?g:c,i={size:f=="vertical"?"height":"width",position:f=="vertical"?"top":"left"},j=f=="vertical"?h.height():h.width();e=="show"&&(h.css(i.size,0),h.css(i. [...]
+ * jQuery UI Effects Drop 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Drop
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.drop=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","opacity"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"left";a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var g=f=="up"||f=="down"?"top":"left",h=f=="up"||f=="left"?"pos":"neg",i=b.options.distance||(g=="top"?c.outerHeight({margin:!0})/2:c.outerWidth({margin:!0})/2);e=="show"&&c.css("opacity",0).css(g,h=="pos"?-i:i);var j [...]
+ * jQuery UI Effects Explode 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Explode
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.explode=function(b){return this.queue(function(){var c=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3,d=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3;b.options.mode=b.options.mode=="toggle"?a(this).is(":visible")?"hide":"show":b.options.mode;var e=a(this).show().css("visibility","hidden"),f=e.offset();f.top-=parseInt(e.css("marginTop"),10)||0,f.left-=parseInt(e.css("marginLeft"),10)||0;var g=e.outerWidth(!0),h=e.outerHeight(!0);for(var [...]
+ * jQuery UI Effects Fade 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Fade
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.fade=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide");c.animate({opacity:d},{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}})(jQuery);/*
+ * jQuery UI Effects Fold 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Fold
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.fold=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.size||15,g=!!b.options.horizFirst,h=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(c,d),c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"}),j=e=="show"!=g,k=j?["width","height"]:["height","width"],l=j?[i.width(),i.height()]:[i.height(),i.width()],m=/([0-9]+)%/.exec(f);m& [...]
+ * jQuery UI Effects Highlight 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Highlight
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.highlight=function(b){return this.queue(function(){var c=a(this),d=["backgroundImage","backgroundColor","opacity"],e=a.effects.setMode(c,b.options.mode||"show"),f={backgroundColor:c.css("backgroundColor")};e=="hide"&&(f.opacity=0),a.effects.save(c,d),c.show().css({backgroundImage:"none",backgroundColor:b.options.color||"#ffff99"}).animate(f,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),e==" [...]
+ * jQuery UI Effects Pulsate 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Pulsate
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.pulsate=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"show");times=(b.options.times||5)*2-1,duration=b.duration?b.duration/2:a.fx.speeds._default/2,isVisible=c.is(":visible"),animateTo=0,isVisible||(c.css("opacity",0).show(),animateTo=1),(d=="hide"&&isVisible||d=="show"&&!isVisible)&&times--;for(var e=0;e<times;e++)c.animate({opacity:animateTo},duration,b.options.easing),animateTo=(animateTo+1)%2;c.animate({opaci [...]
+ * jQuery UI Effects Scale 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Scale
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.puff=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide"),e=parseInt(b.options.percent,10)||150,f=e/100,g={height:c.height(),width:c.width()};a.extend(b.options,{fade:!0,mode:d,percent:d=="hide"?e:100,from:d=="hide"?g:{height:g.height*f,width:g.width*f}}),c.effect("scale",b.options,b.duration,b.callback),c.dequeue()})},a.effects.scale=function(b){return this.queue(function(){var c=a(this),d=a.extend(!0,{},b.optio [...]
+ * jQuery UI Effects Shake 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Shake
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.shake=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"effect"),f=b.options.direction||"left",g=b.options.distance||20,h=b.options.times||3,i=b.duration||b.options.duration||140;a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var j=f=="up"||f=="down"?"top":"left",k=f=="up"||f=="left"?"pos":"neg",l={},m={},n={};l[j]=(k=="pos"?"-=":"+=")+g,m[j]=(k=="pos"?"+=":"-=")+ [...]
+ * jQuery UI Effects Slide 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Slide
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.slide=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"show"),f=b.options.direction||"left";a.effects.save(c,d),c.show(),a.effects.createWrapper(c).css({overflow:"hidden"});var g=f=="up"||f=="down"?"top":"left",h=f=="up"||f=="left"?"pos":"neg",i=b.options.distance||(g=="top"?c.outerHeight({margin:!0}):c.outerWidth({margin:!0}));e=="show"&&c.css(g,h=="pos"?isNaN(i)?"-"+i:- [...]
+ * jQuery UI Effects Transfer 1.8.17
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Effects/Transfer
+ *
+ * Depends:
+ *	jquery.effects.core.js
+ */(function(a,b){a.effects.transfer=function(b){return this.queue(function(){var c=a(this),d=a(b.options.to),e=d.offset(),f={top:e.top,left:e.left,height:d.innerHeight(),width:d.innerWidth()},g=c.offset(),h=a('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(b.options.className).css({top:g.top,left:g.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(f,b.duration,b.options.easing,function(){h.remove(),b.callback&&b.callback.apply(c[0 [...]
\ No newline at end of file
diff --git a/emperor/support_files/js/jquery.colorPicker.js b/emperor/support_files/js/jquery.colorPicker.js
new file mode 100755
index 0000000..02ba9b0
--- /dev/null
+++ b/emperor/support_files/js/jquery.colorPicker.js
@@ -0,0 +1,328 @@
+/**
+ * Really Simple Color Picker in jQuery
+ *
+ * Licensed under the MIT (MIT-LICENSE.txt) licenses.
+ *
+ * Copyright (c) 2008-2012
+ * Lakshan Perera (www.laktek.com) & Daniel Lacy (daniellacy.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+(function ($) {
+    /**
+     * Create a couple private variables.
+    **/
+    var selectorOwner,
+        activePalette,
+        cItterate       = 0,
+        templates       = {
+            control : $('<div class="colorPicker-picker"> </div>'),
+            palette : $('<div id="colorPicker_palette" class="colorPicker-palette" />'),
+            swatch  : $('<div class="colorPicker-swatch"> </div>'),
+            hexLabel: $('<label for="colorPicker_hex">Hex</label>'),
+            hexField: $('<input type="text" id="colorPicker_hex" />')
+        },
+        transparent     = "transparent",
+        lastColor;
+
+    /**
+     * Create our colorPicker function
+    **/
+    $.fn.colorPicker = function (options) {
+
+        return this.each(function () {
+            // Setup time. Clone new elements from our templates, set some IDs, make shortcuts, jazzercise.
+            var element      = $(this),
+                opts         = $.extend({}, $.fn.colorPicker.defaults, options),
+                defaultColor = $.fn.colorPicker.toHex(
+                        (element.val().length > 0) ? element.val() : opts.pickerDefault
+                    ),
+                newControl   = templates.control.clone(),
+                newPalette   = templates.palette.clone().attr('id', 'colorPicker_palette-' + cItterate),
+                newHexLabel  = templates.hexLabel.clone(),
+                newHexField  = templates.hexField.clone(),
+                paletteId    = newPalette[0].id,
+                swatch;
+
+
+            /**
+             * Build a color palette.
+            **/
+            $.each(opts.colors, function (i) {
+                swatch = templates.swatch.clone();
+
+                if (opts.colors[i] === transparent) {
+                    swatch.addClass(transparent).text('X');
+                    $.fn.colorPicker.bindPalette(newHexField, swatch, transparent);
+                } else {
+                    swatch.css("background-color", "#" + this);
+                    $.fn.colorPicker.bindPalette(newHexField, swatch);
+                }
+                swatch.appendTo(newPalette);
+            });
+
+            newHexLabel.attr('for', 'colorPicker_hex-' + cItterate);
+
+            newHexField.attr({
+                'id'    : 'colorPicker_hex-' + cItterate,
+                'value' : defaultColor
+            });
+
+            newHexField.bind("keydown", function (event) {
+                if (event.keyCode === 13) {
+                    var hexColor = $.fn.colorPicker.toHex($(this).val());
+                    $.fn.colorPicker.changeColor(hexColor ? hexColor : element.val());
+                }
+                if (event.keyCode === 27) {
+                    $.fn.colorPicker.hidePalette();
+                }
+            });
+
+            newHexField.bind("keyup", function (event) {
+              var hexColor = $.fn.colorPicker.toHex($(event.target).val());
+              $.fn.colorPicker.previewColor(hexColor ? hexColor : element.val());
+            });
+
+            $('<div class="colorPicker_hexWrap" />').append(newHexLabel).appendTo(newPalette);
+
+            newPalette.find('.colorPicker_hexWrap').append(newHexField);
+
+            $("body").append(newPalette);
+
+            newPalette.hide();
+
+
+            /**
+             * Build replacement interface for original color input.
+            **/
+            newControl.css("background-color", defaultColor);
+
+            newControl.bind("click", function () {
+                $.fn.colorPicker.togglePalette($('#' + paletteId), $(this));
+            });
+
+            if( options && options.onColorChange ) {
+              newControl.data('onColorChange', options.onColorChange);
+            } else {
+              newControl.data('onColorChange', function() {} );
+            }
+            element.after(newControl);
+
+            element.bind("change", function () {
+                element.next(".colorPicker-picker").css(
+                    "background-color", $.fn.colorPicker.toHex($(this).val())
+                );
+            });
+
+            // Hide the original input.
+            element.val(defaultColor).hide();
+
+            cItterate++;
+        });
+    };
+
+    /**
+     * Extend colorPicker with... all our functionality.
+    **/
+    $.extend(true, $.fn.colorPicker, {
+        /**
+         * Return a Hex color, convert an RGB value and return Hex, or return false.
+         *
+         * Inspired by http://code.google.com/p/jquery-color-utils
+        **/
+        toHex : function (color) {
+            // If we have a standard or shorthand Hex color, return that value.
+            if (color.match(/[0-9A-F]{6}|[0-9A-F]{3}$/i)) {
+                return (color.charAt(0) === "#") ? color : ("#" + color);
+
+            // Alternatively, check for RGB color, then convert and return it as Hex.
+            } else if (color.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/)) {
+                var c = ([parseInt(RegExp.$1, 10), parseInt(RegExp.$2, 10), parseInt(RegExp.$3, 10)]),
+                    pad = function (str) {
+                        if (str.length < 2) {
+                            for (var i = 0, len = 2 - str.length; i < len; i++) {
+                                str = '0' + str;
+                            }
+                        }
+
+                        return str;
+                    };
+
+                if (c.length === 3) {
+                    var r = pad(c[0].toString(16)),
+                        g = pad(c[1].toString(16)),
+                        b = pad(c[2].toString(16));
+
+                    return '#' + r + g + b;
+                }
+
+            // Otherwise we wont do anything.
+            } else {
+                return false;
+
+            }
+        },
+
+        /**
+         * Check whether user clicked on the selector or owner.
+        **/
+        checkMouse : function (event, paletteId) {
+            var selector = activePalette,
+                selectorParent = $(event.target).parents("#" + selector.attr('id')).length;
+
+            if (event.target === $(selector)[0] || event.target === selectorOwner[0] || selectorParent > 0) {
+                return;
+            }
+
+            $.fn.colorPicker.hidePalette();
+        },
+
+        /**
+         * Hide the color palette modal.
+        **/
+        hidePalette : function () {
+            $(document).unbind("mousedown", $.fn.colorPicker.checkMouse);
+
+            $('.colorPicker-palette').hide();
+        },
+
+        /**
+         * Show the color palette modal.
+        **/
+        showPalette : function (palette) {
+            var hexColor = selectorOwner.prev("input").val();
+
+            palette.css({
+                top: selectorOwner.offset().top + (selectorOwner.outerHeight()),
+                left: selectorOwner.offset().left
+            });
+
+            $("#color_value").val(hexColor);
+
+            palette.show();
+
+            $(document).bind("mousedown", $.fn.colorPicker.checkMouse);
+        },
+
+        /**
+         * Toggle visibility of the colorPicker palette.
+        **/
+        togglePalette : function (palette, origin) {
+            // selectorOwner is the clicked .colorPicker-picker.
+            if (origin) {
+                selectorOwner = origin;
+            }
+
+            activePalette = palette;
+
+            if (activePalette.is(':visible')) {
+                $.fn.colorPicker.hidePalette();
+
+            } else {
+                $.fn.colorPicker.showPalette(palette);
+
+            }
+        },
+
+        /**
+         * Update the input with a newly selected color.
+        **/
+        changeColor : function (value) {
+            selectorOwner.css("background-color", value);
+            selectorOwner.prev("input").val(value).change();
+
+            $.fn.colorPicker.hidePalette();
+
+            selectorOwner.data('onColorChange').call(selectorOwner, $(selectorOwner).prev("input").attr("id"), value);
+        },
+
+
+        /**
+         * Preview the input with a newly selected color.
+        **/
+        previewColor : function (value) {
+            selectorOwner.css("background-color", value);
+        },
+
+        /**
+         * Bind events to the color palette swatches.
+        */
+        bindPalette : function (paletteInput, element, color) {
+            color = color ? color : $.fn.colorPicker.toHex(element.css("background-color"));
+
+            element.bind({
+                click : function (ev) {
+                    lastColor = color;
+
+                    $.fn.colorPicker.changeColor(color);
+                },
+                mouseover : function (ev) {
+                    lastColor = paletteInput.val();
+
+                    $(this).css("border-color", "#598FEF");
+
+                    paletteInput.val(color);
+
+                    $.fn.colorPicker.previewColor(color);
+                },
+                mouseout : function (ev) {
+                    $(this).css("border-color", "#000");
+
+                    paletteInput.val(selectorOwner.css("background-color"));
+
+                    paletteInput.val(lastColor);
+
+                    $.fn.colorPicker.previewColor(lastColor);
+                }
+            });
+        }
+    });
+
+    /**
+     * Default colorPicker options.
+     *
+     * These are publibly available for global modification using a setting such as:
+     *
+     * $.fn.colorPicker.defaults.colors = ['151337', '111111']
+     *
+     * They can also be applied on a per-bound element basis like so:
+     *
+     * $('#element1').colorPicker({pickerDefault: 'efefef', transparency: true});
+     * $('#element2').colorPicker({pickerDefault: '333333', colors: ['333333', '111111']});
+     *
+    **/
+    $.fn.colorPicker.defaults = {
+        // colorPicker default selected color.
+        pickerDefault : "FFFFFF",
+
+        // Default color set.
+        colors : [
+            '000000', '993300', '333300', '000080', '333399', '333333', '800000', 'FF6600',
+            '808000', '008000', '008080', '0000FF', '666699', '808080', 'FF0000', 'FF9900',
+            '99CC00', '339966', '33CCCC', '3366FF', '800080', '999999', 'FF00FF', 'FFCC00',
+            'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0', 'FF99CC', 'FFCC99',
+            'FFFF99', 'CCFFFF', '99CCFF', 'FFFFFF'
+        ],
+
+        // If we want to simply add more colors to the default set, use addColors.
+        addColors : []
+    };
+
+})(jQuery);
diff --git a/emperor/support_files/js/js/AudioObject.js b/emperor/support_files/js/js/AudioObject.js
new file mode 100644
index 0000000..f631329
--- /dev/null
+++ b/emperor/support_files/js/js/AudioObject.js
@@ -0,0 +1,172 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * AudioObject
+ *
+ *	- 3d spatialized sound with Doppler-shift effect
+ *
+ *	- uses Audio API (currently supported in WebKit-based browsers)
+ *		https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
+ *
+ *	- based on Doppler effect demo from Chromium
+ * 		http://chromium.googlecode.com/svn/trunk/samples/audio/doppler.html
+ *
+ * - parameters
+ *
+ *		- listener
+ *			dopplerFactor	// A constant used to determine the amount of pitch shift to use when rendering a doppler effect.
+ *			speedOfSound	// The speed of sound used for calculating doppler shift. The default value is 343.3 meters / second.
+ *
+ *		- panner
+ *			refDistance		// A reference distance for reducing volume as source move further from the listener.
+ *			maxDistance		// The maximum distance between source and listener, after which the volume will not be reduced any further.
+ *			rolloffFactor	// Describes how quickly the volume is reduced as source moves away from listener.
+ * 			coneInnerAngle	// An angle inside of which there will be no volume reduction.
+ *			coneOuterAngle 	// An angle outside of which the volume will be reduced to a constant value of coneOuterGain.
+ *			coneOuterGain	// Amount of volume reduction outside of the coneOuterAngle.
+ */
+
+THREE.AudioObject = function ( url, volume, playbackRate, loop ) {
+
+	THREE.Object3D.call( this );
+
+	if ( playbackRate === undefined ) playbackRate = 1;
+	if ( volume === undefined ) volume = 1;
+	if ( loop === undefined ) loop = true;
+
+	if ( ! this.context ) {
+
+		try {
+
+			this.context = new webkitAudioContext();
+
+		} catch( error ) {
+
+			console.warn( "THREE.AudioObject: webkitAudioContext not found" );
+			return this;
+
+		}
+
+	}
+
+	this.directionalSource = false;
+
+	this.listener = this.context.listener;
+	this.panner = this.context.createPanner();
+	this.source = this.context.createBufferSource();
+
+	this.masterGainNode = this.context.createGainNode();
+	this.dryGainNode = this.context.createGainNode();
+
+	// Setup initial gains
+
+	this.masterGainNode.gain.value = volume;
+	this.dryGainNode.gain.value = 3.0;
+
+	// Connect dry mix
+
+	this.source.connect( this.panner );
+	this.panner.connect( this.dryGainNode );
+	this.dryGainNode.connect( this.masterGainNode );
+
+	// Connect master gain
+
+	this.masterGainNode.connect( this.context.destination );
+
+	// Set source parameters and load sound
+
+	this.source.playbackRate.value = playbackRate;
+	this.source.loop = loop;
+
+	loadBufferAndPlay( url );
+
+	// private properties
+
+	var soundPosition = new THREE.Vector3(),
+	cameraPosition = new THREE.Vector3(),
+	oldSoundPosition = new THREE.Vector3(),
+	oldCameraPosition = new THREE.Vector3(),
+
+	soundDelta = new THREE.Vector3(),
+	cameraDelta = new THREE.Vector3(),
+
+	soundFront = new THREE.Vector3(),
+	cameraFront = new THREE.Vector3(),
+	soundUp = new THREE.Vector3(),
+	cameraUp = new THREE.Vector3();
+
+	var _this = this;
+
+	// API
+
+	this.setVolume = function ( volume ) {
+
+		this.masterGainNode.gain.value = volume;
+
+	};
+
+	this.update = function ( camera ) {
+
+		oldSoundPosition.copy( soundPosition );
+		oldCameraPosition.copy( cameraPosition );
+
+		soundPosition.copy( this.matrixWorld.getPosition() );
+		cameraPosition.copy( camera.matrixWorld.getPosition() );
+
+		soundDelta.sub( soundPosition, oldSoundPosition );
+		cameraDelta.sub( cameraPosition, oldCameraPosition );
+
+		cameraUp.copy( camera.up );
+
+		cameraFront.set( 0, 0, -1 );
+		camera.matrixWorld.rotateAxis( cameraFront );
+		cameraFront.normalize();
+
+		this.listener.setPosition( cameraPosition.x, cameraPosition.y, cameraPosition.z );
+		this.listener.setVelocity( cameraDelta.x, cameraDelta.y, cameraDelta.z );
+		this.listener.setOrientation( cameraFront.x, cameraFront.y, cameraFront.z, cameraUp.x, cameraUp.y, cameraUp.z );
+
+		this.panner.setPosition( soundPosition.x, soundPosition.y, soundPosition.z );
+		this.panner.setVelocity( soundDelta.x, soundDelta.y, soundDelta.z );
+
+		if ( this.directionalSource ) {
+
+			soundFront.set( 0, 0, -1 );
+			this.matrixWorld.rotateAxis( soundFront );
+			soundFront.normalize();
+
+			soundUp.copy( this.up );
+			this.panner.setOrientation( soundFront.x, soundFront.y, soundFront.z, soundUp.x, soundUp.y, soundUp.z );
+
+		}
+
+
+	};
+
+	function loadBufferAndPlay( url ) {
+
+		// Load asynchronously
+
+		var request = new XMLHttpRequest();
+		request.open( "GET", url, true );
+		request.responseType = "arraybuffer";
+
+		request.onload = function() {
+
+			_this.source.buffer = _this.context.createBuffer( request.response, true );
+			_this.source.noteOn( 0 );
+
+		}
+
+		request.send();
+
+	}
+
+};
+
+THREE.AudioObject.prototype = new THREE.Object3D();
+THREE.AudioObject.prototype.constructor = THREE.AudioObject;
+
+THREE.AudioObject.prototype.context = null;
+THREE.AudioObject.prototype.type = null;
+
diff --git a/emperor/support_files/js/js/Car.js b/emperor/support_files/js/js/Car.js
new file mode 100644
index 0000000..1853210
--- /dev/null
+++ b/emperor/support_files/js/js/Car.js
@@ -0,0 +1,364 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Car = function () {
+
+	var scope = this;
+
+	// car geometry manual parameters
+
+	this.modelScale = 1;
+
+	this.backWheelOffset = 2;
+
+	this.autoWheelGeometry = true;
+
+	// car geometry parameters automatically set from wheel mesh
+	// 	- assumes wheel mesh is front left wheel in proper global
+	//    position with respect to body mesh
+	//	- other wheels are mirrored against car root
+	//	- if necessary back wheels can be offset manually
+
+	this.wheelOffset = new THREE.Vector3();
+
+	this.wheelDiameter = 1;
+
+	// car "feel" parameters
+
+	this.MAX_SPEED = 2200;
+	this.MAX_REVERSE_SPEED = -1500;
+
+	this.MAX_WHEEL_ROTATION = 0.6;
+
+	this.FRONT_ACCELERATION = 1250;
+	this.BACK_ACCELERATION = 1500;
+
+	this.WHEEL_ANGULAR_ACCELERATION = 1.5;
+
+	this.FRONT_DECCELERATION = 750;
+	this.WHEEL_ANGULAR_DECCELERATION = 1.0;
+
+	this.STEERING_RADIUS_RATIO = 0.0023;
+
+	this.MAX_TILT_SIDES = 0.05;
+	this.MAX_TILT_FRONTBACK = 0.015;
+
+	// internal control variables
+
+	this.speed = 0;
+	this.acceleration = 0;
+
+	this.wheelOrientation = 0;
+	this.carOrientation = 0;
+
+	// car rigging
+
+	this.root = new THREE.Object3D();
+
+	this.frontLeftWheelRoot = new THREE.Object3D();
+	this.frontRightWheelRoot = new THREE.Object3D();
+
+	this.bodyMesh = null;
+
+	this.frontLeftWheelMesh = null;
+	this.frontRightWheelMesh = null;
+
+	this.backLeftWheelMesh = null;
+	this.backRightWheelMesh = null;
+
+	this.bodyGeometry = null;
+	this.wheelGeometry = null;
+
+	// internal helper variables
+
+	this.loaded = false;
+
+	this.meshes = [];
+
+	// API
+
+	this.enableShadows = function ( enable ) {
+
+		for ( var i = 0; i < this.meshes.length; i ++ ) {
+
+			this.meshes[ i ].castShadow = enable;
+			this.meshes[ i ].receiveShadow = enable;
+
+		}
+
+	};
+
+	this.setVisible = function ( enable ) {
+
+		for ( var i = 0; i < this.meshes.length; i ++ ) {
+
+			this.meshes[ i ].visible = enable;
+			this.meshes[ i ].visible = enable;
+
+		}
+
+	};
+
+	this.loadPartsJSON = function ( bodyURL, wheelURL ) {
+
+		var loader = new THREE.JSONLoader();
+
+		loader.load( bodyURL, function( geometry ) { createBody( geometry ) } );
+		loader.load( wheelURL, function( geometry ) { createWheels( geometry ) } );
+
+	};
+
+	this.loadPartsBinary = function ( bodyURL, wheelURL ) {
+
+		var loader = new THREE.BinaryLoader();
+
+		loader.load( bodyURL, function( geometry ) { createBody( geometry ) } );
+		loader.load( wheelURL, function( geometry ) { createWheels( geometry ) } );
+
+	};
+
+	this.updateCarModel = function ( delta, controls ) {
+
+		// speed and wheels based on controls
+
+		if ( controls.moveForward ) {
+
+			this.speed = THREE.Math.clamp( this.speed + delta * this.FRONT_ACCELERATION, this.MAX_REVERSE_SPEED, this.MAX_SPEED );
+			this.acceleration = THREE.Math.clamp( this.acceleration + delta, -1, 1 );
+
+		}
+
+		if ( controls.moveBackward ) {
+
+
+			this.speed = THREE.Math.clamp( this.speed - delta * this.BACK_ACCELERATION, this.MAX_REVERSE_SPEED, this.MAX_SPEED );
+			this.acceleration = THREE.Math.clamp( this.acceleration - delta, -1, 1 );
+
+		}
+
+		if ( controls.moveLeft ) {
+
+			this.wheelOrientation = THREE.Math.clamp( this.wheelOrientation + delta * this.WHEEL_ANGULAR_ACCELERATION, - this.MAX_WHEEL_ROTATION, this.MAX_WHEEL_ROTATION );
+
+		}
+
+		if ( controls.moveRight ) {
+
+			this.wheelOrientation = THREE.Math.clamp( this.wheelOrientation - delta * this.WHEEL_ANGULAR_ACCELERATION, - this.MAX_WHEEL_ROTATION, this.MAX_WHEEL_ROTATION );
+
+		}
+
+		// speed decay
+
+		if ( ! ( controls.moveForward || controls.moveBackward ) ) {
+
+			if ( this.speed > 0 ) {
+
+				var k = exponentialEaseOut( this.speed / this.MAX_SPEED );
+
+				this.speed = THREE.Math.clamp( this.speed - k * delta * this.FRONT_DECCELERATION, 0, this.MAX_SPEED );
+				this.acceleration = THREE.Math.clamp( this.acceleration - k * delta, 0, 1 );
+
+			} else {
+
+				var k = exponentialEaseOut( this.speed / this.MAX_REVERSE_SPEED );
+
+				this.speed = THREE.Math.clamp( this.speed + k * delta * this.BACK_ACCELERATION, this.MAX_REVERSE_SPEED, 0 );
+				this.acceleration = THREE.Math.clamp( this.acceleration + k * delta, -1, 0 );
+
+			}
+
+
+		}
+
+		// steering decay
+
+		if ( ! ( controls.moveLeft || controls.moveRight ) ) {
+
+			if ( this.wheelOrientation > 0 ) {
+
+				this.wheelOrientation = THREE.Math.clamp( this.wheelOrientation - delta * this.WHEEL_ANGULAR_DECCELERATION, 0, this.MAX_WHEEL_ROTATION );
+
+			} else {
+
+				this.wheelOrientation = THREE.Math.clamp( this.wheelOrientation + delta * this.WHEEL_ANGULAR_DECCELERATION, - this.MAX_WHEEL_ROTATION, 0 );
+
+			}
+
+		}
+
+		// car update
+
+		var forwardDelta = this.speed * delta;
+
+		this.carOrientation += ( forwardDelta * this.STEERING_RADIUS_RATIO )* this.wheelOrientation;
+
+		// displacement
+
+		this.root.position.x += Math.sin( this.carOrientation ) * forwardDelta;
+		this.root.position.z += Math.cos( this.carOrientation ) * forwardDelta;
+
+		// steering
+
+		this.root.rotation.y = this.carOrientation;
+
+		// tilt
+
+		if ( this.loaded ) {
+
+			this.bodyMesh.rotation.z = this.MAX_TILT_SIDES * this.wheelOrientation * ( this.speed / this.MAX_SPEED );
+			this.bodyMesh.rotation.x = - this.MAX_TILT_FRONTBACK * this.acceleration;
+
+		}
+
+		// wheels rolling
+
+		var angularSpeedRatio = 1 / ( this.modelScale * ( this.wheelDiameter / 2 ) );
+
+		var wheelDelta = forwardDelta * angularSpeedRatio;
+
+		if ( this.loaded ) {
+
+			this.frontLeftWheelMesh.rotation.x += wheelDelta;
+			this.frontRightWheelMesh.rotation.x += wheelDelta;
+			this.backLeftWheelMesh.rotation.x += wheelDelta;
+			this.backRightWheelMesh.rotation.x += wheelDelta;
+
+		}
+
+		// front wheels steering
+
+		this.frontLeftWheelRoot.rotation.y = this.wheelOrientation;
+		this.frontRightWheelRoot.rotation.y = this.wheelOrientation;
+
+	};
+
+	// internal helper methods
+
+	function createBody ( geometry ) {
+
+		scope.bodyGeometry = geometry;
+
+		createCar();
+
+	};
+
+	function createWheels ( geometry ) {
+
+		scope.wheelGeometry = geometry;
+
+		createCar();
+
+	};
+
+	function createCar () {
+
+		if ( scope.bodyGeometry && scope.wheelGeometry ) {
+
+			// compute wheel geometry parameters
+
+			if ( scope.autoWheelGeometry ) {
+
+				scope.wheelGeometry.computeBoundingBox();
+
+				var bb = scope.wheelGeometry.boundingBox;
+
+				scope.wheelOffset.add( bb.min, bb.max );
+				scope.wheelOffset.multiplyScalar( 0.5 );
+
+				scope.wheelDiameter = bb.max.y - bb.min.y;
+
+				THREE.GeometryUtils.center( scope.wheelGeometry );
+
+			}
+
+			// rig the car
+
+			var s = scope.modelScale,
+				delta = new THREE.Vector3(),
+				faceMaterial = new THREE.MeshFaceMaterial();
+
+			// body
+
+			scope.bodyMesh = new THREE.Mesh( scope.bodyGeometry, faceMaterial );
+			scope.bodyMesh.scale.set( s, s, s );
+
+			scope.root.add( scope.bodyMesh );
+
+			// front left wheel
+
+			delta.multiply( scope.wheelOffset, new THREE.Vector3( s, s, s ) );
+
+			scope.frontLeftWheelRoot.position.addSelf( delta );
+
+			scope.frontLeftWheelMesh = new THREE.Mesh( scope.wheelGeometry, faceMaterial );
+			scope.frontLeftWheelMesh.scale.set( s, s, s );
+
+			scope.frontLeftWheelRoot.add( scope.frontLeftWheelMesh );
+			scope.root.add( scope.frontLeftWheelRoot );
+
+			// front right wheel
+
+			delta.multiply( scope.wheelOffset, new THREE.Vector3( -s, s, s ) );
+
+			scope.frontRightWheelRoot.position.addSelf( delta );
+
+			scope.frontRightWheelMesh = new THREE.Mesh( scope.wheelGeometry, faceMaterial );
+
+			scope.frontRightWheelMesh.scale.set( s, s, s );
+			scope.frontRightWheelMesh.rotation.z = Math.PI;
+
+			scope.frontRightWheelRoot.add( scope.frontRightWheelMesh );
+			scope.root.add( scope.frontRightWheelRoot );
+
+			// back left wheel
+
+			delta.multiply( scope.wheelOffset, new THREE.Vector3( s, s, -s ) );
+			delta.z -= scope.backWheelOffset;
+
+			scope.backLeftWheelMesh = new THREE.Mesh( scope.wheelGeometry, faceMaterial );
+
+			scope.backLeftWheelMesh.position.addSelf( delta );
+			scope.backLeftWheelMesh.scale.set( s, s, s );
+
+			scope.root.add( scope.backLeftWheelMesh );
+
+			// back right wheel
+
+			delta.multiply( scope.wheelOffset, new THREE.Vector3( -s, s, -s ) );
+			delta.z -= scope.backWheelOffset;
+
+			scope.backRightWheelMesh = new THREE.Mesh( scope.wheelGeometry, faceMaterial );
+
+			scope.backRightWheelMesh.position.addSelf( delta );
+			scope.backRightWheelMesh.scale.set( s, s, s );
+			scope.backRightWheelMesh.rotation.z = Math.PI;
+
+			scope.root.add( scope.backRightWheelMesh );
+
+			// cache meshes
+
+			scope.meshes = [ scope.bodyMesh, scope.frontLeftWheelMesh, scope.frontRightWheelMesh, scope.backLeftWheelMesh, scope.backRightWheelMesh ];
+
+			// callback
+
+			scope.loaded = true;
+
+			if ( scope.callback ) {
+
+				scope.callback( scope );
+
+			}
+
+		}
+
+	};
+
+	function quadraticEaseOut( k ) { return - k * ( k - 2 ); }
+	function cubicEaseOut( k ) { return --k * k * k + 1; }
+	function circularEaseOut( k ) { return Math.sqrt( 1 - --k * k ); }
+	function sinusoidalEaseOut( k ) { return Math.sin( k * Math.PI / 2 ); }
+	function exponentialEaseOut( k ) { return k === 1 ? 1 : - Math.pow( 2, - 10 * k ) + 1; }
+
+};
diff --git a/emperor/support_files/js/js/DAT.GUI.min.js b/emperor/support_files/js/js/DAT.GUI.min.js
new file mode 100644
index 0000000..8ed2120
--- /dev/null
+++ b/emperor/support_files/js/js/DAT.GUI.min.js
@@ -0,0 +1,53 @@
+/**
+ * dat.gui Javascript Controller Library
+ * http://dataarts.github.com/dat.gui
+ *
+ * Copyright 2011 Data Arts Team, Google Creative Lab
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+var DAT=DAT||{};
+DAT.GUI=function(a){a==void 0&&(a={});var b=!1;a.height==void 0?a.height=300:b=!0;var d=[],c=[],i=!0,f,h,j=this,g=!0,e=280;if(a.width!=void 0)e=a.width;var q=!1,k,p,n=0,r;this.domElement=document.createElement("div");this.domElement.setAttribute("class","guidat");this.domElement.style.width=e+"px";var l=a.height,m=document.createElement("div");m.setAttribute("class","guidat-controllers");m.style.height=l+"px";m.addEventListener("DOMMouseScroll",function(a){var b=this.scrollTop;a.wheelDel [...]
+a.detail&&(b+=a.detail);a.preventDefault&&a.preventDefault();a.returnValue=!1;m.scrollTop=b},!1);var o=document.createElement("a");o.setAttribute("class","guidat-toggle");o.setAttribute("href","#");o.innerHTML=g?"Close Controls":"Open Controls";var t=!1,C=0,x=0,u=!1,v,y,w,z,D=function(a){y=v;z=w;v=a.pageY;w=a.pageX;a=v-y;if(!g)if(a>0)g=!0,l=k=1,o.innerHTML=p||"Close Controls";else return;var b=z-w;if(a>0&&l>h){var d=DAT.GUI.map(l,h,h+100,1,0);a*=d}t=!0;C+=a;k+=a;l+=a;m.style.height=k+"px [...]
+b;e=DAT.GUI.constrain(e,240,500);j.domElement.style.width=e+"px";A()};o.addEventListener("mousedown",function(a){y=v=a.pageY;z=w=a.pageX;u=!0;a.preventDefault();C=x=0;document.addEventListener("mousemove",D,!1);return!1},!1);o.addEventListener("click",function(a){a.preventDefault();return!1},!1);document.addEventListener("mouseup",function(a){u&&!t&&j.toggle();if(u&&t)if(x==0&&B(),k>h)clearTimeout(r),k=n=h,s();else if(m.children.length>=1){var b=m.children[0].offsetHeight;clearTimeout(r) [...]
+b)*b-1;n<=0?(j.close(),k=b*2):(k=n,s())}document.removeEventListener("mousemove",D,!1);a.preventDefault();return u=t=!1},!1);this.domElement.appendChild(m);this.domElement.appendChild(o);if(a.domElement)a.domElement.appendChild(this.domElement);else if(DAT.GUI.autoPlace){if(DAT.GUI.autoPlaceContainer==null)DAT.GUI.autoPlaceContainer=document.createElement("div"),DAT.GUI.autoPlaceContainer.setAttribute("id","guidat"),document.body.appendChild(DAT.GUI.autoPlaceContainer);DAT.GUI.autoPlaceC [...]
+1E3/60;var E=function(){f=setInterval(function(){j.listen()},this.autoListenIntervalTime)};this.__defineSetter__("autoListen",function(a){(i=a)?c.length>0&&E():clearInterval(f)});this.__defineGetter__("autoListen",function(){return i});this.listenTo=function(a){c.length==0&&E();c.push(a)};this.unlistenTo=function(a){for(var b=0;b<c.length;b++)c[b]==a&&c.splice(b,1);c.length<=0&&clearInterval(f)};this.listen=function(a){var a=a||c,b;for(b in a)a[b].updateDisplay()};this.listenAll=function [...]
+this.autoListen=!0;var F=function(a,b){function d(){return a.apply(this,b)}d.prototype=a.prototype;return new d};this.add=function(){if(arguments.length==1){var a=[],c;for(c in arguments[0])a.push(j.add(arguments[0],c));return a}a=arguments[0];c=arguments[1];a:for(var e in d)if(d[e].object==a&&d[e].propertyName==c)break a;e=a[c];e==void 0&&a.get&&(e=a.get(c));if(e==void 0)DAT.GUI.error(a+" either has no property '"+c+"', or the property is inaccessible.");else if(a=typeof e,e=G[a],e==voi [...]
+a+"'");else{for(var f=[this],g=0;g<arguments.length;g++)f.push(arguments[g]);if(e=F(e,f)){m.appendChild(e.domElement);d.push(e);DAT.GUI.allControllers.push(e);a!="function"&&DAT.GUI.saveIndex<DAT.GUI.savedValues.length&&(e.setValue(DAT.GUI.savedValues[DAT.GUI.saveIndex]),DAT.GUI.saveIndex++);A();q||(k=h);if(!b)try{if(arguments.callee.caller==window.onload)l=n=k=h,m.style.height=l+"px"}catch(i){}return e}else DAT.GUI.error("Error creating controller for '"+c+"'.")}};var A=function(){h=0;f [...]
+d[a].domElement.offsetHeight;m.style.overflowY=h-1>k?"auto":"hidden"},G={number:DAT.GUI.ControllerNumber,string:DAT.GUI.ControllerString,"boolean":DAT.GUI.ControllerBoolean,"function":DAT.GUI.ControllerFunction};this.reset=function(){for(var a=0,b=DAT.GUI.allControllers.length;a<b;a++)DAT.GUI.allControllers[a].reset()};this.toggle=function(){g?this.close():this.open()};this.open=function(){o.innerHTML=p||"Close Controls";n=k;clearTimeout(r);s();B();g=!0};this.close=function(){o.innerHTML [...]
+n=0;clearTimeout(r);s();B();g=!1};this.name=function(a){p=a;o.innerHTML=a};this.appearanceVars=function(){return[g,e,k,m.scrollTop]};var s=function(){l=m.offsetHeight;l+=(n-l)*0.6;Math.abs(l-n)<1?l=n:r=setTimeout(s,1E3/30);m.style.height=Math.round(l)+"px";A()},B=function(){j.domElement.style.width=e-1+"px";setTimeout(function(){j.domElement.style.width=e+"px"},1)};if(DAT.GUI.guiIndex<DAT.GUI.savedAppearanceVars.length){e=parseInt(DAT.GUI.savedAppearanceVars[DAT.GUI.guiIndex][1]);j.domEl [...]
+e+"px";k=parseInt(DAT.GUI.savedAppearanceVars[DAT.GUI.guiIndex][2]);q=!0;if(eval(DAT.GUI.savedAppearanceVars[DAT.GUI.guiIndex][0])==!0){var l=k,H=DAT.GUI.savedAppearanceVars[DAT.GUI.guiIndex][3];setTimeout(function(){m.scrollTop=H},0);if(DAT.GUI.scrollTop>-1)document.body.scrollTop=DAT.GUI.scrollTop;n=k;this.open()}DAT.GUI.guiIndex++}DAT.GUI.allGuis.push(this);if(DAT.GUI.allGuis.length==1&&(window.addEventListener("keyup",function(a){!DAT.GUI.supressHotKeys&&a.keyCode==72&&DAT.GUI.toggle [...]
+DAT.GUI.inlineCSS))a=document.createElement("style"),a.setAttribute("type","text/css"),a.innerHTML=DAT.GUI.inlineCSS,document.head.insertBefore(a,document.head.firstChild)};DAT.GUI.hidden=!1;DAT.GUI.autoPlace=!0;DAT.GUI.autoPlaceContainer=null;DAT.GUI.allControllers=[];DAT.GUI.allGuis=[];DAT.GUI.supressHotKeys=!1;DAT.GUI.toggleHide=function(){DAT.GUI.hidden?DAT.GUI.open():DAT.GUI.close()};
+DAT.GUI.open=function(){DAT.GUI.hidden=!1;for(var a in DAT.GUI.allGuis)DAT.GUI.allGuis[a].domElement.style.display="block"};DAT.GUI.close=function(){DAT.GUI.hidden=!0;for(var a in DAT.GUI.allGuis)DAT.GUI.allGuis[a].domElement.style.display="none"};DAT.GUI.saveURL=function(){var a=DAT.GUI.replaceGetVar("saveString",DAT.GUI.getSaveString());window.location=a};DAT.GUI.scrollTop=-1;
+DAT.GUI.load=function(a){var a=a.split(","),b=parseInt(a[0]);DAT.GUI.scrollTop=parseInt(a[1]);for(var d=0;d<b;d++){var c=a.splice(2,4);DAT.GUI.savedAppearanceVars.push(c)}DAT.GUI.savedValues=a.splice(2,a.length)};DAT.GUI.savedValues=[];DAT.GUI.savedAppearanceVars=[];
+DAT.GUI.getSaveString=function(){var a=[],b;a.push(DAT.GUI.allGuis.length);a.push(document.body.scrollTop);for(b in DAT.GUI.allGuis)for(var d=DAT.GUI.allGuis[b].appearanceVars(),c=0;c<d.length;c++)a.push(d[c]);for(b in DAT.GUI.allControllers)DAT.GUI.allControllers[b].type!="function"&&(d=DAT.GUI.allControllers[b].getValue(),DAT.GUI.allControllers[b].type=="number"&&(d=DAT.GUI.roundToDecimal(d,4)),a.push(d));return a.join(",")};
+DAT.GUI.getVarFromURL=function(a){for(var b,d=window.location.href.slice(window.location.href.indexOf("?")+1).split("&"),c=0;c<d.length;c++)if(b=d[c].split("="),b!=void 0&&b[0]==a)return b[1];return null};
+DAT.GUI.replaceGetVar=function(a,b){for(var d,c=window.location.href,i=window.location.href.slice(window.location.href.indexOf("?")+1).split("&"),f=0;f<i.length;f++)if(d=i[f].split("="),d!=void 0&&d[0]==a)return c.replace(d[1],b);return window.location.href.indexOf("?")!=-1?c+"&"+a+"="+b:c+"?"+a+"="+b};DAT.GUI.saveIndex=0;DAT.GUI.guiIndex=0;DAT.GUI.showSaveString=function(){alert(DAT.GUI.getSaveString())};
+DAT.GUI.makeUnselectable=function(a){if(!(a==void 0||a.style==void 0)){a.onselectstart=function(){return!1};a.style.MozUserSelect="none";a.style.KhtmlUserSelect="none";a.unselectable="on";for(var a=a.childNodes,b=0;b<a.length;b++)DAT.GUI.makeUnselectable(a[b])}};DAT.GUI.makeSelectable=function(a){if(!(a==void 0||a.style==void 0)){a.onselectstart=function(){};a.style.MozUserSelect="auto";a.style.KhtmlUserSelect="auto";a.unselectable="off";for(var a=a.childNodes,b=0;b<a.length;b++)DAT.GUI. [...]
+DAT.GUI.map=function(a,b,d,c,i){return c+(i-c)*((a-b)/(d-b))};DAT.GUI.constrain=function(a,b,d){a<b?a=b:a>d&&(a=d);return a};DAT.GUI.error=function(a){typeof console.error=="function"&&console.error("[DAT.GUI ERROR] "+a)};DAT.GUI.roundToDecimal=function(a,b){var d=Math.pow(10,b);return Math.round(a*d)/d};DAT.GUI.extendController=function(a){a.prototype=new DAT.GUI.Controller;a.prototype.constructor=a};DAT.GUI.addClass=function(a,b){DAT.GUI.hasClass(a,b)||(a.className+=" "+b)};
+DAT.GUI.hasClass=function(a,b){return a.className.indexOf(b)!=-1};DAT.GUI.removeClass=function(a,b){a.className=a.className.replace(RegExp(" "+b,"g"),"")};DAT.GUI.getVarFromURL("saveString")!=null&&DAT.GUI.load(DAT.GUI.getVarFromURL("saveString"));
+DAT.GUI.Controller=function(){this.parent=arguments[0];this.object=arguments[1];this.propertyName=arguments[2];if(arguments.length>0)this.initialValue=this.object[this.propertyName];this.domElement=document.createElement("div");this.domElement.setAttribute("class","guidat-controller "+this.type);this.propertyNameElement=document.createElement("span");this.propertyNameElement.setAttribute("class","guidat-propertyname");this.name(this.propertyName);this.domElement.appendChild(this.property [...]
+DAT.GUI.makeUnselectable(this.domElement)};DAT.GUI.Controller.prototype.changeFunction=null;DAT.GUI.Controller.prototype.finishChangeFunction=null;DAT.GUI.Controller.prototype.name=function(a){this.propertyNameElement.innerHTML=a;return this};DAT.GUI.Controller.prototype.reset=function(){this.setValue(this.initialValue);return this};DAT.GUI.Controller.prototype.listen=function(){this.parent.listenTo(this);return this};DAT.GUI.Controller.prototype.unlisten=function(){this.parent.unlistenT [...]
+DAT.GUI.Controller.prototype.setValue=function(a){if(this.object[this.propertyName]!=void 0)this.object[this.propertyName]=a;else{var b={};b[this.propertyName]=a;this.object.set(b)}this.changeFunction!=null&&this.changeFunction.call(this,a);this.updateDisplay();return this};DAT.GUI.Controller.prototype.getValue=function(){var a=this.object[this.propertyName];a==void 0&&(a=this.object.get(this.propertyName));return a};DAT.GUI.Controller.prototype.updateDisplay=function(){};
+DAT.GUI.Controller.prototype.onChange=function(a){this.changeFunction=a;return this};DAT.GUI.Controller.prototype.onFinishChange=function(a){this.finishChangeFunction=a;return this};
+DAT.GUI.Controller.prototype.options=function(){var a=this,b=document.createElement("select");if(arguments.length==1){var d=arguments[0],c;for(c in d){var i=document.createElement("option");i.innerHTML=c;i.setAttribute("value",d[c]);if(arguments[c]==this.getValue())i.selected=!0;b.appendChild(i)}}else for(c=0;c<arguments.length;c++){i=document.createElement("option");i.innerHTML=arguments[c];i.setAttribute("value",arguments[c]);if(arguments[c]==this.getValue())i.selected=!0;b.appendChild [...]
+function(){a.setValue(this.value);a.finishChangeFunction!=null&&a.finishChangeFunction.call(this,a.getValue())},!1);a.domElement.appendChild(b);return this};
+DAT.GUI.ControllerBoolean=function(){this.type="boolean";DAT.GUI.Controller.apply(this,arguments);var a=this,b=document.createElement("input");b.setAttribute("type","checkbox");b.checked=this.getValue();this.setValue(this.getValue());this.domElement.addEventListener("click",function(d){b.checked=!b.checked;d.preventDefault();a.setValue(b.checked)},!1);b.addEventListener("mouseup",function(){b.checked=!b.checked},!1);this.domElement.style.cursor="pointer";this.propertyNameElement.style.cu [...]
+this.domElement.appendChild(b);this.updateDisplay=function(){b.checked=a.getValue()};this.setValue=function(a){if(typeof a!="boolean")try{a=eval(a)}catch(b){}return DAT.GUI.Controller.prototype.setValue.call(this,a)}};DAT.GUI.extendController(DAT.GUI.ControllerBoolean);
+DAT.GUI.ControllerFunction=function(){this.type="function";var a=this;DAT.GUI.Controller.apply(this,arguments);this.domElement.addEventListener("click",function(){a.fire()},!1);this.domElement.style.cursor="pointer";this.propertyNameElement.style.cursor="pointer";var b=null;this.onFire=function(a){b=a;return this};this.fire=function(){b!=null&&b.call(this);a.object[a.propertyName].call(a.object)}};DAT.GUI.extendController(DAT.GUI.ControllerFunction);
+DAT.GUI.ControllerNumber=function(){this.type="number";DAT.GUI.Controller.apply(this,arguments);var a=this,b=!1,d=!1,c=0,i=0,f=arguments[3],h=arguments[4],j=arguments[5];this.min=function(){var b=!1;f==void 0&&h!=void 0&&(b=!0);if(arguments.length==0)return f;else f=arguments[0];b&&(q(),j==void 0&&(j=(h-f)*0.01));return a};this.max=function(){var b=!1;f!=void 0&&h==void 0&&(b=!0);if(arguments.length==0)return h;else h=arguments[0];b&&(q(),j==void 0&&(j=(h-f)*0.01));return a};this.step=fu [...]
+0)return j;else j=arguments[0];return a};this.getMin=function(){return f};this.getMax=function(){return h};this.getStep=function(){return j==void 0?h!=void 0&&f!=void 0?(h-f)/100:1:j};var g=document.createElement("input");g.setAttribute("id",this.propertyName);g.setAttribute("type","text");g.setAttribute("value",this.getValue());j&&g.setAttribute("step",j);this.domElement.appendChild(g);var e,q=function(){e=new DAT.GUI.ControllerNumberSlider(a,f,h,j,a.getValue());a.domElement.appendChild [...]
+f!=void 0&&h!=void 0&&q();g.addEventListener("blur",function(){var b=parseFloat(this.value);e&&DAT.GUI.removeClass(a.domElement,"active");isNaN(b)||a.setValue(b)},!1);g.addEventListener("mousewheel",function(b){b.preventDefault();a.setValue(a.getValue()+Math.abs(b.wheelDeltaY)/b.wheelDeltaY*a.getStep());return!1},!1);g.addEventListener("mousedown",function(a){i=c=a.pageY;DAT.GUI.makeSelectable(g);document.addEventListener("mousemove",p,!1);document.addEventListener("mouseup",k,!1)},!1);g [...]
+function(b){switch(b.keyCode){case 13:b=parseFloat(this.value);a.setValue(b);break;case 38:b=a.getValue()+a.getStep();a.setValue(b);break;case 40:b=a.getValue()-a.getStep(),a.setValue(b)}},!1);var k=function(){document.removeEventListener("mousemove",p,!1);DAT.GUI.makeSelectable(g);a.finishChangeFunction!=null&&a.finishChangeFunction.call(this,a.getValue());d=b=!1;document.removeEventListener("mouseup",k,!1)},p=function(e){i=c;c=e.pageY;var f=i-c;!b&&!d&&(f==0?b=!0:d=!0);if(b)return!0;DA [...]
+"active");DAT.GUI.makeUnselectable(a.parent.domElement);DAT.GUI.makeUnselectable(g);e.preventDefault();e=a.getValue()+f*a.getStep();a.setValue(e);return!1};this.options=function(){a.noSlider();a.domElement.removeChild(g);return DAT.GUI.Controller.prototype.options.apply(this,arguments)};this.noSlider=function(){e&&a.domElement.removeChild(e.domElement);return this};this.setValue=function(a){a=parseFloat(a);f!=void 0&&a<=f?a=f:h!=void 0&&a>=h&&(a=h);return DAT.GUI.Controller.prototype.set [...]
+a)};this.updateDisplay=function(){g.value=DAT.GUI.roundToDecimal(a.getValue(),4);if(e)e.value=a.getValue()}};DAT.GUI.extendController(DAT.GUI.ControllerNumber);
+DAT.GUI.ControllerNumberSlider=function(a,b,d,c,i){var f=!1,h=this;this.domElement=document.createElement("div");this.domElement.setAttribute("class","guidat-slider-bg");this.fg=document.createElement("div");this.fg.setAttribute("class","guidat-slider-fg");this.domElement.appendChild(this.fg);var j=function(b){if(f){var c;c=h.domElement;var d=0,g=0;if(c.offsetParent){do d+=c.offsetLeft,g+=c.offsetTop;while(c=c.offsetParent);c=[d,g]}else c=void 0;b=DAT.GUI.map(b.pageX,c[0],c[0]+h.domEleme [...]
+a.getMin(),a.getMax());b=Math.round(b/a.getStep())*a.getStep();a.setValue(b)}};this.domElement.addEventListener("mousedown",function(b){f=!0;DAT.GUI.addClass(a.domElement,"active");j(b);document.addEventListener("mouseup",g,!1)},!1);var g=function(){DAT.GUI.removeClass(a.domElement,"active");f=!1;a.finishChangeFunction!=null&&a.finishChangeFunction.call(this,a.getValue());document.removeEventListener("mouseup",g,!1)};this.__defineSetter__("value",function(b){this.fg.style.width=DAT.GUI.m [...]
+a.getMax(),0,100)+"%"});document.addEventListener("mousemove",j,!1);this.value=i};
+DAT.GUI.ControllerString=function(){this.type="string";var a=this;DAT.GUI.Controller.apply(this,arguments);var b=document.createElement("input"),d=this.getValue();b.setAttribute("value",d);b.setAttribute("spellcheck","false");this.domElement.addEventListener("mouseup",function(){b.focus();b.select()},!1);b.addEventListener("keyup",function(c){c.keyCode==13&&a.finishChangeFunction!=null&&(a.finishChangeFunction.call(this,a.getValue()),b.blur());a.setValue(b.value)},!1);b.addEventListener( [...]
+function(){DAT.GUI.makeSelectable(b)},!1);b.addEventListener("blur",function(){DAT.GUI.supressHotKeys=!1;a.finishChangeFunction!=null&&a.finishChangeFunction.call(this,a.getValue())},!1);b.addEventListener("focus",function(){DAT.GUI.supressHotKeys=!0},!1);this.updateDisplay=function(){b.value=a.getValue()};this.options=function(){a.domElement.removeChild(b);return DAT.GUI.Controller.prototype.options.apply(this,arguments)};this.domElement.appendChild(b)};DAT.GUI.extendController(DAT.GUI. [...]
+DAT.GUI.inlineCSS="#guidat { position: fixed; top: 0; right: 0; width: auto; z-index: 1001; text-align: right; } .guidat { color: #fff; opacity: 0.97; text-align: left; float: right; margin-right: 20px; margin-bottom: 20px; background-color: #fff; } .guidat, .guidat input { font: 9.5px Lucida Grande, sans-serif; } .guidat-controllers { height: 300px; overflow-y: auto; overflow-x: hidden; background-color: rgba(0, 0, 0, 0.1); } a.guidat-toggle:link, a.guidat-toggle:visited, a.guidat-toggl [...]
diff --git a/emperor/support_files/js/js/Detector.js b/emperor/support_files/js/js/Detector.js
new file mode 100644
index 0000000..2d38906
--- /dev/null
+++ b/emperor/support_files/js/js/Detector.js
@@ -0,0 +1,58 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mr.doob / http://mrdoob.com/
+ */
+
+Detector = {
+
+	canvas : !! window.CanvasRenderingContext2D,
+	webgl : ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(),
+	workers : !! window.Worker,
+	fileapi : window.File && window.FileReader && window.FileList && window.Blob,
+
+	getWebGLErrorMessage : function () {
+
+		var domElement = document.createElement( 'div' );
+
+		domElement.style.fontFamily = 'monospace';
+		domElement.style.fontSize = '13px';
+		domElement.style.textAlign = 'center';
+		domElement.style.background = '#eee';
+		domElement.style.color = '#000';
+		domElement.style.padding = '1em';
+		domElement.style.width = '475px';
+		domElement.style.margin = '5em auto 0';
+
+		if ( ! this.webgl ) {
+
+			domElement.innerHTML = window.WebGLRenderingContext ? [
+				'Your graphics card does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a>.<br />',
+				'Find out how to get it <a href="http://get.webgl.org/">here</a>.'
+			].join( '\n' ) : [
+				'Your browser does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a>.<br/>',
+				'Find out how to get it <a href="http://get.webgl.org/">here</a>.'
+			].join( '\n' );
+
+		}
+
+		return domElement;
+
+	},
+
+	addGetWebGLMessage : function ( parameters ) {
+
+		var parent, id, domElement;
+
+		parameters = parameters || {};
+
+		parent = parameters.parent !== undefined ? parameters.parent : document.body;
+		id = parameters.id !== undefined ? parameters.id : 'oldie';
+
+		domElement = Detector.getWebGLErrorMessage();
+		domElement.id = id;
+
+		parent.appendChild( domElement );
+
+	}
+
+};
diff --git a/emperor/support_files/js/js/ImprovedNoise.js b/emperor/support_files/js/js/ImprovedNoise.js
new file mode 100644
index 0000000..08246a6
--- /dev/null
+++ b/emperor/support_files/js/js/ImprovedNoise.js
@@ -0,0 +1,71 @@
+// http://mrl.nyu.edu/~perlin/noise/
+
+var ImprovedNoise = function () {
+
+	var p = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,
+		 23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,
+		 174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,
+		 133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,
+		 89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,
+		 202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,
+		 248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,
+		 178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,
+		 14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,
+		 93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180];
+
+	for (var i=0; i < 256 ; i++) {
+
+		p[256+i] = p[i];
+
+	}
+
+	function fade(t) {
+
+		return t * t * t * (t * (t * 6 - 15) + 10);
+
+	}
+
+	function lerp(t, a, b) {
+
+		return a + t * (b - a);
+
+	}
+
+	function grad(hash, x, y, z) {
+
+		var h = hash & 15;
+		var u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z;
+		return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
+
+	}
+
+	return {
+
+		noise: function (x, y, z) {
+
+			var floorX = ~~x, floorY = ~~y, floorZ = ~~z;
+
+			var X = floorX & 255, Y = floorY & 255, Z = floorZ & 255;
+
+			x -= floorX;
+			y -= floorY;
+			z -= floorZ;
+
+			var xMinus1 = x -1, yMinus1 = y - 1, zMinus1 = z - 1;
+
+			var u = fade(x), v = fade(y), w = fade(z);
+
+			var A = p[X]+Y, AA = p[A]+Z, AB = p[A+1]+Z, B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z;
+
+			return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), 
+							grad(p[BA], xMinus1, y, z)),
+						lerp(u, grad(p[AB], x, yMinus1, z),
+							grad(p[BB], xMinus1, yMinus1, z))),
+					lerp(v, lerp(u, grad(p[AA+1], x, y, zMinus1),
+							grad(p[BA+1], xMinus1, y, z-1)),
+						lerp(u, grad(p[AB+1], x, yMinus1, zMinus1),
+							grad(p[BB+1], xMinus1, yMinus1, zMinus1))));
+
+		}
+	}
+}
diff --git a/emperor/support_files/js/js/PRNG.js b/emperor/support_files/js/js/PRNG.js
new file mode 100644
index 0000000..038b7ba
--- /dev/null
+++ b/emperor/support_files/js/js/PRNG.js
@@ -0,0 +1,11 @@
+// Park-Miller-Carta Pseudo-Random Number Generator
+// https://github.com/pnitsch/BitmapData.js/blob/master/js/BitmapData.js
+
+var PRNG = function () {
+
+	this.seed = 1;
+	this.next = function() { return (this.gen() / 2147483647); };
+	this.nextRange = function(min, max)	{ return min + ((max - min) * this.next()) };
+	this.gen = function() { return this.seed = (this.seed * 16807) % 2147483647; };
+
+};
diff --git a/emperor/support_files/js/js/RequestAnimationFrame.js b/emperor/support_files/js/js/RequestAnimationFrame.js
new file mode 100644
index 0000000..77f85c5
--- /dev/null
+++ b/emperor/support_files/js/js/RequestAnimationFrame.js
@@ -0,0 +1,22 @@
+/**
+ * Provides requestAnimationFrame in a cross browser way.
+ * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ */
+
+if ( !window.requestAnimationFrame ) {
+
+	window.requestAnimationFrame = ( function() {
+
+		return window.webkitRequestAnimationFrame ||
+		window.mozRequestAnimationFrame ||
+		window.oRequestAnimationFrame ||
+		window.msRequestAnimationFrame ||
+		function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {
+
+			window.setTimeout( callback, 1000 / 60 );
+
+		};
+
+	} )();
+
+}
diff --git a/emperor/support_files/js/js/ShaderExtras.js b/emperor/support_files/js/js/ShaderExtras.js
new file mode 100644
index 0000000..0fcda20
--- /dev/null
+++ b/emperor/support_files/js/js/ShaderExtras.js
@@ -0,0 +1,1779 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ *
+ * ShaderExtras currently contains:
+ *
+ *	screen
+ *	convolution
+ *	film
+ * 	bokeh
+ *  sepia
+ *	dotscreen
+ *	vignette
+ *  bleachbypass
+ *	basic
+ *  dofmipmap
+ *  focus
+ *  triangleBlur
+ *  horizontalBlur + verticalBlur
+ *  horizontalTiltShift + verticalTiltShift
+ *  blend
+ *  fxaa
+ *  luminosity
+ *  colorCorrection
+ *  normalmap
+ *  ssao
+ *  colorify
+ *  unpackDepthRGBA
+ */
+
+THREE.ShaderExtras = {
+
+	/* -------------------------------------------------------------------------
+	//	Full-screen textured quad shader
+	 ------------------------------------------------------------------------- */
+
+	'screen': {
+
+		uniforms: {
+
+			tDiffuse: { type: "t", value: 0, texture: null },
+			opacity:  { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float opacity;",
+
+			"uniform sampler2D tDiffuse;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 texel = texture2D( tDiffuse, vUv );",
+				"gl_FragColor = opacity * texel;",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* ------------------------------------------------------------------------
+	//	Convolution shader
+	//	  - ported from o3d sample to WebGL / GLSL
+	//			http://o3d.googlecode.com/svn/trunk/samples/convolution.html
+	------------------------------------------------------------------------ */
+
+	'convolution': {
+
+		uniforms: {
+
+			"tDiffuse" : 		{ type: "t", value: 0, texture: null },
+			"uImageIncrement" : { type: "v2", value: new THREE.Vector2( 0.001953125, 0.0 ) },
+			"cKernel" : 		{ type: "fv1", value: [] }
+
+		},
+
+		vertexShader: [
+
+			//"#define KERNEL_SIZE 25.0",
+
+			"uniform vec2 uImageIncrement;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = uv - ( ( KERNEL_SIZE - 1.0 ) / 2.0 ) * uImageIncrement;",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			//"#define KERNEL_SIZE 25",
+			"uniform float cKernel[ KERNEL_SIZE ];",
+
+			"uniform sampler2D tDiffuse;",
+			"uniform vec2 uImageIncrement;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec2 imageCoord = vUv;",
+				"vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 );",
+
+				"for( int i = 0; i < KERNEL_SIZE; i ++ ) {",
+
+					"sum += texture2D( tDiffuse, imageCoord ) * cKernel[ i ];",
+					"imageCoord += uImageIncrement;",
+
+				"}",
+
+				"gl_FragColor = sum;",
+
+			"}"
+
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+
+	// Film grain & scanlines shader
+
+	//	- ported from HLSL to WebGL / GLSL
+	//	  http://www.truevision3d.com/forums/showcase/staticnoise_colorblackwhite_scanline_shaders-t18698.0.html
+
+	// Screen Space Static Postprocessor
+	//
+	// Produces an analogue noise overlay similar to a film grain / TV static
+	//
+	// Original implementation and noise algorithm
+	// Pat 'Hawthorne' Shearon
+	//
+	// Optimized scanlines + noise version with intensity scaling
+	// Georg 'Leviathan' Steinrohder
+
+	// This version is provided under a Creative Commons Attribution 3.0 License
+	// http://creativecommons.org/licenses/by/3.0/
+	 ------------------------------------------------------------------------- */
+
+	'film': {
+
+		uniforms: {
+
+			tDiffuse:   { type: "t", value: 0, texture: null },
+			time: 	    { type: "f", value: 0.0 },
+			nIntensity: { type: "f", value: 0.5 },
+			sIntensity: { type: "f", value: 0.05 },
+			sCount: 	{ type: "f", value: 4096 },
+			grayscale:  { type: "i", value: 1 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			// control parameter
+			"uniform float time;",
+
+			"uniform bool grayscale;",
+
+			// noise effect intensity value (0 = no effect, 1 = full effect)
+			"uniform float nIntensity;",
+
+			// scanlines effect intensity value (0 = no effect, 1 = full effect)
+			"uniform float sIntensity;",
+
+			// scanlines effect count value (0 = no effect, 4096 = full effect)
+			"uniform float sCount;",
+
+			"uniform sampler2D tDiffuse;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				// sample the source
+				"vec4 cTextureScreen = texture2D( tDiffuse, vUv );",
+
+				// make some noise
+				"float x = vUv.x * vUv.y * time *  1000.0;",
+				"x = mod( x, 13.0 ) * mod( x, 123.0 );",
+				"float dx = mod( x, 0.01 );",
+
+				// add noise
+				"vec3 cResult = cTextureScreen.rgb + cTextureScreen.rgb * clamp( 0.1 + dx * 100.0, 0.0, 1.0 );",
+
+				// get us a sine and cosine
+				"vec2 sc = vec2( sin( vUv.y * sCount ), cos( vUv.y * sCount ) );",
+
+				// add scanlines
+				"cResult += cTextureScreen.rgb * vec3( sc.x, sc.y, sc.x ) * sIntensity;",
+
+				// interpolate between source and result by intensity
+				"cResult = cTextureScreen.rgb + clamp( nIntensity, 0.0,1.0 ) * ( cResult - cTextureScreen.rgb );",
+
+				// convert to grayscale if desired
+				"if( grayscale ) {",
+
+					"cResult = vec3( cResult.r * 0.3 + cResult.g * 0.59 + cResult.b * 0.11 );",
+
+				"}",
+
+				"gl_FragColor =  vec4( cResult, cTextureScreen.a );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+
+	/* -------------------------------------------------------------------------
+	//	Depth-of-field shader with bokeh
+	//	ported from GLSL shader by Martins Upitis
+	//	http://artmartinsh.blogspot.com/2010/02/glsl-lens-blur-filter-with-bokeh.html
+	 ------------------------------------------------------------------------- */
+
+	'bokeh'	: {
+
+	uniforms: { tColor:   { type: "t", value: 0, texture: null },
+				tDepth:   { type: "t", value: 1, texture: null },
+				focus:    { type: "f", value: 1.0 },
+				aspect:   { type: "f", value: 1.0 },
+				aperture: { type: "f", value: 0.025 },
+				maxblur:  { type: "f", value: 1.0 },
+			  },
+
+	vertexShader: [
+
+	"varying vec2 vUv;",
+
+	"void main() {",
+
+		"vUv = vec2( uv.x, 1.0 - uv.y );",
+		"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+	"}"
+
+	].join("\n"),
+
+	fragmentShader: [
+
+	"varying vec2 vUv;",
+
+	"uniform sampler2D tColor;",
+	"uniform sampler2D tDepth;",
+
+	"uniform float maxblur;",  	// max blur amount
+	"uniform float aperture;",	// aperture - bigger values for shallower depth of field
+
+	"uniform float focus;",
+	"uniform float aspect;",
+
+	"void main() {",
+
+		"vec2 aspectcorrect = vec2( 1.0, aspect );",
+
+		"vec4 depth1 = texture2D( tDepth, vUv );",
+
+		"float factor = depth1.x - focus;",
+
+		"vec2 dofblur = vec2 ( clamp( factor * aperture, -maxblur, maxblur ) );",
+
+		"vec2 dofblur9 = dofblur * 0.9;",
+		"vec2 dofblur7 = dofblur * 0.7;",
+		"vec2 dofblur4 = dofblur * 0.4;",
+
+		"vec4 col = vec4( 0.0 );",
+
+		"col += texture2D( tColor, vUv.xy );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,   0.4  ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.15,  0.37 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.29,  0.29 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.37,  0.15 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.40,  0.0  ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.37, -0.15 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.29, -0.29 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.15, -0.37 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,  -0.4  ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.15,  0.37 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.29,  0.29 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.37,  0.15 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.4,   0.0  ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.37, -0.15 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.29, -0.29 ) * aspectcorrect ) * dofblur );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.15, -0.37 ) * aspectcorrect ) * dofblur );",
+
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.15,  0.37 ) * aspectcorrect ) * dofblur9 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.37,  0.15 ) * aspectcorrect ) * dofblur9 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.37, -0.15 ) * aspectcorrect ) * dofblur9 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.15, -0.37 ) * aspectcorrect ) * dofblur9 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.15,  0.37 ) * aspectcorrect ) * dofblur9 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.37,  0.15 ) * aspectcorrect ) * dofblur9 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.37, -0.15 ) * aspectcorrect ) * dofblur9 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.15, -0.37 ) * aspectcorrect ) * dofblur9 );",
+
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.29,  0.29 ) * aspectcorrect ) * dofblur7 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.40,  0.0  ) * aspectcorrect ) * dofblur7 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.29, -0.29 ) * aspectcorrect ) * dofblur7 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,  -0.4  ) * aspectcorrect ) * dofblur7 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.29,  0.29 ) * aspectcorrect ) * dofblur7 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.4,   0.0  ) * aspectcorrect ) * dofblur7 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.29, -0.29 ) * aspectcorrect ) * dofblur7 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,   0.4  ) * aspectcorrect ) * dofblur7 );",
+
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.29,  0.29 ) * aspectcorrect ) * dofblur4 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.4,   0.0  ) * aspectcorrect ) * dofblur4 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.29, -0.29 ) * aspectcorrect ) * dofblur4 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,  -0.4  ) * aspectcorrect ) * dofblur4 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.29,  0.29 ) * aspectcorrect ) * dofblur4 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.4,   0.0  ) * aspectcorrect ) * dofblur4 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2( -0.29, -0.29 ) * aspectcorrect ) * dofblur4 );",
+		"col += texture2D( tColor, vUv.xy + ( vec2(  0.0,   0.4  ) * aspectcorrect ) * dofblur4 );",
+
+		"gl_FragColor = col / 41.0;",
+		"gl_FragColor.a = 1.0;",
+
+	"}"
+
+	].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Depth-of-field shader using mipmaps
+	//	- from Matt Handley @applmak
+	//	- requires power-of-2 sized render target with enabled mipmaps
+	 ------------------------------------------------------------------------- */
+
+	'dofmipmap': {
+
+		uniforms: {
+
+			tColor:   { type: "t", value: 0, texture: null },
+			tDepth:   { type: "t", value: 1, texture: null },
+			focus:    { type: "f", value: 1.0 },
+			maxblur:  { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float focus;",
+			"uniform float maxblur;",
+
+			"uniform sampler2D tColor;",
+			"uniform sampler2D tDepth;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 depth = texture2D( tDepth, vUv );",
+
+				"float factor = depth.x - focus;",
+
+				"vec4 col = texture2D( tColor, vUv, 2.0 * maxblur * abs( focus - depth.x ) );",
+
+				"gl_FragColor = col;",
+				"gl_FragColor.a = 1.0;",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Sepia tone shader
+	//  - based on glfx.js sepia shader
+	//		https://github.com/evanw/glfx.js
+	 ------------------------------------------------------------------------- */
+
+	'sepia': {
+
+		uniforms: {
+
+			tDiffuse: { type: "t", value: 0, texture: null },
+			amount:   { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float amount;",
+
+			"uniform sampler2D tDiffuse;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 color = texture2D( tDiffuse, vUv );",
+				"vec3 c = color.rgb;",
+
+				"color.r = dot( c, vec3( 1.0 - 0.607 * amount, 0.769 * amount, 0.189 * amount ) );",
+				"color.g = dot( c, vec3( 0.349 * amount, 1.0 - 0.314 * amount, 0.168 * amount ) );",
+				"color.b = dot( c, vec3( 0.272 * amount, 0.534 * amount, 1.0 - 0.869 * amount ) );",
+
+				"gl_FragColor = vec4( min( vec3( 1.0 ), color.rgb ), color.a );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Dot screen shader
+	//  - based on glfx.js sepia shader
+	//		https://github.com/evanw/glfx.js
+	 ------------------------------------------------------------------------- */
+
+	'dotscreen': {
+
+		uniforms: {
+
+			tDiffuse: { type: "t", value: 0, texture: null },
+			tSize:    { type: "v2", value: new THREE.Vector2( 256, 256 ) },
+			center:   { type: "v2", value: new THREE.Vector2( 0.5, 0.5 ) },
+			angle:	  { type: "f", value: 1.57 },
+			scale:	  { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform vec2 center;",
+			"uniform float angle;",
+			"uniform float scale;",
+			"uniform vec2 tSize;",
+
+			"uniform sampler2D tDiffuse;",
+
+			"varying vec2 vUv;",
+
+			"float pattern() {",
+
+				"float s = sin( angle ), c = cos( angle );",
+
+				"vec2 tex = vUv * tSize - center;",
+				"vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * scale;",
+
+				"return ( sin( point.x ) * sin( point.y ) ) * 4.0;",
+
+			"}",
+
+			"void main() {",
+
+				"vec4 color = texture2D( tDiffuse, vUv );",
+
+				"float average = ( color.r + color.g + color.b ) / 3.0;",
+
+				"gl_FragColor = vec4( vec3( average * 10.0 - 5.0 + pattern() ), color.a );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* ------------------------------------------------------------------------------------------------
+	//	Vignette shader
+	//	- based on PaintEffect postprocess from ro.me
+	//		http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js
+	 ------------------------------------------------------------------------------------------------ */
+
+	'vignette': {
+
+		uniforms: {
+
+			tDiffuse: { type: "t", value: 0, texture: null },
+			offset:   { type: "f", value: 1.0 },
+			darkness: { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float offset;",
+			"uniform float darkness;",
+
+			"uniform sampler2D tDiffuse;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				// Eskil's vignette
+
+				"vec4 texel = texture2D( tDiffuse, vUv );",
+				"vec2 uv = ( vUv - vec2( 0.5 ) ) * vec2( offset );",
+				"gl_FragColor = vec4( mix( texel.rgb, vec3( 1.0 - darkness ), dot( uv, uv ) ), texel.a );",
+
+				/*
+				// alternative version from glfx.js
+				// this one makes more "dusty" look (as opposed to "burned")
+
+				"vec4 color = texture2D( tDiffuse, vUv );",
+				"float dist = distance( vUv, vec2( 0.5 ) );",
+				"color.rgb *= smoothstep( 0.8, offset * 0.799, dist *( darkness + offset ) );",
+				"gl_FragColor = color;",
+				*/
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Bleach bypass shader [http://en.wikipedia.org/wiki/Bleach_bypass]
+	//	- based on Nvidia example
+	//		http://developer.download.nvidia.com/shaderlibrary/webpages/shader_library.html#post_bleach_bypass
+	 ------------------------------------------------------------------------- */
+
+	'bleachbypass': {
+
+		uniforms: {
+
+			tDiffuse: { type: "t", value: 0, texture: null },
+			opacity:  { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float opacity;",
+
+			"uniform sampler2D tDiffuse;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 base = texture2D( tDiffuse, vUv );",
+
+				"vec3 lumCoeff = vec3( 0.25, 0.65, 0.1 );",
+				"float lum = dot( lumCoeff, base.rgb );",
+				"vec3 blend = vec3( lum );",
+
+				"float L = min( 1.0, max( 0.0, 10.0 * ( lum - 0.45 ) ) );",
+
+				"vec3 result1 = 2.0 * base.rgb * blend;",
+				"vec3 result2 = 1.0 - 2.0 * ( 1.0 - blend ) * ( 1.0 - base.rgb );",
+
+				"vec3 newColor = mix( result1, result2, L );",
+
+				"float A2 = opacity * base.a;",
+				"vec3 mixRGB = A2 * newColor.rgb;",
+				"mixRGB += ( ( 1.0 - A2 ) * base.rgb );",
+
+				"gl_FragColor = vec4( mixRGB, base.a );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* --------------------------------------------------------------------------------------------------
+	//	Focus shader
+	//	- based on PaintEffect postprocess from ro.me
+	//		http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js
+	 -------------------------------------------------------------------------------------------------- */
+
+	'focus': {
+
+		uniforms : {
+
+			"tDiffuse": 		{ type: "t", value: 0, texture: null },
+			"screenWidth": 		{ type: "f", value: 1024 },
+			"screenHeight": 	{ type: "f", value: 1024 },
+			"sampleDistance": 	{ type: "f", value: 0.94 },
+			"waveFactor": 		{ type: "f", value: 0.00125 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float screenWidth;",
+			"uniform float screenHeight;",
+			"uniform float sampleDistance;",
+			"uniform float waveFactor;",
+
+			"uniform sampler2D tDiffuse;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 color, org, tmp, add;",
+				"float sample_dist, f;",
+				"vec2 vin;",
+				"vec2 uv = vUv;",
+
+				"add += color = org = texture2D( tDiffuse, uv );",
+
+				"vin = ( uv - vec2( 0.5 ) ) * vec2( 1.4 );",
+				"sample_dist = dot( vin, vin ) * 2.0;",
+
+				"f = ( waveFactor * 100.0 + sample_dist ) * sampleDistance * 4.0;",
+
+				"vec2 sampleSize = vec2(  1.0 / screenWidth, 1.0 / screenHeight ) * vec2( f );",
+
+				"add += tmp = texture2D( tDiffuse, uv + vec2( 0.111964, 0.993712 ) * sampleSize );",
+				"if( tmp.b < color.b ) color = tmp;",
+
+				"add += tmp = texture2D( tDiffuse, uv + vec2( 0.846724, 0.532032 ) * sampleSize );",
+				"if( tmp.b < color.b ) color = tmp;",
+
+				"add += tmp = texture2D( tDiffuse, uv + vec2( 0.943883, -0.330279 ) * sampleSize );",
+				"if( tmp.b < color.b ) color = tmp;",
+
+				"add += tmp = texture2D( tDiffuse, uv + vec2( 0.330279, -0.943883 ) * sampleSize );",
+				"if( tmp.b < color.b ) color = tmp;",
+
+				"add += tmp = texture2D( tDiffuse, uv + vec2( -0.532032, -0.846724 ) * sampleSize );",
+				"if( tmp.b < color.b ) color = tmp;",
+
+				"add += tmp = texture2D( tDiffuse, uv + vec2( -0.993712, -0.111964 ) * sampleSize );",
+				"if( tmp.b < color.b ) color = tmp;",
+
+				"add += tmp = texture2D( tDiffuse, uv + vec2( -0.707107, 0.707107 ) * sampleSize );",
+				"if( tmp.b < color.b ) color = tmp;",
+
+				"color = color * vec4( 2.0 ) - ( add / vec4( 8.0 ) );",
+				"color = color + ( add / vec4( 8.0 ) - color ) * ( vec4( 1.0 ) - vec4( sample_dist * 0.5 ) );",
+
+				"gl_FragColor = vec4( color.rgb * color.rgb * vec3( 0.95 ) + color.rgb, 1.0 );",
+
+			"}"
+
+
+		].join("\n")
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Triangle blur shader
+	//  - based on glfx.js triangle blur shader
+	//		https://github.com/evanw/glfx.js
+
+	// 	A basic blur filter, which convolves the image with a
+	// 	pyramid filter. The pyramid filter is separable and is applied as two
+	//  perpendicular triangle filters.
+	 ------------------------------------------------------------------------- */
+
+	'triangleBlur': {
+
+
+		uniforms : {
+
+			"texture": 	{ type: "t", value: 0, texture: null },
+			"delta": 	{ type: "v2", value:new THREE.Vector2( 1, 1 )  }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+		"#define ITERATIONS 10.0",
+
+		"uniform sampler2D texture;",
+		"uniform vec2 delta;",
+
+		"varying vec2 vUv;",
+
+		"float random( vec3 scale, float seed ) {",
+
+			// use the fragment position for a different seed per-pixel
+
+			"return fract( sin( dot( gl_FragCoord.xyz + seed, scale ) ) * 43758.5453 + seed );",
+
+		"}",
+
+		"void main() {",
+
+			"vec4 color = vec4( 0.0 );",
+
+			"float total = 0.0;",
+
+			// randomize the lookup values to hide the fixed number of samples
+
+			"float offset = random( vec3( 12.9898, 78.233, 151.7182 ), 0.0 );",
+
+			"for ( float t = -ITERATIONS; t <= ITERATIONS; t ++ ) {",
+
+				"float percent = ( t + offset - 0.5 ) / ITERATIONS;",
+				"float weight = 1.0 - abs( percent );",
+
+				"color += texture2D( texture, vUv + delta * percent ) * weight;",
+				"total += weight;",
+
+			"}",
+
+			"gl_FragColor = color / total;",
+
+		"}",
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Simple test shader
+	 ------------------------------------------------------------------------- */
+
+	'basic': {
+
+		uniforms: {},
+
+		vertexShader: [
+
+			"void main() {",
+
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"void main() {",
+
+				"gl_FragColor = vec4( 1.0, 0.0, 0.0, 0.5 );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* --------------------------------------------------------------------------------------------------
+	//	Two pass Gaussian blur filter (horizontal and vertical blur shaders)
+	//	- described in http://www.gamerendering.com/2008/10/11/gaussian-blur-filter-shader/
+	//	  and used in http://www.cake23.de/traveling-wavefronts-lit-up.html
+	//
+	//	- 9 samples per pass
+	//	- standard deviation 2.7
+	//	- "h" and "v" parameters should be set to "1 / width" and "1 / height"
+	 -------------------------------------------------------------------------------------------------- */
+
+	'horizontalBlur': {
+
+		uniforms: {
+
+			"tDiffuse": { type: "t", value: 0, texture: null },
+			"h": 		{ type: "f", value: 1.0 / 512.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform sampler2D tDiffuse;",
+			"uniform float h;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 sum = vec4( 0.0 );",
+
+				"sum += texture2D( tDiffuse, vec2( vUv.x - 4.0 * h, vUv.y ) ) * 0.051;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x - 3.0 * h, vUv.y ) ) * 0.0918;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x - 2.0 * h, vUv.y ) ) * 0.12245;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x - 1.0 * h, vUv.y ) ) * 0.1531;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, 		  	vUv.y ) ) * 0.1633;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x + 1.0 * h, vUv.y ) ) * 0.1531;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x + 2.0 * h, vUv.y ) ) * 0.12245;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x + 3.0 * h, vUv.y ) ) * 0.0918;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x + 4.0 * h, vUv.y ) ) * 0.051;",
+
+				"gl_FragColor = sum;",
+
+			"}"
+
+
+		].join("\n")
+
+	},
+
+	'verticalBlur': {
+
+		uniforms: {
+
+			"tDiffuse": { type: "t", value: 0, texture: null },
+			"v": 		{ type: "f", value: 1.0 / 512.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform sampler2D tDiffuse;",
+			"uniform float v;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 sum = vec4( 0.0 );",
+
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 4.0 * v ) ) * 0.051;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 3.0 * v ) ) * 0.0918;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 2.0 * v ) ) * 0.12245;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 1.0 * v ) ) * 0.1531;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y			  ) ) * 0.1633;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 1.0 * v ) ) * 0.1531;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 2.0 * v ) ) * 0.12245;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 3.0 * v ) ) * 0.0918;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 4.0 * v ) ) * 0.051;",
+
+				"gl_FragColor = sum;",
+
+			"}"
+
+
+		].join("\n")
+
+	},
+
+	/* --------------------------------------------------------------------------------------------------
+	//	Simple fake tilt-shift effect, modulating two pass Gaussian blur (see above) by vertical position
+	//
+	//	- 9 samples per pass
+	//	- standard deviation 2.7
+	//	- "h" and "v" parameters should be set to "1 / width" and "1 / height"
+	//	- "r" parameter control where "focused" horizontal line lies
+	 -------------------------------------------------------------------------------------------------- */
+
+	'horizontalTiltShift': {
+
+		uniforms: {
+
+			"tDiffuse": { type: "t", value: 0, texture: null },
+			"h": 		{ type: "f", value: 1.0 / 512.0 },
+			"r": 		{ type: "f", value: 0.35 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform sampler2D tDiffuse;",
+			"uniform float h;",
+			"uniform float r;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 sum = vec4( 0.0 );",
+
+				"float hh = h * abs( r - vUv.y );",
+
+				"sum += texture2D( tDiffuse, vec2( vUv.x - 4.0 * hh, vUv.y ) ) * 0.051;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x - 3.0 * hh, vUv.y ) ) * 0.0918;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x - 2.0 * hh, vUv.y ) ) * 0.12245;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x - 1.0 * hh, vUv.y ) ) * 0.1531;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, 		  	 vUv.y ) ) * 0.1633;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x + 1.0 * hh, vUv.y ) ) * 0.1531;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x + 2.0 * hh, vUv.y ) ) * 0.12245;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x + 3.0 * hh, vUv.y ) ) * 0.0918;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x + 4.0 * hh, vUv.y ) ) * 0.051;",
+
+				"gl_FragColor = sum;",
+
+			"}"
+
+
+		].join("\n")
+
+	},
+
+	'verticalTiltShift': {
+
+		uniforms: {
+
+			"tDiffuse": { type: "t", value: 0, texture: null },
+			"v": 		{ type: "f", value: 1.0 / 512.0 },
+			"r": 		{ type: "f", value: 0.35 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform sampler2D tDiffuse;",
+			"uniform float v;",
+			"uniform float r;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 sum = vec4( 0.0 );",
+
+				"float vv = v * abs( r - vUv.y );",
+
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 4.0 * vv ) ) * 0.051;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 3.0 * vv ) ) * 0.0918;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 2.0 * vv ) ) * 0.12245;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 1.0 * vv ) ) * 0.1531;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y			   ) ) * 0.1633;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 1.0 * vv ) ) * 0.1531;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 2.0 * vv ) ) * 0.12245;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 3.0 * vv ) ) * 0.0918;",
+				"sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 4.0 * vv ) ) * 0.051;",
+
+				"gl_FragColor = sum;",
+
+			"}"
+
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Blend two textures
+	 ------------------------------------------------------------------------- */
+
+	'blend': {
+
+		uniforms: {
+
+			tDiffuse1: { type: "t", value: 0, texture: null },
+			tDiffuse2: { type: "t", value: 1, texture: null },
+			mixRatio:  { type: "f", value: 0.5 },
+			opacity:   { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float opacity;",
+			"uniform float mixRatio;",
+
+			"uniform sampler2D tDiffuse1;",
+			"uniform sampler2D tDiffuse2;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 texel1 = texture2D( tDiffuse1, vUv );",
+				"vec4 texel2 = texture2D( tDiffuse2, vUv );",
+				"gl_FragColor = opacity * mix( texel1, texel2, mixRatio );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	NVIDIA FXAA by Timothy Lottes
+	//		http://timothylottes.blogspot.com/2011/06/fxaa3-source-released.html
+	//	- WebGL port by @supereggbert
+	//		http://www.glge.org/demos/fxaa/
+	 ------------------------------------------------------------------------- */
+
+	'fxaa': {
+
+		uniforms: {
+
+			"tDiffuse": 	{ type: "t", value: 0, texture: null },
+			"resolution": 	{ type: "v2", value: new THREE.Vector2( 1 / 1024, 1 / 512 )  }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform sampler2D tDiffuse;",
+			"uniform vec2 resolution;",
+
+			"varying vec2 vUv;",
+
+			"#define FXAA_REDUCE_MIN   (1.0/128.0)",
+			"#define FXAA_REDUCE_MUL   (1.0/8.0)",
+			"#define FXAA_SPAN_MAX     8.0",
+
+			"void main() {",
+
+				"vec3 rgbNW = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( -1.0, -1.0 ) ) * resolution ).xyz;",
+				"vec3 rgbNE = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( 1.0, -1.0 ) ) * resolution ).xyz;",
+				"vec3 rgbSW = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( -1.0, 1.0 ) ) * resolution ).xyz;",
+				"vec3 rgbSE = texture2D( tDiffuse, ( gl_FragCoord.xy + vec2( 1.0, 1.0 ) ) * resolution ).xyz;",
+				"vec3 rgbM  = texture2D( tDiffuse,  gl_FragCoord.xy  * resolution ).xyz;",
+
+				"vec3 luma = vec3( 0.299, 0.587, 0.114 );",
+
+				"float lumaNW = dot( rgbNW, luma );",
+				"float lumaNE = dot( rgbNE, luma );",
+				"float lumaSW = dot( rgbSW, luma );",
+				"float lumaSE = dot( rgbSE, luma );",
+				"float lumaM  = dot( rgbM,  luma );",
+				"float lumaMin = min( lumaM, min( min( lumaNW, lumaNE ), min( lumaSW, lumaSE ) ) );",
+				"float lumaMax = max( lumaM, max( max( lumaNW, lumaNE) , max( lumaSW, lumaSE ) ) );",
+
+				"vec2 dir;",
+				"dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));",
+				"dir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));",
+
+				"float dirReduce = max( ( lumaNW + lumaNE + lumaSW + lumaSE ) * ( 0.25 * FXAA_REDUCE_MUL ), FXAA_REDUCE_MIN );",
+
+				"float rcpDirMin = 1.0 / ( min( abs( dir.x ), abs( dir.y ) ) + dirReduce );",
+				"dir = min( vec2( FXAA_SPAN_MAX,  FXAA_SPAN_MAX),",
+					  "max( vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX),",
+							"dir * rcpDirMin)) * resolution;",
+
+				"vec3 rgbA = 0.5 * (",
+					"texture2D( tDiffuse, gl_FragCoord.xy  * resolution + dir * ( 1.0 / 3.0 - 0.5 ) ).xyz +",
+					"texture2D( tDiffuse, gl_FragCoord.xy  * resolution + dir * ( 2.0 / 3.0 - 0.5 ) ).xyz );",
+
+				"vec3 rgbB = rgbA * 0.5 + 0.25 * (",
+					"texture2D( tDiffuse, gl_FragCoord.xy  * resolution + dir * -0.5 ).xyz +",
+					"texture2D( tDiffuse, gl_FragCoord.xy  * resolution + dir * 0.5 ).xyz );",
+
+				"float lumaB = dot( rgbB, luma );",
+
+				"if ( ( lumaB < lumaMin ) || ( lumaB > lumaMax ) ) {",
+
+					"gl_FragColor = vec4( rgbA, 1.0 );",
+
+				"} else {",
+
+					"gl_FragColor = vec4( rgbB, 1.0 );",
+
+				"}",
+
+			"}",
+
+		].join("\n"),
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Luminosity
+	//	http://en.wikipedia.org/wiki/Luminosity
+	 ------------------------------------------------------------------------- */
+
+	'luminosity': {
+
+		uniforms: {
+
+			"tDiffuse": 	{ type: "t", value: 0, texture: null }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform sampler2D tDiffuse;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 texel = texture2D( tDiffuse, vUv );",
+
+				"vec3 luma = vec3( 0.299, 0.587, 0.114 );",
+
+				"float v = dot( texel.xyz, luma );",
+
+				"gl_FragColor = vec4( v, v, v, texel.w );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Color correction
+	 ------------------------------------------------------------------------- */
+
+	'colorCorrection': {
+
+		uniforms: {
+
+			"tDiffuse" : 	{ type: "t", value: 0, texture: null },
+			"powRGB" :		{ type: "v3", value: new THREE.Vector3( 2, 2, 2 ) },
+			"mulRGB" :		{ type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform sampler2D tDiffuse;",
+			"uniform vec3 powRGB;",
+			"uniform vec3 mulRGB;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"gl_FragColor = texture2D( tDiffuse, vUv );",
+				"gl_FragColor.rgb = mulRGB * pow( gl_FragColor.rgb, powRGB );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Normal map shader
+	//	- compute normals from heightmap
+	 ------------------------------------------------------------------------- */
+
+	'normalmap': {
+
+		uniforms: {
+
+			"heightMap"	: { type: "t", value: 0, texture: null },
+			"resolution": { type: "v2", value: new THREE.Vector2( 512, 512 ) },
+			"scale"		: { type: "v2", value: new THREE.Vector2( 1, 1 ) },
+			"height"	: { type: "f", value: 0.05 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float height;",
+			"uniform vec2 resolution;",
+			"uniform sampler2D heightMap;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"float val = texture2D( heightMap, vUv ).x;",
+
+				"float valU = texture2D( heightMap, vUv + vec2( 1.0 / resolution.x, 0.0 ) ).x;",
+				"float valV = texture2D( heightMap, vUv + vec2( 0.0, 1.0 / resolution.y ) ).x;",
+
+				"gl_FragColor = vec4( ( 0.5 * normalize( vec3( val - valU, val - valV, height  ) ) + 0.5 ), 1.0 );",
+
+			"}",
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Screen-space ambient occlusion shader
+	//	- ported from
+	//		SSAO GLSL shader v1.2
+	//		assembled by Martins Upitis (martinsh) (http://devlog-martinsh.blogspot.com)
+	//		original technique is made by ArKano22 (http://www.gamedev.net/topic/550699-ssao-no-halo-artifacts/)
+	//	- modifications
+	//		- modified to use RGBA packed depth texture (use clear color 1,1,1,1 for depth pass)
+	//		- made fog more compatible with three.js linear fog
+	//		- refactoring and optimizations
+	 ------------------------------------------------------------------------- */
+
+	'ssao': {
+
+		uniforms: {
+
+			"tDiffuse": 	{ type: "t", value: 0, texture: null },
+			"tDepth":   	{ type: "t", value: 1, texture: null },
+			"size": 		{ type: "v2", value: new THREE.Vector2( 512, 512 ) },
+			"cameraNear":	{ type: "f", value: 1 },
+			"cameraFar":	{ type: "f", value: 100 },
+			"fogNear":		{ type: "f", value: 5 },
+			"fogFar":		{ type: "f", value: 100 },
+			"fogEnabled":	{ type: "i", value: 0 },
+			"aoClamp":		{ type: "f", value: 0.3 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float cameraNear;",
+			"uniform float cameraFar;",
+
+			"uniform float fogNear;",
+			"uniform float fogFar;",
+
+			"uniform bool fogEnabled;",
+
+			"uniform vec2 size;",		// texture width, height
+			"uniform float aoClamp;", 	// depth clamp - reduces haloing at screen edges
+
+			"uniform sampler2D tDiffuse;",
+			"uniform sampler2D tDepth;",
+
+			"varying vec2 vUv;",
+
+			//"#define PI 3.14159265",
+			"#define DL 2.399963229728653", // PI * ( 3.0 - sqrt( 5.0 ) )
+			"#define EULER 2.718281828459045",
+
+			// helpers
+
+			"float width = size.x;", 	// texture width
+			"float height = size.y;", 	// texture height
+
+			"float cameraFarPlusNear = cameraFar + cameraNear;",
+			"float cameraFarMinusNear = cameraFar - cameraNear;",
+			"float cameraCoef = 2.0 * cameraNear;",
+
+			// user variables
+
+			"const int samples = 8;", 		// ao sample count
+			"const float radius = 5.0;", 	// ao radius
+
+			"const bool useNoise = false;", 		 // use noise instead of pattern for sample dithering
+			"const float noiseAmount = 0.0002;", // dithering amount
+
+			"const float diffArea = 0.4;", 		// self-shadowing reduction
+			"const float gDisplace = 0.4;", 	// gauss bell center
+
+			"const bool onlyAO = false;", 		// use only ambient occlusion pass?
+			"const float lumInfluence = 0.3;",  // how much luminance affects occlusion
+
+			// RGBA depth
+
+			"float unpackDepth( const in vec4 rgba_depth ) {",
+
+				"const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );",
+				"float depth = dot( rgba_depth, bit_shift );",
+				"return depth;",
+
+			"}",
+
+			// generating noise / pattern texture for dithering
+
+			"vec2 rand( const vec2 coord ) {",
+
+				"vec2 noise;",
+
+				"if ( useNoise ) {",
+
+					"float nx = dot ( coord, vec2( 12.9898, 78.233 ) );",
+					"float ny = dot ( coord, vec2( 12.9898, 78.233 ) * 2.0 );",
+
+					"noise = clamp( fract ( 43758.5453 * sin( vec2( nx, ny ) ) ), 0.0, 1.0 );",
+
+				"} else {",
+
+					"float ff = fract( 1.0 - coord.s * ( width / 2.0 ) );",
+					"float gg = fract( coord.t * ( height / 2.0 ) );",
+
+					"noise = vec2( 0.25, 0.75 ) * vec2( ff ) + vec2( 0.75, 0.25 ) * gg;",
+
+				"}",
+
+				"return ( noise * 2.0  - 1.0 ) * noiseAmount;",
+
+			"}",
+
+			"float doFog() {",
+
+				"float zdepth = unpackDepth( texture2D( tDepth, vUv ) );",
+				"float depth = -cameraFar * cameraNear / ( zdepth * cameraFarMinusNear - cameraFar );",
+
+				"return smoothstep( fogNear, fogFar, depth );",
+
+			"}",
+
+			"float readDepth( const in vec2 coord ) {",
+
+				//"return ( 2.0 * cameraNear ) / ( cameraFar + cameraNear - unpackDepth( texture2D( tDepth, coord ) ) * ( cameraFar - cameraNear ) );",
+				"return cameraCoef / ( cameraFarPlusNear - unpackDepth( texture2D( tDepth, coord ) ) * cameraFarMinusNear );",
+
+
+			"}",
+
+			"float compareDepths( const in float depth1, const in float depth2, inout int far ) {",
+
+				"float garea = 2.0;", 						 // gauss bell width
+				"float diff = ( depth1 - depth2 ) * 100.0;", // depth difference (0-100)
+
+				// reduce left bell width to avoid self-shadowing
+
+				"if ( diff < gDisplace ) {",
+
+					"garea = diffArea;",
+
+				"} else {",
+
+					"far = 1;",
+
+				"}",
+
+				"float dd = diff - gDisplace;",
+				"float gauss = pow( EULER, -2.0 * dd * dd / ( garea * garea ) );",
+				"return gauss;",
+
+			"}",
+
+			"float calcAO( float depth, float dw, float dh ) {",
+
+				"float dd = radius - depth * radius;",
+				"vec2 vv = vec2( dw, dh );",
+
+				"vec2 coord1 = vUv + dd * vv;",
+				"vec2 coord2 = vUv - dd * vv;",
+
+				"float temp1 = 0.0;",
+				"float temp2 = 0.0;",
+
+				"int far = 0;",
+				"temp1 = compareDepths( depth, readDepth( coord1 ), far );",
+
+				// DEPTH EXTRAPOLATION
+
+				"if ( far > 0 ) {",
+
+					"temp2 = compareDepths( readDepth( coord2 ), depth, far );",
+					"temp1 += ( 1.0 - temp1 ) * temp2;",
+
+				"}",
+
+				"return temp1;",
+
+			"}",
+
+			"void main() {",
+
+				"vec2 noise = rand( vUv );",
+				"float depth = readDepth( vUv );",
+
+				"float tt = clamp( depth, aoClamp, 1.0 );",
+
+				"float w = ( 1.0 / width )  / tt + ( noise.x * ( 1.0 - noise.x ) );",
+				"float h = ( 1.0 / height ) / tt + ( noise.y * ( 1.0 - noise.y ) );",
+
+				"float pw;",
+				"float ph;",
+
+				"float ao;",
+
+				"float dz = 1.0 / float( samples );",
+				"float z = 1.0 - dz / 2.0;",
+				"float l = 0.0;",
+
+				"for ( int i = 0; i <= samples; i ++ ) {",
+
+					"float r = sqrt( 1.0 - z );",
+
+					"pw = cos( l ) * r;",
+					"ph = sin( l ) * r;",
+					"ao += calcAO( depth, pw * w, ph * h );",
+					"z = z - dz;",
+					"l = l + DL;",
+
+				"}",
+
+				"ao /= float( samples );",
+				"ao = 1.0 - ao;",
+
+				"if ( fogEnabled ) {",
+
+					"ao = mix( ao, 1.0, doFog() );",
+
+				"}",
+
+				"vec3 color = texture2D( tDiffuse, vUv ).rgb;",
+
+				"vec3 lumcoeff = vec3( 0.299, 0.587, 0.114 );",
+				"float lum = dot( color.rgb, lumcoeff );",
+				"vec3 luminance = vec3( lum );",
+
+				"vec3 final = vec3( color * mix( vec3( ao ), vec3( 1.0 ), luminance * lumInfluence ) );", // mix( color * ao, white, luminance )
+
+				"if ( onlyAO ) {",
+
+					"final = vec3( mix( vec3( ao ), vec3( 1.0 ), luminance * lumInfluence ) );", // ambient occlusion only
+
+				"}",
+
+				"gl_FragColor = vec4( final, 1.0 );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Colorify shader
+	 ------------------------------------------------------------------------- */
+
+	'colorify': {
+
+		uniforms: {
+
+			tDiffuse: { type: "t", value: 0, texture: null },
+			color:    { type: "c", value: new THREE.Color( 0xffffff ) }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform vec3 color;",
+			"uniform sampler2D tDiffuse;",
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vec4 texel = texture2D( tDiffuse, vUv );",
+
+				"vec3 luma = vec3( 0.299, 0.587, 0.114 );",
+				"float v = dot( texel.xyz, luma );",
+
+				"gl_FragColor = vec4( v * color, texel.w );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* -------------------------------------------------------------------------
+	//	Unpack RGBA depth shader
+	//	- show RGBA encoded depth as monochrome color
+	 ------------------------------------------------------------------------- */
+
+	'unpackDepthRGBA': {
+
+		uniforms: {
+
+			tDiffuse: { type: "t", value: 0, texture: null },
+			opacity:  { type: "f", value: 1.0 }
+
+		},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"uniform float opacity;",
+
+			"uniform sampler2D tDiffuse;",
+
+			"varying vec2 vUv;",
+
+			// RGBA depth
+
+			"float unpackDepth( const in vec4 rgba_depth ) {",
+
+				"const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );",
+				"float depth = dot( rgba_depth, bit_shift );",
+				"return depth;",
+
+			"}",
+
+			"void main() {",
+
+				"float depth = 1.0 - unpackDepth( texture2D( tDiffuse, vUv ) );",
+				"gl_FragColor = opacity * vec4( vec3( depth ), 1.0 );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	// METHODS
+
+	buildKernel: function( sigma ) {
+
+		// We lop off the sqrt(2 * pi) * sigma term, since we're going to normalize anyway.
+
+		function gauss( x, sigma ) {
+
+			return Math.exp( - ( x * x ) / ( 2.0 * sigma * sigma ) );
+
+		}
+
+		var i, values, sum, halfWidth, kMaxKernelSize = 25, kernelSize = 2 * Math.ceil( sigma * 3.0 ) + 1;
+
+		if ( kernelSize > kMaxKernelSize ) kernelSize = kMaxKernelSize;
+		halfWidth = ( kernelSize - 1 ) * 0.5
+
+		values = new Array( kernelSize );
+		sum = 0.0;
+		for ( i = 0; i < kernelSize; ++i ) {
+
+			values[ i ] = gauss( i - halfWidth, sigma );
+			sum += values[ i ];
+
+		}
+
+		// normalize the kernel
+
+		for ( i = 0; i < kernelSize; ++i ) values[ i ] /= sum;
+
+		return values;
+
+	}
+
+};
diff --git a/emperor/support_files/js/js/ShaderSkin.js b/emperor/support_files/js/js/ShaderSkin.js
new file mode 100644
index 0000000..1858ff8
--- /dev/null
+++ b/emperor/support_files/js/js/ShaderSkin.js
@@ -0,0 +1,752 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ */
+
+
+THREE.ShaderSkin = {
+
+	/* ------------------------------------------------------------------------------------------
+	//	Simple skin shader
+	//		- per-pixel Blinn-Phong diffuse term mixed with half-Lambert wrap-around term (per color component)
+	//		- physically based specular term (Kelemen/Szirmay-Kalos specular reflectance)
+	//
+	//		- diffuse map
+	//		- point and directional lights (use with "lights: true" material option)
+	//		- fog (use with "fog: true" material option)
+	//		- shadow maps
+	//
+	// ------------------------------------------------------------------------------------------ */
+
+	'skinSimple' : {
+
+		uniforms: THREE.UniformsUtils.merge( [
+
+			THREE.UniformsLib[ "fog" ],
+			THREE.UniformsLib[ "lights" ],
+			THREE.UniformsLib[ "shadowmap" ],
+
+			{
+
+			"tDiffuse"	: { type: "t", value: 0, texture: null },
+			"tBeckmann"	: { type: "t", value: 1, texture: null },
+
+			"uDiffuseColor":  { type: "c", value: new THREE.Color( 0xeeeeee ) },
+			"uSpecularColor": { type: "c", value: new THREE.Color( 0x111111 ) },
+			"uAmbientColor":  { type: "c", value: new THREE.Color( 0x050505 ) },
+			"uOpacity": 	  { type: "f", value: 1 },
+
+			"uRoughness": 	  		{ type: "f", value: 0.15 },
+			"uSpecularBrightness": 	{ type: "f", value: 0.75 },
+
+			"uWrapRGB":	{ type: "v3", value: new THREE.Vector3( 0.75, 0.375, 0.1875 ) }
+
+			}
+
+		] ),
+
+		fragmentShader: [
+
+			"uniform vec3 uAmbientColor;",
+			"uniform vec3 uDiffuseColor;",
+			"uniform vec3 uSpecularColor;",
+			"uniform float uOpacity;",
+
+			"uniform float uRoughness;",
+			"uniform float uSpecularBrightness;",
+
+			"uniform vec3 uWrapRGB;",
+
+			"uniform sampler2D tDiffuse;",
+			"uniform sampler2D tBeckmann;",
+
+			"varying vec3 vNormal;",
+			"varying vec2 vUv;",
+
+			"uniform vec3 ambientLightColor;",
+
+			"#if MAX_DIR_LIGHTS > 0",
+
+				"uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+				"uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+
+			"#endif",
+
+			"#if MAX_POINT_LIGHTS > 0",
+
+				"uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+				"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+				"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+			"#endif",
+
+			"varying vec3 vViewPosition;",
+
+			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+			THREE.ShaderChunk[ "fog_pars_fragment" ],
+
+			// Fresnel term
+
+			"float fresnelReflectance( vec3 H, vec3 V, float F0 ) {",
+
+				"float base = 1.0 - dot( V, H );",
+				"float exponential = pow( base, 5.0 );",
+
+				"return exponential + F0 * ( 1.0 - exponential );",
+
+			"}",
+
+			// Kelemen/Szirmay-Kalos specular BRDF
+
+			"float KS_Skin_Specular( vec3 N,", 		// Bumped surface normal
+									"vec3 L,", 		// Points to light
+									"vec3 V,", 		// Points to eye
+									"float m,",  	// Roughness
+									"float rho_s", 	// Specular brightness
+									") {",
+
+				"float result = 0.0;",
+				"float ndotl = dot( N, L );",
+
+				"if( ndotl > 0.0 ) {",
+
+					"vec3 h = L + V;", // Unnormalized half-way vector
+					"vec3 H = normalize( h );",
+
+					"float ndoth = dot( N, H );",
+
+					"float PH = pow( 2.0 * texture2D( tBeckmann, vec2( ndoth, m ) ).x, 10.0 );",
+
+					"float F = fresnelReflectance( H, V, 0.028 );",
+					"float frSpec = max( PH * F / dot( h, h ), 0.0 );",
+
+					"result = ndotl * rho_s * frSpec;", // BRDF * dot(N,L) * rho_s
+
+				"}",
+
+				"return result;",
+
+			"}",
+
+			"void main() {",
+
+				"gl_FragColor = vec4( vec3( 1.0 ), uOpacity );",
+
+				"vec4 colDiffuse = texture2D( tDiffuse, vUv );",
+				"colDiffuse.rgb *= colDiffuse.rgb;",
+
+				"gl_FragColor = gl_FragColor * colDiffuse;",
+
+				"vec3 normal = normalize( vNormal );",
+				"vec3 viewPosition = normalize( vViewPosition );",
+
+				// point lights
+
+				"vec3 specularTotal = vec3( 0.0 );",
+
+				"#if MAX_POINT_LIGHTS > 0",
+
+					"vec3 pointTotal = vec3( 0.0 );",
+
+					"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+						"vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+
+						"vec3 lVector = lPosition.xyz + vViewPosition.xyz;",
+
+						"float lDistance = 1.0;",
+
+						"if ( pointLightDistance[ i ] > 0.0 )",
+							"lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+						"lVector = normalize( lVector );",
+
+						"float pointDiffuseWeightFull = max( dot( normal, lVector ), 0.0 );",
+						"float pointDiffuseWeightHalf = max( 0.5 * dot( normal, lVector ) + 0.5, 0.0 );",
+						"vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), uWrapRGB );",
+
+						"float pointSpecularWeight = KS_Skin_Specular( normal, lVector, viewPosition, uRoughness, uSpecularBrightness );",
+
+						"pointTotal    += lDistance * uDiffuseColor * pointLightColor[ i ] * pointDiffuseWeight;",
+						"specularTotal += lDistance * uSpecularColor * pointLightColor[ i ] * pointSpecularWeight;",
+
+					"}",
+
+				"#endif",
+
+				// directional lights
+
+				"#if MAX_DIR_LIGHTS > 0",
+
+					"vec3 dirTotal = vec3( 0.0 );",
+
+					"for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
+
+						"vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+
+						"vec3 dirVector = normalize( lDirection.xyz );",
+
+						"float dirDiffuseWeightFull = max( dot( normal, dirVector ), 0.0 );",
+						"float dirDiffuseWeightHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );",
+						"vec3 dirDiffuseWeight = mix( vec3 ( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), uWrapRGB );",
+
+						"float dirSpecularWeight =  KS_Skin_Specular( normal, dirVector, viewPosition, uRoughness, uSpecularBrightness );",
+
+						"dirTotal 	   += uDiffuseColor * directionalLightColor[ i ] * dirDiffuseWeight;",
+						"specularTotal += uSpecularColor * directionalLightColor[ i ] * dirSpecularWeight;",
+
+					"}",
+
+				"#endif",
+
+				// all lights contribution summation
+
+				"vec3 totalLight = vec3( 0.0 );",
+
+				"#if MAX_DIR_LIGHTS > 0",
+					"totalLight += dirTotal;",
+				"#endif",
+
+				"#if MAX_POINT_LIGHTS > 0",
+					"totalLight += pointTotal;",
+				"#endif",
+
+				"gl_FragColor.xyz = gl_FragColor.xyz * ( totalLight + ambientLightColor * uAmbientColor ) + specularTotal;",
+
+				THREE.ShaderChunk[ "shadowmap_fragment" ],
+				THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+				THREE.ShaderChunk[ "fog_fragment" ],
+
+			"}"
+
+		].join("\n"),
+
+		vertexShader: [
+
+			"varying vec3 vNormal;",
+			"varying vec2 vUv;",
+
+			"varying vec3 vViewPosition;",
+
+			THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+			"void main() {",
+
+				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+				"vViewPosition = -mvPosition.xyz;",
+
+				"vNormal = normalMatrix * normal;",
+
+				"vUv = uv;",
+
+				"gl_Position = projectionMatrix * mvPosition;",
+
+				THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+			"}"
+
+		].join( "\n" )
+
+	},
+
+	/* ------------------------------------------------------------------------------------------
+	//	Skin shader
+	//		- Blinn-Phong diffuse term (using normal + diffuse maps)
+	//		- subsurface scattering approximation by four blur layers
+	//		- physically based specular term (Kelemen/Szirmay-Kalos specular reflectance)
+	//
+	//		- point and directional lights (use with "lights: true" material option)
+	//
+	//		- based on Nvidia Advanced Skin Rendering GDC 2007 presentation
+	//		  and GPU Gems 3 Chapter 14. Advanced Techniques for Realistic Real-Time Skin Rendering
+	//
+	//			http://developer.download.nvidia.com/presentations/2007/gdc/Advanced_Skin.pdf
+	//			http://http.developer.nvidia.com/GPUGems3/gpugems3_ch14.html
+	// ------------------------------------------------------------------------------------------ */
+
+	'skin' : {
+
+		uniforms: THREE.UniformsUtils.merge( [
+
+			THREE.UniformsLib[ "fog" ],
+			THREE.UniformsLib[ "lights" ],
+
+			{
+
+			"passID": { type: "i", value: 0 },
+
+			"tDiffuse"	: { type: "t", value: 0, texture: null },
+			"tNormal"	: { type: "t", value: 1, texture: null },
+
+			"tBlur1"	: { type: "t", value: 2, texture: null },
+			"tBlur2"	: { type: "t", value: 3, texture: null },
+			"tBlur3"	: { type: "t", value: 4, texture: null },
+			"tBlur4"	: { type: "t", value: 5, texture: null },
+
+			"tBeckmann"	: { type: "t", value: 6, texture: null },
+
+			"uNormalScale": { type: "f", value: 1.0 },
+
+			"uDiffuseColor":  { type: "c", value: new THREE.Color( 0xeeeeee ) },
+			"uSpecularColor": { type: "c", value: new THREE.Color( 0x111111 ) },
+			"uAmbientColor":  { type: "c", value: new THREE.Color( 0x050505 ) },
+			"uOpacity": 	  { type: "f", value: 1 },
+
+			"uRoughness": 	  		{ type: "f", value: 0.15 },
+			"uSpecularBrightness": 	{ type: "f", value: 0.75 }
+
+			}
+
+		] ),
+
+		fragmentShader: [
+
+			"uniform vec3 uAmbientColor;",
+			"uniform vec3 uDiffuseColor;",
+			"uniform vec3 uSpecularColor;",
+			"uniform float uOpacity;",
+
+			"uniform float uRoughness;",
+			"uniform float uSpecularBrightness;",
+
+			"uniform int passID;",
+
+			"uniform sampler2D tDiffuse;",
+			"uniform sampler2D tNormal;",
+
+			"uniform sampler2D tBlur1;",
+			"uniform sampler2D tBlur2;",
+			"uniform sampler2D tBlur3;",
+			"uniform sampler2D tBlur4;",
+
+			"uniform sampler2D tBeckmann;",
+
+			"uniform float uNormalScale;",
+
+			"varying vec3 vTangent;",
+			"varying vec3 vBinormal;",
+			"varying vec3 vNormal;",
+			"varying vec2 vUv;",
+
+			"uniform vec3 ambientLightColor;",
+
+			"#if MAX_DIR_LIGHTS > 0",
+				"uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+				"uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+			"#endif",
+
+			"#if MAX_POINT_LIGHTS > 0",
+				"uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+				"varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
+			"#endif",
+
+			"varying vec3 vViewPosition;",
+
+			THREE.ShaderChunk[ "fog_pars_fragment" ],
+
+			"float fresnelReflectance( vec3 H, vec3 V, float F0 ) {",
+
+				"float base = 1.0 - dot( V, H );",
+				"float exponential = pow( base, 5.0 );",
+
+				"return exponential + F0 * ( 1.0 - exponential );",
+
+			"}",
+
+			// Kelemen/Szirmay-Kalos specular BRDF
+
+			"float KS_Skin_Specular( vec3 N,", 		// Bumped surface normal
+									"vec3 L,", 		// Points to light
+									"vec3 V,", 		// Points to eye
+									"float m,",  	// Roughness
+									"float rho_s", 	// Specular brightness
+									") {",
+
+				"float result = 0.0;",
+				"float ndotl = dot( N, L );",
+
+				"if( ndotl > 0.0 ) {",
+
+					"vec3 h = L + V;", // Unnormalized half-way vector
+					"vec3 H = normalize( h );",
+
+					"float ndoth = dot( N, H );",
+
+					"float PH = pow( 2.0 * texture2D( tBeckmann, vec2( ndoth, m ) ).x, 10.0 );",
+					"float F = fresnelReflectance( H, V, 0.028 );",
+					"float frSpec = max( PH * F / dot( h, h ), 0.0 );",
+
+					"result = ndotl * rho_s * frSpec;", // BRDF * dot(N,L) * rho_s
+
+				"}",
+
+				"return result;",
+
+			"}",
+
+			"void main() {",
+
+				"gl_FragColor = vec4( 1.0 );",
+
+				"vec4 mColor = vec4( uDiffuseColor, uOpacity );",
+				"vec4 mSpecular = vec4( uSpecularColor, uOpacity );",
+
+				"vec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;",
+				"normalTex.xy *= uNormalScale;",
+				"normalTex = normalize( normalTex );",
+
+				"vec4 colDiffuse = texture2D( tDiffuse, vUv );",
+				"colDiffuse *= colDiffuse;",
+
+				"gl_FragColor = gl_FragColor * colDiffuse;",
+
+				"mat3 tsb = mat3( vTangent, vBinormal, vNormal );",
+				"vec3 finalNormal = tsb * normalTex;",
+
+				"vec3 normal = normalize( finalNormal );",
+				"vec3 viewPosition = normalize( vViewPosition );",
+
+				// point lights
+
+				"vec3 specularTotal = vec3( 0.0 );",
+
+				"#if MAX_POINT_LIGHTS > 0",
+
+					"vec4 pointTotal = vec4( vec3( 0.0 ), 1.0 );",
+
+					"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+						"vec3 pointVector = normalize( vPointLight[ i ].xyz );",
+						"float pointDistance = vPointLight[ i ].w;",
+
+						"float pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );",
+
+						"pointTotal  += pointDistance * vec4( pointLightColor[ i ], 1.0 ) * ( mColor * pointDiffuseWeight );",
+
+						"if ( passID == 1 )",
+							"specularTotal += pointDistance * mSpecular.xyz * pointLightColor[ i ] * KS_Skin_Specular( normal, pointVector, viewPosition, uRoughness, uSpecularBrightness );",
+
+					"}",
+
+				"#endif",
+
+				// directional lights
+
+				"#if MAX_DIR_LIGHTS > 0",
+
+					"vec4 dirTotal = vec4( vec3( 0.0 ), 1.0 );",
+
+					"for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
+
+						"vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+
+						"vec3 dirVector = normalize( lDirection.xyz );",
+
+						"float dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );",
+
+						"dirTotal  += vec4( directionalLightColor[ i ], 1.0 ) * ( mColor * dirDiffuseWeight );",
+
+						"if ( passID == 1 )",
+							"specularTotal += mSpecular.xyz * directionalLightColor[ i ] * KS_Skin_Specular( normal, dirVector, viewPosition, uRoughness, uSpecularBrightness );",
+
+					"}",
+
+				"#endif",
+
+				// all lights contribution summation
+
+				"vec4 totalLight = vec4( vec3( 0.0 ), uOpacity );",
+
+				"#if MAX_DIR_LIGHTS > 0",
+					"totalLight += dirTotal;",
+				"#endif",
+
+				"#if MAX_POINT_LIGHTS > 0",
+					"totalLight += pointTotal;",
+				"#endif",
+
+				"gl_FragColor = gl_FragColor * totalLight;",
+
+				"if ( passID == 0 ) {",
+
+					"gl_FragColor = vec4( sqrt( gl_FragColor.xyz ), gl_FragColor.w );",
+
+				"} else if ( passID == 1 ) {",
+
+					//"#define VERSION1",
+
+					"#ifdef VERSION1",
+
+						"vec3 nonblurColor = sqrt( gl_FragColor.xyz );",
+
+					"#else",
+
+						"vec3 nonblurColor = gl_FragColor.xyz;",
+
+					"#endif",
+
+					"vec3 blur1Color = texture2D( tBlur1, vUv ).xyz;",
+					"vec3 blur2Color = texture2D( tBlur2, vUv ).xyz;",
+					"vec3 blur3Color = texture2D( tBlur3, vUv ).xyz;",
+					"vec3 blur4Color = texture2D( tBlur4, vUv ).xyz;",
+
+
+					//"gl_FragColor = vec4( blur1Color, gl_FragColor.w );",
+
+					//"gl_FragColor = vec4( vec3( 0.22, 0.5, 0.7 ) * nonblurColor + vec3( 0.2, 0.5, 0.3 ) * blur1Color + vec3( 0.58, 0.0, 0.0 ) * blur2Color, gl_FragColor.w );",
+
+					//"gl_FragColor = vec4( vec3( 0.25, 0.6, 0.8 ) * nonblurColor + vec3( 0.15, 0.25, 0.2 ) * blur1Color + vec3( 0.15, 0.15, 0.0 ) * blur2Color + vec3( 0.45, 0.0, 0.0 ) * blur3Color, gl_FragColor.w );",
+
+
+					"gl_FragColor = vec4( vec3( 0.22,  0.437, 0.635 ) * nonblurColor + ",
+										 "vec3( 0.101, 0.355, 0.365 ) * blur1Color + ",
+										 "vec3( 0.119, 0.208, 0.0 )   * blur2Color + ",
+										 "vec3( 0.114, 0.0,   0.0 )   * blur3Color + ",
+										 "vec3( 0.444, 0.0,   0.0 )   * blur4Color",
+										 ", gl_FragColor.w );",
+
+					"gl_FragColor.xyz *= pow( colDiffuse.xyz, vec3( 0.5 ) );",
+
+					"gl_FragColor.xyz += ambientLightColor * uAmbientColor * colDiffuse.xyz + specularTotal;",
+
+					"#ifndef VERSION1",
+
+						"gl_FragColor.xyz = sqrt( gl_FragColor.xyz );",
+
+					"#endif",
+
+				"}",
+
+				THREE.ShaderChunk[ "fog_fragment" ],
+
+			"}"
+
+		].join("\n"),
+
+		vertexShader: [
+
+			"attribute vec4 tangent;",
+
+			"#ifdef VERTEX_TEXTURES",
+
+				"uniform sampler2D tDisplacement;",
+				"uniform float uDisplacementScale;",
+				"uniform float uDisplacementBias;",
+
+			"#endif",
+
+			"varying vec3 vTangent;",
+			"varying vec3 vBinormal;",
+			"varying vec3 vNormal;",
+			"varying vec2 vUv;",
+
+			"#if MAX_POINT_LIGHTS > 0",
+
+				"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+				"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+				"varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
+
+			"#endif",
+
+			"varying vec3 vViewPosition;",
+
+			"void main() {",
+
+				"vec4 mPosition = objectMatrix * vec4( position, 1.0 );",
+
+				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+
+				"vViewPosition = -mvPosition.xyz;",
+
+				"vNormal = normalize( normalMatrix * normal );",
+
+				// tangent and binormal vectors
+
+				"vTangent = normalize( normalMatrix * tangent.xyz );",
+
+				"vBinormal = cross( vNormal, vTangent ) * tangent.w;",
+				"vBinormal = normalize( vBinormal );",
+
+				"vUv = uv;",
+
+				// point lights
+
+				"#if MAX_POINT_LIGHTS > 0",
+
+					"for( int i = 0; i < MAX_POINT_LIGHTS; i++ ) {",
+
+						"vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+
+						"vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+						"float lDistance = 1.0;",
+
+						"if ( pointLightDistance[ i ] > 0.0 )",
+							"lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+						"lVector = normalize( lVector );",
+
+						"vPointLight[ i ] = vec4( lVector, lDistance );",
+
+					"}",
+
+				"#endif",
+
+				// displacement mapping
+
+				"#ifdef VERTEX_TEXTURES",
+
+					"vec3 dv = texture2D( tDisplacement, uv ).xyz;",
+					"float df = uDisplacementScale * dv.x + uDisplacementBias;",
+					"vec4 displacedPosition = vec4( vNormal.xyz * df, 0.0 ) + mvPosition;",
+					"gl_Position = projectionMatrix * displacedPosition;",
+
+				"#else",
+
+					"gl_Position = projectionMatrix * mvPosition;",
+
+				"#endif",
+
+			"}"
+
+		].join("\n"),
+
+		vertexShaderUV: [
+
+			"attribute vec4 tangent;",
+
+			"#ifdef VERTEX_TEXTURES",
+
+				"uniform sampler2D tDisplacement;",
+				"uniform float uDisplacementScale;",
+				"uniform float uDisplacementBias;",
+
+			"#endif",
+
+			"varying vec3 vTangent;",
+			"varying vec3 vBinormal;",
+			"varying vec3 vNormal;",
+			"varying vec2 vUv;",
+
+			"#if MAX_POINT_LIGHTS > 0",
+
+				"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+				"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+				"varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
+
+			"#endif",
+
+			"varying vec3 vViewPosition;",
+
+			"void main() {",
+
+				"vec4 mPosition = objectMatrix * vec4( position, 1.0 );",
+
+				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+
+				"vViewPosition = -mvPosition.xyz;",
+
+				"vNormal = normalize( normalMatrix * normal );",
+
+				// tangent and binormal vectors
+
+				"vTangent = normalize( normalMatrix * tangent.xyz );",
+
+				"vBinormal = cross( vNormal, vTangent ) * tangent.w;",
+				"vBinormal = normalize( vBinormal );",
+
+				"vUv = uv;",
+
+				// point lights
+
+				"#if MAX_POINT_LIGHTS > 0",
+
+					"for( int i = 0; i < MAX_POINT_LIGHTS; i++ ) {",
+
+						"vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+
+						"vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+						"float lDistance = 1.0;",
+
+						"if ( pointLightDistance[ i ] > 0.0 )",
+							"lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+						"lVector = normalize( lVector );",
+
+						"vPointLight[ i ] = vec4( lVector, lDistance );",
+
+					"}",
+
+				"#endif",
+
+				"gl_Position = vec4( uv.x * 2.0 - 1.0, uv.y * 2.0 - 1.0, 0.0, 1.0 );",
+
+			"}"
+
+		].join("\n")
+
+	},
+
+	/* ------------------------------------------------------------------------------------------
+	// Beckmann distribution function
+	//	- to be used in specular term of skin shader
+	//	- render a screen-aligned quad to precompute a 512 x 512 texture
+	//
+	//		- from http://developer.nvidia.com/node/171
+	 ------------------------------------------------------------------------------------------ */
+
+	"beckmann" : {
+
+		uniforms: {},
+
+		vertexShader: [
+
+			"varying vec2 vUv;",
+
+			"void main() {",
+
+				"vUv = vec2( uv.x, 1.0 - uv.y );",
+				"gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+			"}"
+
+		].join("\n"),
+
+		fragmentShader: [
+
+			"varying vec2 vUv;",
+
+			"float PHBeckmann( float ndoth, float m ) {",
+
+				"float alpha = acos( ndoth );",
+				"float ta = tan( alpha );",
+
+				"float val = 1.0 / ( m * m * pow( ndoth, 4.0 ) ) * exp( -( ta * ta ) / ( m * m ) );",
+				"return val;",
+
+			"}",
+
+			"float KSTextureCompute( vec2 tex ) {",
+
+				// Scale the value to fit within [0,1] � invert upon lookup.
+
+				"return 0.5 * pow( PHBeckmann( tex.x, tex.y ), 0.1 );",
+
+			"}",
+
+			"void main() {",
+
+				"float x = KSTextureCompute( vUv );",
+
+				"gl_FragColor = vec4( x, x, x, 1.0 );",
+
+			"}"
+
+		].join("\n")
+
+	}
+
+};
\ No newline at end of file
diff --git a/emperor/support_files/js/js/ShaderTerrain.js b/emperor/support_files/js/js/ShaderTerrain.js
new file mode 100644
index 0000000..ff147a7
--- /dev/null
+++ b/emperor/support_files/js/js/ShaderTerrain.js
@@ -0,0 +1,306 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ */
+
+THREE.ShaderTerrain = {
+
+	/* -------------------------------------------------------------------------
+	//	Dynamic terrain shader
+	//		- Blinn-Phong
+	//		- height + normal + diffuse1 + diffuse2 + specular + detail maps
+	//		- point and directional lights (use with "lights: true" material option)
+	 ------------------------------------------------------------------------- */
+
+	'terrain' : {
+
+		uniforms: THREE.UniformsUtils.merge( [
+
+			THREE.UniformsLib[ "fog" ],
+			THREE.UniformsLib[ "lights" ],
+
+			{
+
+			"enableDiffuse1"  : { type: "i", value: 0 },
+			"enableDiffuse2"  : { type: "i", value: 0 },
+			"enableSpecular"  : { type: "i", value: 0 },
+			"enableReflection": { type: "i", value: 0 },
+
+			"tDiffuse1"	   : { type: "t", value: 0, texture: null },
+			"tDiffuse2"	   : { type: "t", value: 1, texture: null },
+			"tDetail"	   : { type: "t", value: 2, texture: null },
+			"tNormal"	   : { type: "t", value: 3, texture: null },
+			"tSpecular"	   : { type: "t", value: 4, texture: null },
+			"tDisplacement": { type: "t", value: 5, texture: null },
+
+			"uNormalScale": { type: "f", value: 1.0 },
+
+			"uDisplacementBias": { type: "f", value: 0.0 },
+			"uDisplacementScale": { type: "f", value: 1.0 },
+
+			"uDiffuseColor": { type: "c", value: new THREE.Color( 0xeeeeee ) },
+			"uSpecularColor": { type: "c", value: new THREE.Color( 0x111111 ) },
+			"uAmbientColor": { type: "c", value: new THREE.Color( 0x050505 ) },
+			"uShininess": { type: "f", value: 30 },
+			"uOpacity": { type: "f", value: 1 },
+
+			"uRepeatBase"    : { type: "v2", value: new THREE.Vector2( 1, 1 ) },
+			"uRepeatOverlay" : { type: "v2", value: new THREE.Vector2( 1, 1 ) },
+
+			"uOffset" : { type: "v2", value: new THREE.Vector2( 0, 0 ) }
+
+			}
+
+		] ),
+
+		fragmentShader: [
+
+			"uniform vec3 uAmbientColor;",
+			"uniform vec3 uDiffuseColor;",
+			"uniform vec3 uSpecularColor;",
+			"uniform float uShininess;",
+			"uniform float uOpacity;",
+
+			"uniform bool enableDiffuse1;",
+			"uniform bool enableDiffuse2;",
+			"uniform bool enableSpecular;",
+
+			"uniform sampler2D tDiffuse1;",
+			"uniform sampler2D tDiffuse2;",
+			"uniform sampler2D tDetail;",
+			"uniform sampler2D tNormal;",
+			"uniform sampler2D tSpecular;",
+			"uniform sampler2D tDisplacement;",
+
+			"uniform float uNormalScale;",
+
+			"uniform vec2 uRepeatOverlay;",
+			"uniform vec2 uRepeatBase;",
+
+			"uniform vec2 uOffset;",
+
+			"varying vec3 vTangent;",
+			"varying vec3 vBinormal;",
+			"varying vec3 vNormal;",
+			"varying vec2 vUv;",
+
+			"uniform vec3 ambientLightColor;",
+
+			"#if MAX_DIR_LIGHTS > 0",
+				"uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+				"uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+			"#endif",
+
+			"#if MAX_POINT_LIGHTS > 0",
+				"uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+				"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+				"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+			"#endif",
+
+			"varying vec3 vViewPosition;",
+
+			THREE.ShaderChunk[ "fog_pars_fragment" ],
+
+			"void main() {",
+
+				"gl_FragColor = vec4( vec3( 1.0 ), uOpacity );",
+
+				"vec3 specularTex = vec3( 1.0 );",
+
+				"vec2 uvOverlay = uRepeatOverlay * vUv + uOffset;",
+				"vec2 uvBase = uRepeatBase * vUv;",
+
+				"vec3 normalTex = texture2D( tDetail, uvOverlay ).xyz * 2.0 - 1.0;",
+				"normalTex.xy *= uNormalScale;",
+				"normalTex = normalize( normalTex );",
+
+				"if( enableDiffuse1 && enableDiffuse2 ) {",
+
+					"vec4 colDiffuse1 = texture2D( tDiffuse1, uvOverlay );",
+					"vec4 colDiffuse2 = texture2D( tDiffuse2, uvOverlay );",
+
+					"#ifdef GAMMA_INPUT",
+
+						"colDiffuse1.xyz *= colDiffuse1.xyz;",
+						"colDiffuse2.xyz *= colDiffuse2.xyz;",
+
+					"#endif",
+
+					"gl_FragColor = gl_FragColor * mix ( colDiffuse1, colDiffuse2, 1.0 - texture2D( tDisplacement, uvBase ) );",
+
+				" } else if( enableDiffuse1 ) {",
+
+					"gl_FragColor = gl_FragColor * texture2D( tDiffuse1, uvOverlay );",
+
+				"} else if( enableDiffuse2 ) {",
+
+					"gl_FragColor = gl_FragColor * texture2D( tDiffuse2, uvOverlay );",
+
+				"}",
+
+				"if( enableSpecular )",
+					"specularTex = texture2D( tSpecular, uvOverlay ).xyz;",
+
+				"mat3 tsb = mat3( vTangent, vBinormal, vNormal );",
+				"vec3 finalNormal = tsb * normalTex;",
+
+				"vec3 normal = normalize( finalNormal );",
+				"vec3 viewPosition = normalize( vViewPosition );",
+
+				// point lights
+
+				"#if MAX_POINT_LIGHTS > 0",
+
+					"vec3 pointDiffuse = vec3( 0.0 );",
+					"vec3 pointSpecular = vec3( 0.0 );",
+
+					"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+						"vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+						"vec3 lVector = lPosition.xyz + vViewPosition.xyz;",
+
+						"float lDistance = 1.0;",
+						"if ( pointLightDistance[ i ] > 0.0 )",
+							"lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+						"lVector = normalize( lVector );",
+
+						"vec3 pointHalfVector = normalize( lVector + viewPosition );",
+						"float pointDistance = lDistance;",
+
+						"float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );",
+						"float pointDiffuseWeight = max( dot( normal, lVector ), 0.0 );",
+
+						"float pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, uShininess ), 0.0 );",
+
+						"pointDiffuse += pointDistance * pointLightColor[ i ] * uDiffuseColor * pointDiffuseWeight;",
+						"pointSpecular += pointDistance * pointLightColor[ i ] * uSpecularColor * pointSpecularWeight * pointDiffuseWeight;",
+
+					"}",
+
+				"#endif",
+
+				// directional lights
+
+				"#if MAX_DIR_LIGHTS > 0",
+
+					"vec3 dirDiffuse = vec3( 0.0 );",
+					"vec3 dirSpecular = vec3( 0.0 );",
+
+					"for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
+
+						"vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+
+						"vec3 dirVector = normalize( lDirection.xyz );",
+						"vec3 dirHalfVector = normalize( dirVector + viewPosition );",
+
+						"float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
+						"float dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );",
+
+						"float dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, uShininess ), 0.0 );",
+
+						"dirDiffuse += directionalLightColor[ i ] * uDiffuseColor * dirDiffuseWeight;",
+						"dirSpecular += directionalLightColor[ i ] * uSpecularColor * dirSpecularWeight * dirDiffuseWeight;",
+
+					"}",
+
+				"#endif",
+
+				// all lights contribution summation
+
+				"vec3 totalDiffuse = vec3( 0.0 );",
+				"vec3 totalSpecular = vec3( 0.0 );",
+
+				"#if MAX_DIR_LIGHTS > 0",
+
+					"totalDiffuse += dirDiffuse;",
+					"totalSpecular += dirSpecular;",
+
+				"#endif",
+
+				"#if MAX_POINT_LIGHTS > 0",
+
+					"totalDiffuse += pointDiffuse;",
+					"totalSpecular += pointSpecular;",
+
+				"#endif",
+
+				//"gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * uAmbientColor) + totalSpecular;",
+				"gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * uAmbientColor + totalSpecular );",
+
+				THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+				THREE.ShaderChunk[ "fog_fragment" ],
+
+			"}"
+
+		].join("\n"),
+
+		vertexShader: [
+
+			"attribute vec4 tangent;",
+
+			"uniform vec2 uRepeatBase;",
+
+			"uniform sampler2D tNormal;",
+
+			"#ifdef VERTEX_TEXTURES",
+
+				"uniform sampler2D tDisplacement;",
+				"uniform float uDisplacementScale;",
+				"uniform float uDisplacementBias;",
+
+			"#endif",
+
+			"varying vec3 vTangent;",
+			"varying vec3 vBinormal;",
+			"varying vec3 vNormal;",
+			"varying vec2 vUv;",
+
+			"varying vec3 vViewPosition;",
+
+			"void main() {",
+
+				"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+
+				"vViewPosition = -mvPosition.xyz;",
+
+				"vNormal = normalize( normalMatrix * normal );",
+
+				// tangent and binormal vectors
+
+				"vTangent = normalize( normalMatrix * tangent.xyz );",
+
+				"vBinormal = cross( vNormal, vTangent ) * tangent.w;",
+				"vBinormal = normalize( vBinormal );",
+
+				// texture coordinates
+
+				"vUv = uv;",
+
+				"vec2 uvBase = uv * uRepeatBase;",
+
+				// displacement mapping
+
+				"#ifdef VERTEX_TEXTURES",
+
+					"vec3 dv = texture2D( tDisplacement, uvBase ).xyz;",
+					"float df = uDisplacementScale * dv.x + uDisplacementBias;",
+					"vec4 displacedPosition = vec4( vNormal.xyz * df, 0.0 ) + mvPosition;",
+					"gl_Position = projectionMatrix * displacedPosition;",
+
+				"#else",
+
+					"gl_Position = projectionMatrix * mvPosition;",
+
+				"#endif",
+
+				"vec3 normalTex = texture2D( tNormal, uvBase ).xyz * 2.0 - 1.0;",
+				"vNormal = normalMatrix * normalTex;",
+
+			"}"
+
+		].join("\n")
+
+	}
+
+};
diff --git a/emperor/support_files/js/js/SimplexNoise.js b/emperor/support_files/js/js/SimplexNoise.js
new file mode 100644
index 0000000..5f85487
--- /dev/null
+++ b/emperor/support_files/js/js/SimplexNoise.js
@@ -0,0 +1,316 @@
+// Ported from Stefan Gustavson's java implementation
+// http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
+// Read Stefan's excellent paper for details on how this code works.
+//
+// Sean McCullough banksean at gmail.com
+//
+// Added 4D noise
+// Joshua Koo zz85nus at gmail.com 
+
+/**
+ * You can pass in a random number generator object if you like.
+ * It is assumed to have a random() method.
+ */
+var SimplexNoise = function(r) {
+	if (r == undefined) r = Math;
+  this.grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0], 
+                                 [1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1], 
+                                 [0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]]; 
+
+  this.grad4 = [[0,1,1,1], [0,1,1,-1], [0,1,-1,1], [0,1,-1,-1],
+	     [0,-1,1,1], [0,-1,1,-1], [0,-1,-1,1], [0,-1,-1,-1],
+	     [1,0,1,1], [1,0,1,-1], [1,0,-1,1], [1,0,-1,-1],
+	     [-1,0,1,1], [-1,0,1,-1], [-1,0,-1,1], [-1,0,-1,-1],
+	     [1,1,0,1], [1,1,0,-1], [1,-1,0,1], [1,-1,0,-1],
+	     [-1,1,0,1], [-1,1,0,-1], [-1,-1,0,1], [-1,-1,0,-1],
+	     [1,1,1,0], [1,1,-1,0], [1,-1,1,0], [1,-1,-1,0],
+	     [-1,1,1,0], [-1,1,-1,0], [-1,-1,1,0], [-1,-1,-1,0]];
+
+  this.p = [];
+  for (var i=0; i<256; i++) {
+	  this.p[i] = Math.floor(r.random()*256);
+  }
+  // To remove the need for index wrapping, double the permutation table length 
+  this.perm = []; 
+  for(var i=0; i<512; i++) {
+		this.perm[i]=this.p[i & 255];
+	} 
+
+  // A lookup table to traverse the simplex around a given point in 4D. 
+  // Details can be found where this table is used, in the 4D noise method. 
+  this.simplex = [ 
+    [0,1,2,3],[0,1,3,2],[0,0,0,0],[0,2,3,1],[0,0,0,0],[0,0,0,0],[0,0,0,0],[1,2,3,0], 
+    [0,2,1,3],[0,0,0,0],[0,3,1,2],[0,3,2,1],[0,0,0,0],[0,0,0,0],[0,0,0,0],[1,3,2,0], 
+    [0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0], 
+    [1,2,0,3],[0,0,0,0],[1,3,0,2],[0,0,0,0],[0,0,0,0],[0,0,0,0],[2,3,0,1],[2,3,1,0], 
+    [1,0,2,3],[1,0,3,2],[0,0,0,0],[0,0,0,0],[0,0,0,0],[2,0,3,1],[0,0,0,0],[2,1,3,0], 
+    [0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0], 
+    [2,0,1,3],[0,0,0,0],[0,0,0,0],[0,0,0,0],[3,0,1,2],[3,0,2,1],[0,0,0,0],[3,1,2,0], 
+    [2,1,0,3],[0,0,0,0],[0,0,0,0],[0,0,0,0],[3,1,0,2],[0,0,0,0],[3,2,0,1],[3,2,1,0]]; 
+};
+
+SimplexNoise.prototype.dot = function(g, x, y) { 
+	return g[0]*x + g[1]*y;
+};
+
+SimplexNoise.prototype.noise = function(xin, yin) { 
+  var n0, n1, n2; // Noise contributions from the three corners 
+  // Skew the input space to determine which simplex cell we're in 
+  var F2 = 0.5*(Math.sqrt(3.0)-1.0); 
+  var s = (xin+yin)*F2; // Hairy factor for 2D 
+  var i = Math.floor(xin+s); 
+  var j = Math.floor(yin+s); 
+  var G2 = (3.0-Math.sqrt(3.0))/6.0; 
+  var t = (i+j)*G2; 
+  var X0 = i-t; // Unskew the cell origin back to (x,y) space 
+  var Y0 = j-t; 
+  var x0 = xin-X0; // The x,y distances from the cell origin 
+  var y0 = yin-Y0; 
+  // For the 2D case, the simplex shape is an equilateral triangle. 
+  // Determine which simplex we are in. 
+  var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords 
+  if(x0>y0) {i1=1; j1=0;} // lower triangle, XY order: (0,0)->(1,0)->(1,1) 
+  else {i1=0; j1=1;}      // upper triangle, YX order: (0,0)->(0,1)->(1,1) 
+  // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and 
+  // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where 
+  // c = (3-sqrt(3))/6 
+  var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords 
+  var y1 = y0 - j1 + G2; 
+  var x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords 
+  var y2 = y0 - 1.0 + 2.0 * G2; 
+  // Work out the hashed gradient indices of the three simplex corners 
+  var ii = i & 255; 
+  var jj = j & 255; 
+  var gi0 = this.perm[ii+this.perm[jj]] % 12; 
+  var gi1 = this.perm[ii+i1+this.perm[jj+j1]] % 12; 
+  var gi2 = this.perm[ii+1+this.perm[jj+1]] % 12; 
+  // Calculate the contribution from the three corners 
+  var t0 = 0.5 - x0*x0-y0*y0; 
+  if(t0<0) n0 = 0.0; 
+  else { 
+    t0 *= t0; 
+    n0 = t0 * t0 * this.dot(this.grad3[gi0], x0, y0);  // (x,y) of grad3 used for 2D gradient 
+  } 
+  var t1 = 0.5 - x1*x1-y1*y1; 
+  if(t1<0) n1 = 0.0; 
+  else { 
+    t1 *= t1; 
+    n1 = t1 * t1 * this.dot(this.grad3[gi1], x1, y1); 
+  }
+  var t2 = 0.5 - x2*x2-y2*y2; 
+  if(t2<0) n2 = 0.0; 
+  else { 
+    t2 *= t2; 
+    n2 = t2 * t2 * this.dot(this.grad3[gi2], x2, y2); 
+  } 
+  // Add contributions from each corner to get the final noise value. 
+  // The result is scaled to return values in the interval [-1,1]. 
+  return 70.0 * (n0 + n1 + n2); 
+};
+
+// 3D simplex noise 
+SimplexNoise.prototype.noise3d = function(xin, yin, zin) { 
+  var n0, n1, n2, n3; // Noise contributions from the four corners 
+  // Skew the input space to determine which simplex cell we're in 
+  var F3 = 1.0/3.0; 
+  var s = (xin+yin+zin)*F3; // Very nice and simple skew factor for 3D 
+  var i = Math.floor(xin+s); 
+  var j = Math.floor(yin+s); 
+  var k = Math.floor(zin+s); 
+  var G3 = 1.0/6.0; // Very nice and simple unskew factor, too 
+  var t = (i+j+k)*G3; 
+  var X0 = i-t; // Unskew the cell origin back to (x,y,z) space 
+  var Y0 = j-t; 
+  var Z0 = k-t; 
+  var x0 = xin-X0; // The x,y,z distances from the cell origin 
+  var y0 = yin-Y0; 
+  var z0 = zin-Z0; 
+  // For the 3D case, the simplex shape is a slightly irregular tetrahedron. 
+  // Determine which simplex we are in. 
+  var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords 
+  var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords 
+  if(x0>=y0) { 
+    if(y0>=z0) 
+      { i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; } // X Y Z order 
+      else if(x0>=z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; } // X Z Y order 
+      else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; } // Z X Y order 
+    } 
+  else { // x0<y0 
+    if(y0<z0) { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; } // Z Y X order 
+    else if(x0<z0) { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; } // Y Z X order 
+    else { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; } // Y X Z order 
+  } 
+  // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z), 
+  // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and 
+  // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where 
+  // c = 1/6.
+  var x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords 
+  var y1 = y0 - j1 + G3; 
+  var z1 = z0 - k1 + G3; 
+  var x2 = x0 - i2 + 2.0*G3; // Offsets for third corner in (x,y,z) coords 
+  var y2 = y0 - j2 + 2.0*G3; 
+  var z2 = z0 - k2 + 2.0*G3; 
+  var x3 = x0 - 1.0 + 3.0*G3; // Offsets for last corner in (x,y,z) coords 
+  var y3 = y0 - 1.0 + 3.0*G3; 
+  var z3 = z0 - 1.0 + 3.0*G3; 
+  // Work out the hashed gradient indices of the four simplex corners 
+  var ii = i & 255; 
+  var jj = j & 255; 
+  var kk = k & 255; 
+  var gi0 = this.perm[ii+this.perm[jj+this.perm[kk]]] % 12; 
+  var gi1 = this.perm[ii+i1+this.perm[jj+j1+this.perm[kk+k1]]] % 12; 
+  var gi2 = this.perm[ii+i2+this.perm[jj+j2+this.perm[kk+k2]]] % 12; 
+  var gi3 = this.perm[ii+1+this.perm[jj+1+this.perm[kk+1]]] % 12; 
+  // Calculate the contribution from the four corners 
+  var t0 = 0.6 - x0*x0 - y0*y0 - z0*z0; 
+  if(t0<0) n0 = 0.0; 
+  else { 
+    t0 *= t0; 
+    n0 = t0 * t0 * this.dot(this.grad3[gi0], x0, y0, z0); 
+  }
+  var t1 = 0.6 - x1*x1 - y1*y1 - z1*z1; 
+  if(t1<0) n1 = 0.0; 
+  else { 
+    t1 *= t1; 
+    n1 = t1 * t1 * this.dot(this.grad3[gi1], x1, y1, z1); 
+  } 
+  var t2 = 0.6 - x2*x2 - y2*y2 - z2*z2; 
+  if(t2<0) n2 = 0.0; 
+  else { 
+    t2 *= t2; 
+    n2 = t2 * t2 * this.dot(this.grad3[gi2], x2, y2, z2); 
+  } 
+  var t3 = 0.6 - x3*x3 - y3*y3 - z3*z3; 
+  if(t3<0) n3 = 0.0; 
+  else { 
+    t3 *= t3; 
+    n3 = t3 * t3 * this.dot(this.grad3[gi3], x3, y3, z3); 
+  } 
+  // Add contributions from each corner to get the final noise value. 
+  // The result is scaled to stay just inside [-1,1] 
+  return 32.0*(n0 + n1 + n2 + n3); 
+};
+
+// 4D simplex noise
+SimplexNoise.prototype.noise4d = function( x, y, z, w ) {
+	// For faster and easier lookups
+	var grad4 = this.grad4;
+	var simplex = this.simplex;
+	var perm = this.perm;
+	
+   // The skewing and unskewing factors are hairy again for the 4D case
+   var F4 = (Math.sqrt(5.0)-1.0)/4.0;
+   var G4 = (5.0-Math.sqrt(5.0))/20.0;
+   var n0, n1, n2, n3, n4; // Noise contributions from the five corners
+   // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
+   var s = (x + y + z + w) * F4; // Factor for 4D skewing
+   var i = Math.floor(x + s);
+   var j = Math.floor(y + s);
+   var k = Math.floor(z + s);
+   var l = Math.floor(w + s);
+   var t = (i + j + k + l) * G4; // Factor for 4D unskewing
+   var X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space
+   var Y0 = j - t;
+   var Z0 = k - t;
+   var W0 = l - t;
+   var x0 = x - X0;  // The x,y,z,w distances from the cell origin
+   var y0 = y - Y0;
+   var z0 = z - Z0;
+   var w0 = w - W0;
+
+   // For the 4D case, the simplex is a 4D shape I won't even try to describe.
+   // To find out which of the 24 possible simplices we're in, we need to
+   // determine the magnitude ordering of x0, y0, z0 and w0.
+   // The method below is a good way of finding the ordering of x,y,z,w and
+   // then find the correct traversal order for the simplex we’re in.
+   // First, six pair-wise comparisons are performed between each possible pair
+   // of the four coordinates, and the results are used to add up binary bits
+   // for an integer index.
+   var c1 = (x0 > y0) ? 32 : 0;
+   var c2 = (x0 > z0) ? 16 : 0;
+   var c3 = (y0 > z0) ? 8 : 0;
+   var c4 = (x0 > w0) ? 4 : 0;
+   var c5 = (y0 > w0) ? 2 : 0;
+   var c6 = (z0 > w0) ? 1 : 0;
+   var c = c1 + c2 + c3 + c4 + c5 + c6;
+   var i1, j1, k1, l1; // The integer offsets for the second simplex corner
+   var i2, j2, k2, l2; // The integer offsets for the third simplex corner
+   var i3, j3, k3, l3; // The integer offsets for the fourth simplex corner
+   // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
+   // Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
+   // impossible. Only the 24 indices which have non-zero entries make any sense.
+   // We use a thresholding to set the coordinates in turn from the largest magnitude.
+   // The number 3 in the "simplex" array is at the position of the largest coordinate.
+   i1 = simplex[c][0]>=3 ? 1 : 0;
+   j1 = simplex[c][1]>=3 ? 1 : 0;
+   k1 = simplex[c][2]>=3 ? 1 : 0;
+   l1 = simplex[c][3]>=3 ? 1 : 0;
+   // The number 2 in the "simplex" array is at the second largest coordinate.
+   i2 = simplex[c][0]>=2 ? 1 : 0;
+   j2 = simplex[c][1]>=2 ? 1 : 0;    k2 = simplex[c][2]>=2 ? 1 : 0;
+   l2 = simplex[c][3]>=2 ? 1 : 0;
+   // The number 1 in the "simplex" array is at the second smallest coordinate.
+   i3 = simplex[c][0]>=1 ? 1 : 0;
+   j3 = simplex[c][1]>=1 ? 1 : 0;
+   k3 = simplex[c][2]>=1 ? 1 : 0;
+   l3 = simplex[c][3]>=1 ? 1 : 0;
+   // The fifth corner has all coordinate offsets = 1, so no need to look that up.
+   var x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords
+   var y1 = y0 - j1 + G4;
+   var z1 = z0 - k1 + G4;
+   var w1 = w0 - l1 + G4;
+   var x2 = x0 - i2 + 2.0*G4; // Offsets for third corner in (x,y,z,w) coords
+   var y2 = y0 - j2 + 2.0*G4;
+   var z2 = z0 - k2 + 2.0*G4;
+   var w2 = w0 - l2 + 2.0*G4;
+   var x3 = x0 - i3 + 3.0*G4; // Offsets for fourth corner in (x,y,z,w) coords
+   var y3 = y0 - j3 + 3.0*G4;
+   var z3 = z0 - k3 + 3.0*G4;
+   var w3 = w0 - l3 + 3.0*G4;
+   var x4 = x0 - 1.0 + 4.0*G4; // Offsets for last corner in (x,y,z,w) coords
+   var y4 = y0 - 1.0 + 4.0*G4;
+   var z4 = z0 - 1.0 + 4.0*G4;
+   var w4 = w0 - 1.0 + 4.0*G4;
+   // Work out the hashed gradient indices of the five simplex corners
+   var ii = i & 255;
+   var jj = j & 255;
+   var kk = k & 255;
+   var ll = l & 255;
+   var gi0 = perm[ii+perm[jj+perm[kk+perm[ll]]]] % 32;
+   var gi1 = perm[ii+i1+perm[jj+j1+perm[kk+k1+perm[ll+l1]]]] % 32;
+   var gi2 = perm[ii+i2+perm[jj+j2+perm[kk+k2+perm[ll+l2]]]] % 32;
+   var gi3 = perm[ii+i3+perm[jj+j3+perm[kk+k3+perm[ll+l3]]]] % 32;
+   var gi4 = perm[ii+1+perm[jj+1+perm[kk+1+perm[ll+1]]]] % 32;
+   // Calculate the contribution from the five corners
+   var t0 = 0.6 - x0*x0 - y0*y0 - z0*z0 - w0*w0;
+   if(t0<0) n0 = 0.0;
+   else {
+     t0 *= t0;
+     n0 = t0 * t0 * this.dot(grad4[gi0], x0, y0, z0, w0);
+   }
+  var t1 = 0.6 - x1*x1 - y1*y1 - z1*z1 - w1*w1;
+   if(t1<0) n1 = 0.0;
+   else {
+     t1 *= t1;
+     n1 = t1 * t1 * this.dot(grad4[gi1], x1, y1, z1, w1);
+   }
+  var t2 = 0.6 - x2*x2 - y2*y2 - z2*z2 - w2*w2;
+   if(t2<0) n2 = 0.0;
+   else {
+     t2 *= t2;
+     n2 = t2 * t2 * this.dot(grad4[gi2], x2, y2, z2, w2);
+   }   var t3 = 0.6 - x3*x3 - y3*y3 - z3*z3 - w3*w3;
+   if(t3<0) n3 = 0.0;
+   else {
+     t3 *= t3;
+     n3 = t3 * t3 * this.dot(grad4[gi3], x3, y3, z3, w3);
+   }
+  var t4 = 0.6 - x4*x4 - y4*y4 - z4*z4 - w4*w4;
+   if(t4<0) n4 = 0.0;
+   else {
+     t4 *= t4;
+     n4 = t4 * t4 * this.dot(grad4[gi4], x4, y4, z4, w4);
+   }
+   // Sum up and scale the result to cover the range [-1,1]
+   return 27.0 * (n0 + n1 + n2 + n3 + n4);
+};
diff --git a/emperor/support_files/js/js/Sparks.js b/emperor/support_files/js/js/Sparks.js
new file mode 100644
index 0000000..3b63994
--- /dev/null
+++ b/emperor/support_files/js/js/Sparks.js
@@ -0,0 +1,832 @@
+/*
+ * @author zz85 (http://github.com/zz85 http://www.lab4games.net/zz85/blog)
+ *
+ * a simple to use javascript 3d particles system inspired by FliNT and Stardust
+ * created with TWEEN.js and THREE.js
+ *
+ * for feature requests or bugs, please visit https://github.com/zz85/sparks.js
+ *
+ * licensed under the MIT license 
+ */
+
+var SPARKS = {};
+
+/********************************
+* Emitter Class
+*
+*   Creates and Manages Particles
+*********************************/
+
+SPARKS.Emitter = function (counter) {
+    
+    this._counter = counter ? counter : new SPARKS.SteadyCounter(10); // provides number of particles to produce
+    
+    this._particles = [];
+    
+    
+    this._initializers = []; // use for creation of particles
+    this._actions = [];     // uses action to update particles
+    this._activities = [];  //  not supported yet
+        
+    this._handlers = [];
+    
+    this.callbacks = {};
+};
+
+
+SPARKS.Emitter.prototype = {
+	
+	_TIMESTEP: 15,
+	_timer: null,
+	_lastTime: null,
+	_timerStep: 10,
+	_velocityVerlet: true,
+	
+	// run its built in timer / stepping
+	start: function() {
+		this._lastTime = Date.now();
+		this._timer = setTimeout(this.step, this._timerStep, this);
+		this._isRunning = true;
+	},
+	
+	stop: function() {
+		this._isRunning = false;
+		clearTimeout(this._timer);
+	},
+	
+	isRunning: function() {
+		return this._isRunning & true;
+	},
+	
+	// Step gets called upon by the engine
+	// but attempts to call update() on a regular basics
+	// This method is also described in http://gameclosure.com/2011/04/11/deterministic-delta-tee-in-js-games/
+	step: function(emitter) {
+		
+		var time = Date.now();
+		var elapsed = time - emitter._lastTime;
+	   	
+		if (!this._velocityVerlet) {
+			// if elapsed is way higher than time step, (usually after switching tabs, or excution cached in ff)
+			// we will drop cycles. perhaps set to a limit of 10 or something?
+			var maxBlock = emitter._TIMESTEP * 20;
+			
+			if (elapsed >= maxBlock) {
+				//console.log('warning: sparks.js is fast fowarding engine, skipping steps', elapsed / emitter._TIMESTEP);
+				//emitter.update( (elapsed - maxBlock) / 1000);
+				elapsed = maxBlock;
+			}
+		
+			while(elapsed >= emitter._TIMESTEP) {
+				emitter.update(emitter._TIMESTEP / 1000);
+				elapsed -= emitter._TIMESTEP;
+			}
+			emitter._lastTime = time - elapsed;
+			
+		} else {
+			emitter.update(elapsed/1000);
+			emitter._lastTime = time;
+		}
+		
+		
+		
+		if (emitter._isRunning)
+		setTimeout(emitter.step, emitter._timerStep, emitter);
+		
+	},
+
+
+	// Update particle engine in seconds, not milliseconds
+    update: function(time) {
+		
+        var len = this._counter.updateEmitter( this, time );
+        
+        // Create particles
+        for( i = 0; i < len; i++ ) {
+            this.createParticle();
+        }
+        
+        // Update activities
+        len = this._activities.length;
+        for ( i = 0; i < len; i++ )
+        {
+            this_.activities[i].update( this, time );
+        }
+        
+        
+        len = this._actions.length;
+        var action;
+        var len2 = this._particles.length;
+        
+        for( j = 0; j < len; j++ )
+        {
+            action = this._actions[j];
+            for ( i = 0; i < len2; ++i )
+            {
+                particle = this._particles[i];
+                action.update( this, particle, time );
+            }
+        }
+        
+        
+        // remove dead particles
+        for ( i = len2; i--; )
+        {
+            particle = this._particles[i];
+            if ( particle.isDead )
+            {
+                //particle = 
+				this._particles.splice( i, 1 );
+                this.dispatchEvent("dead", particle);
+				SPARKS.VectorPool.release(particle.position); //
+				SPARKS.VectorPool.release(particle.velocity);
+                
+            } else {
+                this.dispatchEvent("updated", particle);
+            }
+        }
+        
+		this.dispatchEvent("loopUpdated");
+		
+    },
+    
+    createParticle: function() {
+        var particle = new SPARKS.Particle();
+        // In future, use a Particle Factory
+        var len = this._initializers.length, i;
+
+        for ( i = 0; i < len; i++ ) {
+            this._initializers[i].initialize( this, particle );
+        }
+        
+        this._particles.push( particle );
+        
+        this.dispatchEvent("created", particle); // ParticleCreated
+        
+        return particle;
+    },
+    
+    addInitializer: function (initializer) {
+        this._initializers.push(initializer);
+    },
+    
+    addAction: function (action) {
+        this._actions.push(action);
+    },
+
+    removeInitializer: function (initializer) {
+		var index = this._initializers.indexOf(initializer);
+		if (index > -1) {
+			this._initializers.splice( index, 1 );
+		}
+    },
+
+    removeAction: function (action) {
+		var index = this._actions.indexOf(action);
+		if (index > -1) {
+			this._actions.splice( index, 1 );
+		}
+		//console.log('removeAction', index, this._actions);
+    },
+    
+    addCallback: function(name, callback) {
+        this.callbacks[name] = callback;
+    },
+    
+    dispatchEvent: function(name, args) {
+        var callback = this.callbacks[name];
+        if (callback) {
+            callback(args);
+        }
+    
+    }
+    
+
+};
+
+
+/*
+ * Constant Names for
+ * Events called by emitter.dispatchEvent()
+ * 
+ */
+SPARKS.EVENT_PARTICLE_CREATED = "created"
+SPARKS.EVENT_PARTICLE_UPDATED = "updated"
+SPARKS.EVENT_PARTICLE_DEAD = "dead";
+SPARKS.EVENT_LOOP_UPDATED = "loopUpdated";
+
+
+
+/*
+ * Steady Counter attempts to produces a particle rate steadily
+ *
+ */
+
+// Number of particles per seconds
+SPARKS.SteadyCounter = function(rate) {
+    this.rate = rate;
+    
+	// we use a shortfall counter to make up for slow emitters 
+	this.leftover = 0;
+	
+};
+
+SPARKS.SteadyCounter.prototype.updateEmitter = function(emitter, time) {
+
+	var targetRelease = time * this.rate + this.leftover;
+	var actualRelease = Math.floor(targetRelease);
+	
+	this.leftover = targetRelease - actualRelease;
+	
+	return actualRelease;
+};
+
+
+/*
+ * Shot Counter produces specified particles 
+ * on a single impluse or burst
+ */
+
+SPARKS.ShotCounter = function(particles) {
+	this.particles = particles;
+	this.used = false;
+};
+
+SPARKS.ShotCounter.prototype.updateEmitter = function(emitter, time) {
+
+	if (this.used) {
+		return 0;
+	} else {
+		this.used = true;
+	}
+	
+	return this.particles;
+};
+
+
+/********************************
+* Particle Class
+*
+*   Represents a single particle
+*********************************/
+SPARKS.Particle = function() {
+
+    /**
+     * The lifetime of the particle, in seconds.
+     */
+    this.lifetime = 0;
+    
+    /**
+     * The age of the particle, in seconds.
+     */
+    this.age = 0;
+    
+    /**
+     * The energy of the particle.
+     */
+    this.energy = 1;
+    
+    /**
+     * Whether the particle is dead and should be removed from the stage.
+     */
+    this.isDead = false;
+    
+    this.target = null; // tag
+    
+    /**
+     * For 3D
+     */
+     
+     this.position = SPARKS.VectorPool.get().set(0,0,0); //new THREE.Vector3( 0, 0, 0 );
+     this.velocity = SPARKS.VectorPool.get().set(0,0,0); //new THREE.Vector3( 0, 0, 0 );
+	this._oldvelocity = SPARKS.VectorPool.get().set(0,0,0);
+     // rotation vec3
+     // angVelocity vec3
+     // faceAxis vec3
+    
+};
+
+
+/********************************
+* Action Classes
+*
+*   An abstract class which have
+*   update function
+*********************************/
+SPARKS.Action = function() {
+    this._priority = 0;
+};
+
+
+SPARKS.Age = function(easing) {
+    this._easing = (easing == null) ? TWEEN.Easing.Linear.EaseNone : easing;
+};
+
+SPARKS.Age.prototype.update = function (emitter, particle, time) {
+    particle.age += time;
+    if( particle.age >= particle.lifetime )
+    {
+        particle.energy = 0;
+        particle.isDead = true;
+    }
+    else
+    {
+        var t = this._easing(particle.age / particle.lifetime);
+        particle.energy = -1 * t + 1;
+    }
+};
+
+/*
+// Mark particle as dead when particle's < 0
+
+SPARKS.Death = function(easing) {
+    this._easing = (easing == null) ? TWEEN.Linear.EaseNone : easing;
+};
+
+SPARKS.Death.prototype.update = function (emitter, particle, time) {
+    if (particle.life <= 0) {
+        particle.isDead = true;
+    }
+};
+*/
+			
+
+SPARKS.Move = function() {
+    
+};
+
+SPARKS.Move.prototype.update = function(emitter, particle, time) {
+    // attempt verlet velocity updating.
+    var p = particle.position;
+	var v = particle.velocity;
+    var old = particle._oldvelocity;
+	
+	if (this._velocityVerlet) {	
+		p.x += (v.x + old.x) * 0.5 * time;
+		p.y += (v.y + old.y) * 0.5 * time;
+		p.z += (v.z + old.z) * 0.5 * time;
+	} else {
+		p.x += v.x * time;
+		p.y += v.y * time;
+		p.z += v.z * time;
+	}
+
+    //  OldVel = Vel;
+    // Vel = Vel + Accel * dt;
+    // Pos = Pos + (vel + Vel + Accel * dt) * 0.5 * dt;
+	
+
+
+};
+
+/* Marks particles found in specified zone dead */
+SPARKS.DeathZone = function(zone) {
+    this.zone = zone;
+};
+
+SPARKS.DeathZone.prototype.update = function(emitter, particle, time) {
+    
+    if (this.zone.contains(particle.position)) {
+		particle.isDead = true;
+	}
+
+};
+
+/*
+ * SPARKS.ActionZone applies an action when particle is found in zone
+ */
+SPARKS.ActionZone = function(action, zone) {
+	this.action = action;
+    this.zone = zone;
+};
+
+SPARKS.ActionZone.prototype.update = function(emitter, particle, time) {
+
+    if (this.zone.contains(particle.position)) {
+		this.action.update( emitter, particle, time );
+	}
+
+};
+
+/*
+ * Accelerate action affects velocity in specified 3d direction 
+ */
+SPARKS.Accelerate = function(x,y,z) {
+	
+	if (x instanceof THREE.Vector3) {
+		this.acceleration = x;
+		return;
+	}
+
+    this.acceleration = new THREE.Vector3(x,y,z);
+    
+};
+
+SPARKS.Accelerate.prototype.update = function(emitter, particle, time) {
+    var acc = this.acceleration;
+    
+    var v = particle.velocity;
+    
+	particle._oldvelocity.set(v.x, v.y, v.z);
+	
+    v.x += acc.x * time;
+    v.y += acc.y * time;
+    v.z += acc.z * time; 
+
+};
+
+/*
+ * Accelerate Factor accelerate based on a factor of particle's velocity.
+ */
+SPARKS.AccelerateFactor = function(factor) {
+    this.factor = factor;
+};
+
+SPARKS.AccelerateFactor.prototype.update = function(emitter, particle, time) {
+    var factor = this.factor;
+    
+    var v = particle.velocity;
+	var len = v.length();
+	var adjFactor;
+    if (len>0) {
+
+		adjFactor = factor * time / len;
+		adjFactor += 1;
+		
+		v.multiplyScalar(adjFactor);
+		// v.x *= adjFactor;
+		// 	    v.y *= adjFactor;
+		// 	    v.z *= adjFactor; 
+	}
+
+};
+
+/*
+AccelerateNormal
+ * AccelerateVelocity affects velocity based on its velocity direction
+ */
+SPARKS.AccelerateVelocity = function(factor) {
+
+	this.factor = factor;
+
+};
+
+SPARKS.AccelerateVelocity.prototype.update = function(emitter, particle, time) {
+    var factor = this.factor;
+
+    var v = particle.velocity;
+
+
+    v.z += - v.x * factor;
+    v.y += v.z * factor;
+    v.x +=  v.y * factor;
+
+};
+
+
+/* Set the max ammount of x,y,z drift movements in a second */
+SPARKS.RandomDrift = function(x,y,z) {
+	if (x instanceof THREE.Vector3) {
+		this.drift = x;
+		return;
+	}
+
+    this.drift = new THREE.Vector3(x,y,z);
+}
+
+
+SPARKS.RandomDrift.prototype.update = function(emitter, particle, time) {
+    var drift = this.drift;
+    
+    var v = particle.velocity;
+    
+    v.x += ( Math.random() - 0.5 ) * drift.x * time;
+    v.y += ( Math.random() - 0.5 ) * drift.y * time;
+    v.z += ( Math.random() - 0.5 ) * drift.z * time;
+
+};
+
+/********************************
+* Zone Classes
+*
+*   An abstract classes which have
+*   getLocation() function
+*********************************/
+SPARKS.Zone = function() {
+};
+
+// TODO, contains() for Zone
+
+SPARKS.PointZone = function(pos) {
+    this.pos = pos;
+};
+
+SPARKS.PointZone.prototype.getLocation = function() {
+    return this.pos;
+};
+
+SPARKS.PointZone = function(pos) {
+    this.pos = pos;
+};
+
+SPARKS.PointZone.prototype.getLocation = function() {
+    return this.pos;
+};
+
+SPARKS.LineZone = function(start, end) {
+    this.start = start;
+	this.end = end;
+	this._length = end.clone().subSelf( start );
+};
+
+SPARKS.LineZone.prototype.getLocation = function() {
+    var len = this._length.clone();
+
+	len.multiplyScalar( Math.random() );
+	return len.addSelf( this.start );
+	
+};
+
+// Basically a RectangleZone
+SPARKS.ParallelogramZone = function(corner, side1, side2) {
+    this.corner = corner;
+	this.side1 = side1;
+	this.side2 = side2;
+};
+
+SPARKS.ParallelogramZone.prototype.getLocation = function() {
+    
+	var d1 = this.side1.clone().multiplyScalar( Math.random() );
+	var d2 = this.side2.clone().multiplyScalar( Math.random() );
+	d1.addSelf(d2);
+	return d1.addSelf( this.corner );
+	
+};
+
+SPARKS.CubeZone = function(position, x, y, z) {
+    this.position = position;
+	this.x = x;
+	this.y = y;
+	this.z = z;
+};
+
+SPARKS.CubeZone.prototype.getLocation = function() {
+    //TODO use pool?
+
+	var location = this.position.clone();
+	location.x += Math.random() * this.x;
+	location.y += Math.random() * this.y;
+	location.z += Math.random() * this.z;
+	
+	return location;
+	
+};
+
+
+SPARKS.CubeZone.prototype.contains = function(position) {
+
+	var startX = this.position.x;
+	var startY = this.position.y;
+	var startZ = this.position.z;
+	var x = this.x; // width
+	var y = this.y; // depth
+	var z = this.z; // height
+	
+	if (x<0) {
+		startX += x;
+		x = Math.abs(x);
+	}
+	
+	if (y<0) {
+		startY += y;
+		y = Math.abs(y);
+	}
+	
+	if (z<0) {
+		startZ += z;
+		z = Math.abs(z);
+	}
+	
+	var diffX = position.x - startX;
+	var diffY = position.y - startY;
+	var diffZ = position.z - startZ;
+	
+	if ( (diffX > 0) && (diffX < x) && 
+			(diffY > 0) && (diffY < y) && 
+			(diffZ > 0) && (diffZ < z) ) {
+		return true;
+	}
+	
+	return false;
+	
+};
+
+
+
+/**
+ * The constructor creates a DiscZone 3D zone.
+ * 
+ * @param centre The point at the center of the disc.
+ * @param normal A vector normal to the disc.
+ * @param outerRadius The outer radius of the disc.
+ * @param innerRadius The inner radius of the disc. This defines the hole 
+ * in the center of the disc. If set to zero, there is no hole. 
+ */
+
+/*
+// BUGGY!!
+SPARKS.DiscZone = function(center, radiusNormal, outerRadius, innerRadius) {
+    this.center = center;
+	this.radiusNormal = radiusNormal;
+	this.outerRadius = (outerRadius==undefined) ? 0 : outerRadius;
+	this.innerRadius = (innerRadius==undefined) ? 0 : innerRadius;
+	
+};
+
+SPARKS.DiscZone.prototype.getLocation = function() {
+    var rand = Math.random();
+	var _innerRadius = this.innerRadius;
+	var _outerRadius = this.outerRadius;
+	var center = this.center;
+	var _normal = this.radiusNormal;
+	
+	_distToOrigin = _normal.dot( center );
+	
+	var radius = _innerRadius + (1 - rand * rand ) * ( _outerRadius - _innerRadius );
+	var angle = Math.random() * SPARKS.Utils.TWOPI;
+	
+	var _distToOrigin = _normal.dot( center );
+	var axes = SPARKS.Utils.getPerpendiculars( _normal.clone() );
+	var _planeAxis1 = axes[0];
+	var _planeAxis2 = axes[1];
+	
+	var p = _planeAxis1.clone();
+	p.multiplyScalar( radius * Math.cos( angle ) );
+	var p2 = _planeAxis2.clone();
+	p2.multiplyScalar( radius * Math.sin( angle ) );
+	p.addSelf( p2 );
+	return _center.add( p );
+	
+};
+*/
+
+SPARKS.SphereCapZone = function(x, y, z, minr, maxr, angle) {
+    this.x = x;
+    this.y = y;
+    this.z = z;
+    this.minr = minr;
+    this.maxr = maxr;
+    this.angle = angle;
+};
+
+SPARKS.SphereCapZone.prototype.getLocation = function() {
+    var theta = Math.PI *2  * SPARKS.Utils.random();
+    var r = SPARKS.Utils.random();
+    
+    //new THREE.Vector3
+    var v =  SPARKS.VectorPool.get().set(r * Math.cos(theta), -1 / Math.tan(this.angle * SPARKS.Utils.DEGREE_TO_RADIAN), r * Math.sin(theta));
+    
+    //v.length = StardustMath.interpolate(0, _minRadius, 1, _maxRadius, Math.random());
+            
+    var i = this.minr - ((this.minr-this.maxr) *  Math.random() );
+    v.multiplyScalar(i);
+
+	v.__markedForReleased = true;
+    
+    return v;
+};
+
+
+/********************************
+* Initializer Classes
+*
+*   Classes which initializes
+*   particles. Implements initialize( emitter:Emitter, particle:Particle )
+*********************************/
+
+// Specifies random life between max and min
+SPARKS.Lifetime = function(min, max) {
+    this._min = min;
+    
+    this._max = max ? max : min;
+    
+};
+
+SPARKS.Lifetime.prototype.initialize = function( emitter/*Emitter*/, particle/*Particle*/ ) {
+    particle.lifetime = this._min + SPARKS.Utils.random() * ( this._max - this._min );
+};
+
+
+SPARKS.Position = function(zone) {
+    this.zone = zone;
+};
+
+SPARKS.Position.prototype.initialize = function( emitter/*Emitter*/, particle/*Particle*/ ) {
+    var pos = this.zone.getLocation();
+    particle.position.set(pos.x, pos.y, pos.z);
+};
+
+SPARKS.Velocity = function(zone) {
+    this.zone = zone;
+};
+
+SPARKS.Velocity.prototype.initialize = function( emitter/*Emitter*/, particle/*Particle*/ ) {
+    var pos = this.zone.getLocation();
+    particle.velocity.set(pos.x, pos.y, pos.z);
+	if (pos.__markedForReleased) {
+		//console.log("release");
+		SPARKS.VectorPool.release(pos);
+		pos.__markedForReleased = false;
+	}
+};
+
+SPARKS.Target = function(target, callback) {
+    this.target = target;
+    this.callback = callback;
+};
+
+SPARKS.Target.prototype.initialize = function( emitter, particle ) {
+
+    if (this.callback) {
+        particle.target = this.callback();
+    } else {
+        particle.target = this.target;
+    }
+
+};
+
+/********************************
+* VectorPool 
+*
+*  Reuse much of Vectors if possible
+*********************************/
+
+SPARKS.VectorPool = {
+	__pools: [],
+
+	// Get a new Vector
+	get: function() {
+		if (this.__pools.length>0) {
+			return this.__pools.pop();
+		}
+		
+		return this._addToPool();
+		
+	},
+	
+	// Release a vector back into the pool
+	release: function(v) {
+		this.__pools.push(v);
+	},
+	
+	// Create a bunch of vectors and add to the pool
+	_addToPool: function() {
+		//console.log("creating some pools");
+		
+		for (var i=0, size = 100; i < size; i++) {
+			this.__pools.push(new THREE.Vector3());
+		}
+		
+		return new THREE.Vector3();
+		
+	}
+	
+	
+	
+};
+
+
+/********************************
+* Util Classes
+*
+*   Classes which initializes
+*   particles. Implements initialize( emitter:Emitter, particle:Particle )
+*********************************/
+SPARKS.Utils = {
+    random: function() {
+        return Math.random();
+    },
+    DEGREE_TO_RADIAN: Math.PI / 180,
+	TWOPI: Math.PI * 2,
+
+	getPerpendiculars: function(normal) {
+		var p1 = this.getPerpendicular( normal );
+		var p2 = normal.cross( p1 );
+		p2.normalize();
+		return [ p1, p2 ];
+	},
+	
+	getPerpendicular: function( v )
+	{
+		if( v.x == 0 )
+		{
+			return new THREE.Vector3D( 1, 0, 0 );
+		}
+		else
+		{
+			var temp = new THREE.Vector3( v.y, -v.x, 0 );
+			return temp.normalize();
+		}
+	}
+
+};
\ No newline at end of file
diff --git a/emperor/support_files/js/js/Stats.js b/emperor/support_files/js/js/Stats.js
new file mode 100644
index 0000000..6cdeeb3
--- /dev/null
+++ b/emperor/support_files/js/js/Stats.js
@@ -0,0 +1,8 @@
+// stats.js r8 - http://github.com/mrdoob/stats.js
+var Stats=function(){var h,a,n=0,o=0,i=Date.now(),u=i,p=i,l=0,q=1E3,r=0,e,j,f,b=[[16,16,48],[0,255,255]],m=0,s=1E3,t=0,d,k,g,c=[[16,48,16],[0,255,0]];h=document.createElement("div");h.style.cursor="pointer";h.style.width="80px";h.style.opacity="0.9";h.style.zIndex="10001";h.addEventListener("mousedown",function(a){a.preventDefault();n=(n+1)%2;n==0?(e.style.display="block",d.style.display="none"):(e.style.display="none",d.style.display="block")},!1);e=document.createElement("div");e.style [...]
+"left";e.style.lineHeight="1.2em";e.style.backgroundColor="rgb("+Math.floor(b[0][0]/2)+","+Math.floor(b[0][1]/2)+","+Math.floor(b[0][2]/2)+")";e.style.padding="0 0 3px 3px";h.appendChild(e);j=document.createElement("div");j.style.fontFamily="Helvetica, Arial, sans-serif";j.style.fontSize="9px";j.style.color="rgb("+b[1][0]+","+b[1][1]+","+b[1][2]+")";j.style.fontWeight="bold";j.innerHTML="FPS";e.appendChild(j);f=document.createElement("div");f.style.position="relative";f.style.width="74px [...]
+"30px";f.style.backgroundColor="rgb("+b[1][0]+","+b[1][1]+","+b[1][2]+")";for(e.appendChild(f);f.children.length<74;)a=document.createElement("span"),a.style.width="1px",a.style.height="30px",a.style.cssFloat="left",a.style.backgroundColor="rgb("+b[0][0]+","+b[0][1]+","+b[0][2]+")",f.appendChild(a);d=document.createElement("div");d.style.textAlign="left";d.style.lineHeight="1.2em";d.style.backgroundColor="rgb("+Math.floor(c[0][0]/2)+","+Math.floor(c[0][1]/2)+","+Math.floor(c[0][2]/2)+")" [...]
+"0 0 3px 3px";d.style.display="none";h.appendChild(d);k=document.createElement("div");k.style.fontFamily="Helvetica, Arial, sans-serif";k.style.fontSize="9px";k.style.color="rgb("+c[1][0]+","+c[1][1]+","+c[1][2]+")";k.style.fontWeight="bold";k.innerHTML="MS";d.appendChild(k);g=document.createElement("div");g.style.position="relative";g.style.width="74px";g.style.height="30px";g.style.backgroundColor="rgb("+c[1][0]+","+c[1][1]+","+c[1][2]+")";for(d.appendChild(g);g.children.length<74;)a=d [...]
+a.style.width="1px",a.style.height=Math.random()*30+"px",a.style.cssFloat="left",a.style.backgroundColor="rgb("+c[0][0]+","+c[0][1]+","+c[0][2]+")",g.appendChild(a);return{domElement:h,update:function(){i=Date.now();m=i-u;s=Math.min(s,m);t=Math.max(t,m);k.textContent=m+" MS ("+s+"-"+t+")";var a=Math.min(30,30-m/200*30);g.appendChild(g.firstChild).style.height=a+"px";u=i;o++;if(i>p+1E3)l=Math.round(o*1E3/(i-p)),q=Math.min(q,l),r=Math.max(r,l),j.textContent=l+" FPS ("+q+"-"+r+")",a=Math.mi [...]
+100*30),f.appendChild(f.firstChild).style.height=a+"px",p=i,o=0}}};
+
diff --git a/emperor/support_files/js/js/Tween.js b/emperor/support_files/js/js/Tween.js
new file mode 100644
index 0000000..67824ab
--- /dev/null
+++ b/emperor/support_files/js/js/Tween.js
@@ -0,0 +1,13 @@
+// tween.js r5 - http://github.com/sole/tween.js
+var TWEEN=TWEEN||function(){var a,e,c=60,b=false,h=[];return{setFPS:function(f){c=f||60},start:function(f){arguments.length!=0&&this.setFPS(f);e=setInterval(this.update,1E3/c)},stop:function(){clearInterval(e)},setAutostart:function(f){(b=f)&&!e&&this.start()},add:function(f){h.push(f);b&&!e&&this.start()},getAll:function(){return h},removeAll:function(){h=[]},remove:function(f){a=h.indexOf(f);a!==-1&&h.splice(a,1)},update:function(f){a=0;num_tweens=h.length;for(f=f||Date.now();a<num_twe [...]
+else{h.splice(a,1);num_tweens--}num_tweens==0&&b==true&&this.stop()}}}();
+TWEEN.Tween=function(a){var e={},c={},b={},h=1E3,f=0,j=null,n=TWEEN.Easing.Linear.EaseNone,k=null,l=null,m=null;this.to=function(d,g){if(g!==null)h=g;for(var i in d)if(a[i]!==null)b[i]=d[i];return this};this.start=function(d){TWEEN.add(this);j=d?d+f:Date.now()+f;for(var g in b)if(a[g]!==null){e[g]=a[g];c[g]=b[g]-a[g]}return this};this.stop=function(){TWEEN.remove(this);return this};this.delay=function(d){f=d;return this};this.easing=function(d){n=d;return this};this.chain=function(d){k=d [...]
+function(d){l=d;return this};this.onComplete=function(d){m=d;return this};this.update=function(d){var g,i;if(d<j)return true;d=(d-j)/h;d=d>1?1:d;i=n(d);for(g in c)a[g]=e[g]+c[g]*i;l!==null&&l.call(a,i);if(d==1){m!==null&&m.call(a);k!==null&&k.start();return false}return true}};TWEEN.Easing={Linear:{},Quadratic:{},Cubic:{},Quartic:{},Quintic:{},Sinusoidal:{},Exponential:{},Circular:{},Elastic:{},Back:{},Bounce:{}};TWEEN.Easing.Linear.EaseNone=function(a){return a};
+TWEEN.Easing.Quadratic.EaseIn=function(a){return a*a};TWEEN.Easing.Quadratic.EaseOut=function(a){return-a*(a-2)};TWEEN.Easing.Quadratic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a;return-0.5*(--a*(a-2)-1)};TWEEN.Easing.Cubic.EaseIn=function(a){return a*a*a};TWEEN.Easing.Cubic.EaseOut=function(a){return--a*a*a+1};TWEEN.Easing.Cubic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a;return 0.5*((a-=2)*a*a+2)};TWEEN.Easing.Quartic.EaseIn=function(a){return a*a*a*a};
+TWEEN.Easing.Quartic.EaseOut=function(a){return-(--a*a*a*a-1)};TWEEN.Easing.Quartic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a*a;return-0.5*((a-=2)*a*a*a-2)};TWEEN.Easing.Quintic.EaseIn=function(a){return a*a*a*a*a};TWEEN.Easing.Quintic.EaseOut=function(a){return(a-=1)*a*a*a*a+1};TWEEN.Easing.Quintic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a*a*a;return 0.5*((a-=2)*a*a*a*a+2)};TWEEN.Easing.Sinusoidal.EaseIn=function(a){return-Math.cos(a*Math.PI/2)+1};
+TWEEN.Easing.Sinusoidal.EaseOut=function(a){return Math.sin(a*Math.PI/2)};TWEEN.Easing.Sinusoidal.EaseInOut=function(a){return-0.5*(Math.cos(Math.PI*a)-1)};TWEEN.Easing.Exponential.EaseIn=function(a){return a==0?0:Math.pow(2,10*(a-1))};TWEEN.Easing.Exponential.EaseOut=function(a){return a==1?1:-Math.pow(2,-10*a)+1};TWEEN.Easing.Exponential.EaseInOut=function(a){if(a==0)return 0;if(a==1)return 1;if((a*=2)<1)return 0.5*Math.pow(2,10*(a-1));return 0.5*(-Math.pow(2,-10*(a-1))+2)};
+TWEEN.Easing.Circular.EaseIn=function(a){return-(Math.sqrt(1-a*a)-1)};TWEEN.Easing.Circular.EaseOut=function(a){return Math.sqrt(1- --a*a)};TWEEN.Easing.Circular.EaseInOut=function(a){if((a/=0.5)<1)return-0.5*(Math.sqrt(1-a*a)-1);return 0.5*(Math.sqrt(1-(a-=2)*a)+1)};TWEEN.Easing.Elastic.EaseIn=function(a){var e,c=0.1,b=0.4;if(a==0)return 0;if(a==1)return 1;b||(b=0.3);if(!c||c<1){c=1;e=b/4}else e=b/(2*Math.PI)*Math.asin(1/c);return-(c*Math.pow(2,10*(a-=1))*Math.sin((a-e)*2*Math.PI/b))};
+TWEEN.Easing.Elastic.EaseOut=function(a){var e,c=0.1,b=0.4;if(a==0)return 0;if(a==1)return 1;b||(b=0.3);if(!c||c<1){c=1;e=b/4}else e=b/(2*Math.PI)*Math.asin(1/c);return c*Math.pow(2,-10*a)*Math.sin((a-e)*2*Math.PI/b)+1};
+TWEEN.Easing.Elastic.EaseInOut=function(a){var e,c=0.1,b=0.4;if(a==0)return 0;if(a==1)return 1;b||(b=0.3);if(!c||c<1){c=1;e=b/4}else e=b/(2*Math.PI)*Math.asin(1/c);if((a*=2)<1)return-0.5*c*Math.pow(2,10*(a-=1))*Math.sin((a-e)*2*Math.PI/b);return c*Math.pow(2,-10*(a-=1))*Math.sin((a-e)*2*Math.PI/b)*0.5+1};TWEEN.Easing.Back.EaseIn=function(a){return a*a*(2.70158*a-1.70158)};TWEEN.Easing.Back.EaseOut=function(a){return(a-=1)*a*(2.70158*a+1.70158)+1};
+TWEEN.Easing.Back.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*(3.5949095*a-2.5949095);return 0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)};TWEEN.Easing.Bounce.EaseIn=function(a){return 1-TWEEN.Easing.Bounce.EaseOut(1-a)};TWEEN.Easing.Bounce.EaseOut=function(a){return(a/=1)<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375};
+TWEEN.Easing.Bounce.EaseInOut=function(a){if(a<0.5)return TWEEN.Easing.Bounce.EaseIn(a*2)*0.5;return TWEEN.Easing.Bounce.EaseOut(a*2-1)*0.5+0.5};
\ No newline at end of file
diff --git a/emperor/support_files/js/js/ctm/CTMLoader.js b/emperor/support_files/js/js/ctm/CTMLoader.js
new file mode 100644
index 0000000..b045648
--- /dev/null
+++ b/emperor/support_files/js/js/ctm/CTMLoader.js
@@ -0,0 +1,821 @@
+/**
+ * Loader for CTM encoded models generated by OpenCTM tools:
+ *	http://openctm.sourceforge.net/
+ *
+ * Uses js-openctm library by Juan Mellado
+ *	http://code.google.com/p/js-openctm/
+ *
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.CTMLoader = function ( context, showStatus ) {
+
+	this.context = context;
+
+	THREE.Loader.call( this, showStatus );
+
+};
+
+THREE.CTMLoader.prototype = new THREE.Loader();
+THREE.CTMLoader.prototype.constructor = THREE.CTMLoader;
+
+
+// Load multiple CTM parts defined in JSON
+
+THREE.CTMLoader.prototype.loadParts = function( url, callback, useWorker, useBuffers, basePath ) {
+
+	var scope = this;
+
+	var xhr = new XMLHttpRequest();
+
+	basePath = basePath ? basePath : this.extractUrlbase( url );
+
+	xhr.onreadystatechange = function() {
+
+		if ( xhr.readyState == 4 ) {
+
+			if ( xhr.status == 200 || xhr.status == 0 ) {
+
+				var jsonObject = JSON.parse( xhr.responseText );
+
+				var materials = [], geometries = [], counter = 0;
+
+				function callbackFinal( geometry ) {
+
+					counter += 1;
+
+					geometries.push( geometry );
+
+					if ( counter === jsonObject.offsets.length ) {
+
+						callback( geometries, materials );
+
+					}
+
+				}
+
+
+				// init materials
+
+				for ( var i = 0; i < jsonObject.materials.length; i ++ ) {
+
+					materials[ i ] = THREE.Loader.prototype.createMaterial( jsonObject.materials[ i ], basePath );
+
+				}
+
+				// load joined CTM file
+
+				var partUrl = basePath + jsonObject.data;
+				scope.load( partUrl, callbackFinal, useWorker, useBuffers, jsonObject.offsets );
+
+			}
+
+		}
+
+	}
+
+	xhr.open( "GET", url, true );
+	if ( xhr.overrideMimeType ) xhr.overrideMimeType( "text/plain; charset=x-user-defined" );
+	xhr.setRequestHeader( "Content-Type", "text/plain" );
+	xhr.send( null );
+
+};
+
+// Load CTMLoader compressed models
+//  - parameters
+//		- url (required)
+//		- callback (required)
+
+THREE.CTMLoader.prototype.load = function( url, callback, useWorker, useBuffers, offsets ) {
+
+	var scope = this;
+
+	offsets = offsets !== undefined ? offsets : [ 0 ];
+
+	var xhr = new XMLHttpRequest(),
+		callbackProgress = null;
+
+	var length = 0;
+
+	xhr.onreadystatechange = function() {
+
+		if ( xhr.readyState == 4 ) {
+
+			if ( xhr.status == 200 || xhr.status == 0 ) {
+
+				var binaryData = xhr.responseText;
+
+				//var s = Date.now();
+
+				if ( useWorker ) {
+
+					var worker = new Worker( "js/ctm/CTMWorker.js" );
+
+					worker.onmessage = function( event ) {
+
+						var files = event.data;
+
+						for ( var i = 0; i < files.length; i ++ ) {
+
+							var ctmFile = files[ i ];
+
+							if ( useBuffers ) {
+
+								scope.createModelBuffers( ctmFile, callback );
+
+							} else {
+
+								scope.createModelClassic( ctmFile, callback );
+
+							}
+
+						}
+
+						//var e = Date.now();
+						//console.log( "CTM data parse time [worker]: " + (e-s) + " ms" );
+
+					};
+
+					worker.postMessage( { "data": binaryData, "offsets": offsets } );
+
+				} else {
+
+
+					for ( var i = 0; i < offsets.length; i ++ ) {
+
+						var stream = new CTM.Stream( binaryData );
+						stream.offset = offsets[ i ];
+
+						var ctmFile = new CTM.File( stream );
+
+						if ( useBuffers ) {
+
+							scope.createModelBuffers( ctmFile, callback );
+
+						} else {
+
+							scope.createModelClassic( ctmFile, callback );
+
+						}
+
+					}
+
+					//var e = Date.now();
+					//console.log( "CTM data parse time [inline]: " + (e-s) + " ms" );
+
+				}
+
+			} else {
+
+				console.error( "Couldn't load [" + url + "] [" + xhr.status + "]" );
+
+			}
+
+		} else if ( xhr.readyState == 3 ) {
+
+			if ( callbackProgress ) {
+
+				if ( length == 0 ) {
+
+					length = xhr.getResponseHeader( "Content-Length" );
+
+				}
+
+				callbackProgress( { total: length, loaded: xhr.responseText.length } );
+
+			}
+
+		} else if ( xhr.readyState == 2 ) {
+
+			length = xhr.getResponseHeader( "Content-Length" );
+
+		}
+
+	}
+
+	xhr.overrideMimeType( "text/plain; charset=x-user-defined" );
+	xhr.open( "GET", url, true );
+	xhr.send( null );
+
+};
+
+
+THREE.CTMLoader.prototype.createModelBuffers = function ( file, callback ) {
+
+	var gl = this.context;
+
+	var Model = function ( ) {
+
+		var scope = this;
+
+		var dynamic = false,
+		computeNormals = true,
+		normalizeNormals = true,
+		reorderVertices = true;
+
+		scope.materials = [];
+
+		THREE.BufferGeometry.call( this );
+
+		// init GL buffers
+
+		var vertexIndexArray = file.body.indices,
+		vertexPositionArray = file.body.vertices,
+		vertexNormalArray = file.body.normals;
+
+		var vertexUvArray, vertexColorArray;
+
+		if ( file.body.uvMaps !== undefined && file.body.uvMaps.length > 0 ) {
+
+			vertexUvArray = file.body.uvMaps[ 0 ].uv;
+
+		}
+
+		if ( file.body.attrMaps !== undefined && file.body.attrMaps.length > 0 && file.body.attrMaps[ 0 ].name === "Color" ) {
+
+			vertexColorArray = file.body.attrMaps[ 0 ].attr;
+
+		}
+
+		//console.log( "vertices", vertexPositionArray.length/3 );
+		//console.log( "triangles", vertexIndexArray.length/3 );
+
+		// compute face normals from scratch
+		// (must be done before computing offsets)
+
+		if ( vertexNormalArray === undefined && computeNormals ) {
+
+			var nElements = vertexPositionArray.length;
+
+			vertexNormalArray = new Float32Array( nElements );
+
+			var vA, vB, vC, x, y, z,
+
+			pA = new THREE.Vector3(),
+			pB = new THREE.Vector3(),
+			pC = new THREE.Vector3(),
+
+			cb = new THREE.Vector3(),
+			ab = new THREE.Vector3();
+
+			for ( var i = 0; i < vertexIndexArray.length; i += 3 ) {
+
+				vA = vertexIndexArray[ i ];
+				vB = vertexIndexArray[ i + 1 ];
+				vC = vertexIndexArray[ i + 2 ];
+
+				x = vertexPositionArray[ vA * 3 ];
+				y = vertexPositionArray[ vA * 3 + 1 ];
+				z = vertexPositionArray[ vA * 3 + 2 ];
+				pA.set( x, y, z );
+
+				x = vertexPositionArray[ vB * 3 ];
+				y = vertexPositionArray[ vB * 3 + 1 ];
+				z = vertexPositionArray[ vB * 3 + 2 ];
+				pB.set( x, y, z );
+
+				x = vertexPositionArray[ vC * 3 ];
+				y = vertexPositionArray[ vC * 3 + 1 ];
+				z = vertexPositionArray[ vC * 3 + 2 ];
+				pC.set( x, y, z );
+
+				cb.sub( pC, pB );
+				ab.sub( pA, pB );
+				cb.crossSelf( ab );
+
+				vertexNormalArray[ vA * 3 ] 	+= cb.x;
+				vertexNormalArray[ vA * 3 + 1 ] += cb.y;
+				vertexNormalArray[ vA * 3 + 2 ] += cb.z;
+
+				vertexNormalArray[ vB * 3 ] 	+= cb.x;
+				vertexNormalArray[ vB * 3 + 1 ] += cb.y;
+				vertexNormalArray[ vB * 3 + 2 ] += cb.z;
+
+				vertexNormalArray[ vC * 3 ] 	+= cb.x;
+				vertexNormalArray[ vC * 3 + 1 ] += cb.y;
+				vertexNormalArray[ vC * 3 + 2 ] += cb.z;
+
+			}
+
+			if ( normalizeNormals ) {
+
+				for ( var i = 0; i < nElements; i += 3 ) {
+
+					x = vertexNormalArray[ i ];
+					y = vertexNormalArray[ i + 1 ];
+					z = vertexNormalArray[ i + 2 ];
+
+					var n = 1.0 / Math.sqrt( x * x + y * y + z * z );
+
+					vertexNormalArray[ i ] 	   *= n;
+					vertexNormalArray[ i + 1 ] *= n;
+					vertexNormalArray[ i + 2 ] *= n;
+
+				}
+
+			}
+
+		}
+
+		// reorder vertices
+		// (needed for buffer splitting, to keep together face vertices)
+
+		if ( reorderVertices ) {
+
+			var newFaces = new Uint32Array( vertexIndexArray.length ),
+				newVertices = new Float32Array( vertexPositionArray.length );
+
+			var newNormals, newUvs, newColors;
+
+			if ( vertexNormalArray ) newNormals = new Float32Array( vertexNormalArray.length );
+			if ( vertexUvArray ) newUvs = new Float32Array( vertexUvArray.length );
+			if ( vertexColorArray ) newColors = new Float32Array( vertexColorArray.length );
+
+			var indexMap = {}, vertexCounter = 0;
+
+			function handleVertex( v ) {
+
+				if ( indexMap[ v ] === undefined ) {
+
+					indexMap[ v ] = vertexCounter;
+
+					var sx = v * 3,
+						sy = v * 3 + 1,
+						sz = v * 3 + 2,
+
+						dx = vertexCounter * 3,
+						dy = vertexCounter * 3 + 1,
+						dz = vertexCounter * 3 + 2;
+
+					newVertices[ dx ] = vertexPositionArray[ sx ];
+					newVertices[ dy ] = vertexPositionArray[ sy ];
+					newVertices[ dz ] = vertexPositionArray[ sz ];
+
+					if ( vertexNormalArray ) {
+
+						newNormals[ dx ] = vertexNormalArray[ sx ];
+						newNormals[ dy ] = vertexNormalArray[ sy ];
+						newNormals[ dz ] = vertexNormalArray[ sz ];
+
+					}
+
+					if ( vertexUvArray ) {
+
+						newUvs[ vertexCounter * 2 ] 	= vertexUvArray[ v * 2 ];
+						newUvs[ vertexCounter * 2 + 1 ] = vertexUvArray[ v * 2 + 1 ];
+
+					}
+
+					if ( vertexColorArray ) {
+
+						newColors[ vertexCounter * 4 ] 	   = vertexNormalArray[ v * 4 ];
+						newColors[ vertexCounter * 4 + 1 ] = vertexNormalArray[ v * 4 + 1 ];
+						newColors[ vertexCounter * 4 + 2 ] = vertexNormalArray[ v * 4 + 2 ];
+						newColors[ vertexCounter * 4 + 3 ] = vertexNormalArray[ v * 4 + 3 ];
+
+					}
+
+					vertexCounter += 1;
+
+				}
+
+			}
+
+			var a, b, c;
+
+			for ( var i = 0; i < vertexIndexArray.length; i += 3 ) {
+
+				a = vertexIndexArray[ i ];
+				b = vertexIndexArray[ i + 1 ];
+				c = vertexIndexArray[ i + 2 ];
+
+				handleVertex( a );
+				handleVertex( b );
+				handleVertex( c );
+
+				newFaces[ i ] 	  = indexMap[ a ];
+				newFaces[ i + 1 ] = indexMap[ b ];
+				newFaces[ i + 2 ] = indexMap[ c ];
+
+			}
+
+			vertexIndexArray = newFaces;
+			vertexPositionArray = newVertices;
+
+			if ( vertexNormalArray ) vertexNormalArray = newNormals;
+			if ( vertexUvArray ) vertexUvArray = newUvs;
+			if ( vertexColorArray ) vertexColorArray = newColors;
+
+		}
+
+		// compute offsets
+
+		scope.offsets = [];
+
+		var indices = vertexIndexArray;
+
+		var start = 0,
+			min = vertexPositionArray.length,
+			max = 0,
+			minPrev = min;
+
+		for ( var i = 0; i < indices.length; ) {
+
+			for ( var j = 0; j < 3; ++ j ) {
+
+				var idx = indices[ i ++ ];
+
+				if ( idx < min ) min = idx;
+				if ( idx > max ) max = idx;
+
+			}
+
+			if ( max - min > 65535 ) {
+
+				i -= 3;
+
+				for ( var k = start; k < i; ++ k ) {
+
+					indices[ k ] -= minPrev;
+
+				}
+
+				scope.offsets.push( { start: start, count: i - start, index: minPrev } );
+
+				start = i;
+				min = vertexPositionArray.length;
+				max = 0;
+
+			}
+
+			minPrev = min;
+
+		}
+
+		for ( var k = start; k < i; ++ k ) {
+
+			indices[ k ] -= minPrev;
+
+		}
+
+		scope.offsets.push( { start: start, count: i - start, index: minPrev } );
+
+
+		// indices
+
+		scope.vertexIndexBuffer = gl.createBuffer();
+		gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, scope.vertexIndexBuffer );
+		gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( vertexIndexArray ), gl.STATIC_DRAW );
+
+		scope.vertexIndexBuffer.itemSize = 1;
+		scope.vertexIndexBuffer.numItems = vertexIndexArray.length;
+
+		// vertices
+
+		scope.vertexPositionBuffer = gl.createBuffer();
+		gl.bindBuffer( gl.ARRAY_BUFFER, scope.vertexPositionBuffer );
+		gl.bufferData( gl.ARRAY_BUFFER, vertexPositionArray, gl.STATIC_DRAW );
+
+		scope.vertexPositionBuffer.itemSize = 3;
+		scope.vertexPositionBuffer.numItems = vertexPositionArray.length;
+
+		// normals
+
+		if ( vertexNormalArray !== undefined ) {
+
+			scope.vertexNormalBuffer = gl.createBuffer();
+			gl.bindBuffer( gl.ARRAY_BUFFER, scope.vertexNormalBuffer );
+			gl.bufferData( gl.ARRAY_BUFFER, vertexNormalArray, gl.STATIC_DRAW );
+
+			scope.vertexNormalBuffer.itemSize = 3;
+			scope.vertexNormalBuffer.numItems = vertexNormalArray.length;
+
+		}
+
+		// uvs
+
+		if ( vertexUvArray !== undefined ) {
+
+			// "fix" flipping
+
+			for ( var i = 0; i < vertexUvArray.length; i += 2 ) {
+
+				vertexUvArray[ i + 1 ] = 1 - vertexUvArray[ i + 1 ];
+
+			}
+
+			scope.vertexUvBuffer = gl.createBuffer();
+			gl.bindBuffer( gl.ARRAY_BUFFER, scope.vertexUvBuffer );
+			gl.bufferData( gl.ARRAY_BUFFER, vertexUvArray, gl.STATIC_DRAW );
+
+			scope.vertexUvBuffer.itemSize = 2;
+			scope.vertexUvBuffer.numItems = vertexUvArray.length;
+
+		}
+
+		// colors
+
+		if ( vertexColorArray !== undefined ) {
+
+			scope.vertexColorBuffer = gl.createBuffer();
+			gl.bindBuffer( gl.ARRAY_BUFFER, scope.vertexColorBuffer );
+			gl.bufferData( gl.ARRAY_BUFFER, vertexColorArray, gl.STATIC_DRAW );
+
+			scope.vertexColorBuffer.itemSize = 4;
+			scope.vertexColorBuffer.numItems = vertexColorArray.length;
+
+		}
+
+		// compute bounding sphere and bounding box
+		// (must do it now as we don't keep typed arrays after setting GL buffers)
+
+		scope.boundingBox = { min: new THREE.Vector3( Infinity, Infinity, Infinity ), max: new THREE.Vector3( -Infinity, -Infinity, -Infinity ) };
+
+		var vertices = file.body.vertices,
+			bb = scope.boundingBox,
+			radius, maxRadius = 0,
+			x, y, z;
+
+		for ( var i = 0, il = vertices.length; i < il; i += 3 ) {
+
+			x = vertices[ i ];
+			y = vertices[ i + 1 ];
+			z = vertices[ i + 2 ];
+
+			// bounding sphere
+
+			radius = Math.sqrt( x * x + y * y + z * z );
+			if ( radius > maxRadius ) maxRadius = radius;
+
+			// bounding box
+
+			if ( x < bb.min.x ) {
+
+				bb.min.x = x;
+
+			} else if ( x > bb.max.x ) {
+
+				bb.max.x = x;
+
+			}
+
+			if ( y < bb.min.y ) {
+
+				bb.min.y = y;
+
+			} else if ( y > bb.max.y ) {
+
+				bb.max.y = y;
+
+			}
+
+			if ( z < bb.min.z ) {
+
+				bb.min.z = z;
+
+			} else if ( z > bb.max.z ) {
+
+				bb.max.z = z;
+
+			}
+
+		}
+
+		scope.boundingSphere = { radius: maxRadius };
+
+		// keep references to typed arrays
+
+		if ( dynamic ) {
+
+			scope.vertexIndexArray = vertexIndexArray;
+			scope.vertexPositionArray = vertexPositionArray;
+			scope.vertexNormalArray = vertexNormalArray;
+			scope.vertexUvArray = vertexUvArray;
+			scope.vertexColorArray = vertexColorArray;
+
+		}
+
+	}
+
+	Model.prototype = new THREE.BufferGeometry();
+	Model.prototype.constructor = Model;
+
+	callback( new Model() );
+
+};
+
+THREE.CTMLoader.prototype.createModelClassic = function ( file, callback ) {
+
+	var Model = function ( ) {
+
+		var scope = this;
+
+		scope.materials = [];
+
+		THREE.Geometry.call( this );
+
+		var normals = [],
+			uvs = [],
+			colors = [];
+
+		init_vertices( file.body.vertices );
+
+		if ( file.body.normals !== undefined )
+			init_normals( file.body.normals );
+
+		if ( file.body.uvMaps !== undefined && file.body.uvMaps.length > 0 )
+			init_uvs( file.body.uvMaps[ 0 ].uv );
+
+		if ( file.body.attrMaps !== undefined && file.body.attrMaps.length > 0 && file.body.attrMaps[ 0 ].name === "Color" )
+			init_colors( file.body.attrMaps[ 0 ].attr );
+
+		var hasNormals = normals.length > 0 ? true : false,
+			hasUvs = uvs.length > 0 ? true : false,
+			hasColors = colors.length > 0 ? true : false;
+
+		init_faces( file.body.indices );
+
+		this.computeCentroids();
+		this.computeFaceNormals();
+		//this.computeTangents();
+
+		function init_vertices( buffer ) {
+
+			var x, y, z, i, il = buffer.length;
+
+			for( i = 0; i < il; i += 3 ) {
+
+				x = buffer[ i ];
+				y = buffer[ i + 1 ];
+				z = buffer[ i + 2 ];
+
+				vertex( scope, x, y, z );
+
+			}
+
+		};
+
+		function init_normals( buffer ) {
+
+			var x, y, z, i, il = buffer.length;
+
+			for( i = 0; i < il; i += 3 ) {
+
+				x = buffer[ i ];
+				y = buffer[ i + 1 ];
+				z = buffer[ i + 2 ];
+
+				normals.push( x, y, z );
+
+			}
+
+		};
+
+		function init_colors( buffer ) {
+
+			var r, g, b, a, i, il = buffer.length;
+
+			for( i = 0; i < il; i += 4 ) {
+
+				r = buffer[ i ];
+				g = buffer[ i + 1 ];
+				b = buffer[ i + 2 ];
+				a = buffer[ i + 3 ];
+
+				var color = new THREE.Color();
+				color.setRGB( r, g, b );
+
+				colors.push( color );
+
+			}
+
+		};
+
+
+		function init_uvs( buffer ) {
+
+			var u, v, i, il = buffer.length;
+
+			for( i = 0; i < il; i += 2 ) {
+
+				u = buffer[ i ];
+				v = buffer[ i + 1 ];
+
+				uvs.push( u, 1 - v );
+
+			}
+
+		};
+
+		function init_faces( buffer ) {
+
+			var a, b, c,
+				u1, v1, u2, v2, u3, v3,
+				m, face,
+				i, il = buffer.length;
+
+			m = 0; // all faces defaulting to material 0
+
+			for( i = 0; i < il; i += 3 ) {
+
+				a = buffer[ i ];
+				b = buffer[ i + 1 ];
+				c = buffer[ i + 2 ];
+
+				if ( hasNormals ){
+
+					face = f3n( scope, normals, a, b, c, m, a, b, c );
+
+				} else {
+
+					face = f3( scope, a, b, c, m );
+
+				}
+
+				if ( hasColors ) {
+
+					face.vertexColors[ 0 ] = colors[ a ];
+					face.vertexColors[ 1 ] = colors[ b ];
+					face.vertexColors[ 2 ] = colors[ c ];
+
+				}
+
+				if ( hasUvs ) {
+
+					u1 = uvs[ a * 2 ];
+					v1 = uvs[ a * 2 + 1 ];
+
+					u2 = uvs[ b * 2 ];
+					v2 = uvs[ b * 2 + 1 ];
+
+					u3 = uvs[ c * 2 ];
+					v3 = uvs[ c * 2 + 1 ];
+
+					uv3( scope.faceVertexUvs[ 0 ], u1, v1, u2, v2, u3, v3 );
+
+				}
+
+			}
+
+		}
+
+	};
+
+	function vertex ( scope, x, y, z ) {
+
+		scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) );
+
+	};
+
+	function f3 ( scope, a, b, c, mi ) {
+
+		var face = new THREE.Face3( a, b, c, null, null, mi );
+
+		scope.faces.push( face );
+
+		return face;
+
+	};
+
+	function f3n ( scope, normals, a, b, c, mi, na, nb, nc ) {
+
+		var nax = normals[ na * 3     ],
+			nay = normals[ na * 3 + 1 ],
+			naz = normals[ na * 3 + 2 ],
+
+			nbx = normals[ nb * 3     ],
+			nby = normals[ nb * 3 + 1 ],
+			nbz = normals[ nb * 3 + 2 ],
+
+			ncx = normals[ nc * 3     ],
+			ncy = normals[ nc * 3 + 1 ],
+			ncz = normals[ nc * 3 + 2 ];
+
+		var na = new THREE.Vector3( nax, nay, naz ),
+			nb = new THREE.Vector3( nbx, nby, nbz ),
+			nc = new THREE.Vector3( ncx, ncy, ncz );
+
+		var face = new THREE.Face3( a, b, c, [ na, nb, nc ], null, mi );
+
+		scope.faces.push( face );
+
+		return face;
+
+	};
+
+	function uv3 ( where, u1, v1, u2, v2, u3, v3 ) {
+
+		var uv = [];
+		uv.push( new THREE.UV( u1, v1 ) );
+		uv.push( new THREE.UV( u2, v2 ) );
+		uv.push( new THREE.UV( u3, v3 ) );
+		where.push( uv );
+
+	};
+
+	Model.prototype = new THREE.Geometry();
+	Model.prototype.constructor = Model;
+
+	callback( new Model() );
+
+};
diff --git a/emperor/support_files/js/js/ctm/CTMWorker.js b/emperor/support_files/js/js/ctm/CTMWorker.js
new file mode 100644
index 0000000..cec035f
--- /dev/null
+++ b/emperor/support_files/js/js/ctm/CTMWorker.js
@@ -0,0 +1,19 @@
+importScripts( "lzma.js", "ctm.js" );
+
+self.onmessage = function( event ) {
+
+	var files = [];
+
+	for ( var i = 0; i < event.data.offsets.length; i ++ ) {
+
+		var stream = new CTM.Stream( event.data.data );
+		stream.offset = event.data.offsets[ i ];
+
+		files[ i ] = new CTM.File( stream );
+
+	}
+
+	self.postMessage( files );
+	self.close();
+
+}
diff --git a/emperor/support_files/js/js/ctm/ctm.js b/emperor/support_files/js/js/ctm/ctm.js
new file mode 100644
index 0000000..4675cc1
--- /dev/null
+++ b/emperor/support_files/js/js/ctm/ctm.js
@@ -0,0 +1,626 @@
+
+var CTM = CTM || {};
+
+CTM.CompressionMethod = {
+  RAW: 0x00574152,
+  MG1: 0x0031474d,
+  MG2: 0x0032474d
+};
+
+CTM.Flags = {
+  NORMALS: 0x00000001
+};
+
+CTM.File = function(stream){
+  this.load(stream);
+};
+
+CTM.File.prototype.load = function(stream){
+  this.header = new CTM.FileHeader(stream);
+
+  this.body = new CTM.FileBody(this.header);
+  
+  this.getReader().read(stream, this.body);
+};
+
+CTM.File.prototype.getReader = function(){
+  var reader;
+
+  switch(this.header.compressionMethod){
+    case CTM.CompressionMethod.RAW:
+      reader = new CTM.ReaderRAW();
+      break;
+    case CTM.CompressionMethod.MG1:
+      reader = new CTM.ReaderMG1();
+      break;
+    case CTM.CompressionMethod.MG2:
+      reader = new CTM.ReaderMG2();
+      break;
+  }
+
+  return reader;
+};
+
+CTM.FileHeader = function(stream){
+  stream.readInt32(); //magic "OCTM"
+  this.fileFormat = stream.readInt32();
+  this.compressionMethod = stream.readInt32();
+  this.vertexCount = stream.readInt32();
+  this.triangleCount = stream.readInt32();
+  this.uvMapCount = stream.readInt32();
+  this.attrMapCount = stream.readInt32();
+  this.flags = stream.readInt32();
+  this.comment = stream.readString();
+};
+
+CTM.FileHeader.prototype.hasNormals = function(){
+  return this.flags & CTM.Flags.NORMALS;
+};
+
+CTM.FileBody = function(header){
+  var i = header.triangleCount * 3,
+      v = header.vertexCount * 3,
+      n = header.hasNormals()? header.vertexCount * 3: 0,
+      u = header.vertexCount * 2,
+      a = header.vertexCount * 4,
+      j = 0;
+
+  var data = new ArrayBuffer(
+    (i + v + n + (u * header.uvMapCount) + (a * header.attrMapCount) ) * 4);
+
+  this.indices = new Uint32Array(data, 0, i);
+
+  this.vertices = new Float32Array(data, i * 4, v);
+
+  if ( header.hasNormals() ){
+    this.normals = new Float32Array(data, (i + v) * 4, n);
+  }
+  
+  if (header.uvMapCount){
+    this.uvMaps = [];
+    for (j = 0; j < header.uvMapCount; ++ j){
+      this.uvMaps[j] = {uv: new Float32Array(data,
+        (i + v + n + (j * u) ) * 4, u) };
+    }
+  }
+  
+  if (header.attrMapCount){
+    this.attrMaps = [];
+    for (j = 0; j < header.attrMapCount; ++ j){
+      this.attrMaps[j] = {attr: new Float32Array(data,
+        (i + v + n + (u * header.uvMapCount) + (j * a) ) * 4, a) };
+    }
+  }
+};
+
+CTM.FileMG2Header = function(stream){
+  stream.readInt32(); //magic "MG2H"
+  this.vertexPrecision = stream.readFloat32();
+  this.normalPrecision = stream.readFloat32();
+  this.lowerBoundx = stream.readFloat32();
+  this.lowerBoundy = stream.readFloat32();
+  this.lowerBoundz = stream.readFloat32();
+  this.higherBoundx = stream.readFloat32();
+  this.higherBoundy = stream.readFloat32();
+  this.higherBoundz = stream.readFloat32();
+  this.divx = stream.readInt32();
+  this.divy = stream.readInt32();
+  this.divz = stream.readInt32();
+  
+  this.sizex = (this.higherBoundx - this.lowerBoundx) / this.divx;
+  this.sizey = (this.higherBoundy - this.lowerBoundy) / this.divy;
+  this.sizez = (this.higherBoundz - this.lowerBoundz) / this.divz;
+};
+
+CTM.ReaderRAW = function(){
+};
+
+CTM.ReaderRAW.prototype.read = function(stream, body){
+  this.readIndices(stream, body.indices);
+  this.readVertices(stream, body.vertices);
+  
+  if (body.normals){
+    this.readNormals(stream, body.normals);
+  }
+  if (body.uvMaps){
+    this.readUVMaps(stream, body.uvMaps);
+  }
+  if (body.attrMaps){
+    this.readAttrMaps(stream, body.attrMaps);
+  }
+};
+
+CTM.ReaderRAW.prototype.readIndices = function(stream, indices){
+  stream.readInt32(); //magic "INDX"
+  stream.readArrayInt32(indices);
+};
+
+CTM.ReaderRAW.prototype.readVertices = function(stream, vertices){
+  stream.readInt32(); //magic "VERT"
+  stream.readArrayFloat32(vertices);
+};
+
+CTM.ReaderRAW.prototype.readNormals = function(stream, normals){
+  stream.readInt32(); //magic "NORM"
+  stream.readArrayFloat32(normals);
+};
+
+CTM.ReaderRAW.prototype.readUVMaps = function(stream, uvMaps){
+  var i = 0;
+  for (; i < uvMaps.length; ++ i){
+    stream.readInt32(); //magic "TEXC"
+
+    uvMaps[i].name = stream.readString();
+    uvMaps[i].filename = stream.readString();
+    stream.readArrayFloat32(uvMaps[i].uv);
+  }
+};
+
+CTM.ReaderRAW.prototype.readAttrMaps = function(stream, attrMaps){
+  var i = 0;
+  for (; i < attrMaps.length; ++ i){
+    stream.readInt32(); //magic "ATTR"
+
+    attrMaps[i].name = stream.readString();
+    stream.readArrayFloat32(attrMaps[i].attr);
+  }
+};
+
+CTM.ReaderMG1 = function(){
+};
+
+CTM.ReaderMG1.prototype.read = function(stream, body){
+  this.readIndices(stream, body.indices);
+  this.readVertices(stream, body.vertices);
+  
+  if (body.normals){
+    this.readNormals(stream, body.normals);
+  }
+  if (body.uvMaps){
+    this.readUVMaps(stream, body.uvMaps);
+  }
+  if (body.attrMaps){
+    this.readAttrMaps(stream, body.attrMaps);
+  }
+};
+
+CTM.ReaderMG1.prototype.readIndices = function(stream, indices){
+  stream.readInt32(); //magic "INDX"
+  stream.readInt32(); //packed size
+  
+  var interleaved = new CTM.InterleavedStream(indices, 3);
+  LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+
+  CTM.restoreIndices(indices, indices.length);
+};
+
+CTM.ReaderMG1.prototype.readVertices = function(stream, vertices){
+  stream.readInt32(); //magic "VERT"
+  stream.readInt32(); //packed size
+  
+  var interleaved = new CTM.InterleavedStream(vertices, 1);
+  LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+};
+
+CTM.ReaderMG1.prototype.readNormals = function(stream, normals){
+  stream.readInt32(); //magic "NORM"
+  stream.readInt32(); //packed size
+
+  var interleaved = new CTM.InterleavedStream(normals, 3);
+  LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+};
+
+CTM.ReaderMG1.prototype.readUVMaps = function(stream, uvMaps){
+  var i = 0;
+  for (; i < uvMaps.length; ++ i){
+    stream.readInt32(); //magic "TEXC"
+
+    uvMaps[i].name = stream.readString();
+    uvMaps[i].filename = stream.readString();
+    
+    stream.readInt32(); //packed size
+
+    var interleaved = new CTM.InterleavedStream(uvMaps[i].uv, 2);
+    LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+  }
+};
+
+CTM.ReaderMG1.prototype.readAttrMaps = function(stream, attrMaps){
+  var i = 0;
+  for (; i < attrMaps.length; ++ i){
+    stream.readInt32(); //magic "ATTR"
+
+    attrMaps[i].name = stream.readString();
+    
+    stream.readInt32(); //packed size
+
+    var interleaved = new CTM.InterleavedStream(attrMaps[i].attr, 4);
+    LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+  }
+};
+
+CTM.ReaderMG2 = function(){
+};
+
+CTM.ReaderMG2.prototype.read = function(stream, body){
+  this.MG2Header = new CTM.FileMG2Header(stream);
+  
+  this.readVertices(stream, body.vertices);
+  this.readIndices(stream, body.indices);
+  
+  if (body.normals){
+    this.readNormals(stream, body);
+  }
+  if (body.uvMaps){
+    this.readUVMaps(stream, body.uvMaps);
+  }
+  if (body.attrMaps){
+    this.readAttrMaps(stream, body.attrMaps);
+  }
+};
+
+CTM.ReaderMG2.prototype.readVertices = function(stream, vertices){
+  stream.readInt32(); //magic "VERT"
+  stream.readInt32(); //packed size
+
+  var interleaved = new CTM.InterleavedStream(vertices, 3);
+  LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+  
+  var gridIndices = this.readGridIndices(stream, vertices);
+  
+  CTM.restoreVertices(vertices, this.MG2Header, gridIndices, this.MG2Header.vertexPrecision);
+};
+
+CTM.ReaderMG2.prototype.readGridIndices = function(stream, vertices){
+  stream.readInt32(); //magic "GIDX"
+  stream.readInt32(); //packed size
+  
+  var gridIndices = new Uint32Array(vertices.length / 3);
+  
+  var interleaved = new CTM.InterleavedStream(gridIndices, 1);
+  LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+  
+  CTM.restoreGridIndices(gridIndices, gridIndices.length);
+  
+  return gridIndices;
+};
+
+CTM.ReaderMG2.prototype.readIndices = function(stream, indices){
+  stream.readInt32(); //magic "INDX"
+  stream.readInt32(); //packed size
+
+  var interleaved = new CTM.InterleavedStream(indices, 3);
+  LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+
+  CTM.restoreIndices(indices, indices.length);
+};
+
+CTM.ReaderMG2.prototype.readNormals = function(stream, body){
+  stream.readInt32(); //magic "NORM"
+  stream.readInt32(); //packed size
+
+  var interleaved = new CTM.InterleavedStream(body.normals, 3);
+  LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+
+  var smooth = CTM.calcSmoothNormals(body.indices, body.vertices);
+
+  CTM.restoreNormals(body.normals, smooth, this.MG2Header.normalPrecision);
+};
+
+CTM.ReaderMG2.prototype.readUVMaps = function(stream, uvMaps){
+  var i = 0;
+  for (; i < uvMaps.length; ++ i){
+    stream.readInt32(); //magic "TEXC"
+
+    uvMaps[i].name = stream.readString();
+    uvMaps[i].filename = stream.readString();
+    
+    var precision = stream.readFloat32();
+    
+    stream.readInt32(); //packed size
+
+    var interleaved = new CTM.InterleavedStream(uvMaps[i].uv, 2);
+    LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+    
+    CTM.restoreMap(uvMaps[i].uv, 2, precision);
+  }
+};
+
+CTM.ReaderMG2.prototype.readAttrMaps = function(stream, attrMaps){
+  var i = 0;
+  for (; i < attrMaps.length; ++ i){
+    stream.readInt32(); //magic "ATTR"
+
+    attrMaps[i].name = stream.readString();
+    
+    var precision = stream.readFloat32();
+    
+    stream.readInt32(); //packed size
+
+    var interleaved = new CTM.InterleavedStream(attrMaps[i].attr, 4);
+    LZMA.decompress(stream, stream, interleaved, interleaved.data.length);
+    
+    CTM.restoreMap(attrMaps[i].attr, 4, precision);
+  }
+};
+
+CTM.restoreIndices = function(indices, len){
+  var i = 3;
+  if (len > 0){
+    indices[2] += indices[0];
+  }
+  for (; i < len; i += 3){
+    indices[i] += indices[i - 3];
+    
+    if (indices[i] === indices[i - 3]){
+      indices[i + 1] += indices[i - 2];
+    }else{
+      indices[i + 1] += indices[i];
+    }
+
+    indices[i + 2] += indices[i];
+  }
+};
+
+CTM.restoreGridIndices = function(gridIndices, len){
+  var i = 1;
+  for (; i < len; ++ i){
+    gridIndices[i] += gridIndices[i - 1];
+  }
+};
+
+CTM.restoreVertices = function(vertices, grid, gridIndices, precision){
+  var gridIdx, delta, x, y, z,
+      intVertices = new Uint32Array(vertices.buffer, vertices.byteOffset, vertices.length),
+      ydiv = grid.divx, zdiv = ydiv * grid.divy,
+      prevGridIdx = 0x7fffffff, prevDelta = 0,
+      i = 0, j = 0, len = gridIndices.length;
+
+  for (; i < len; j += 3){
+    x = gridIdx = gridIndices[i ++];
+    
+    z = ~~(x / zdiv);
+    x -= ~~(z * zdiv);
+    y = ~~(x / ydiv);
+    x -= ~~(y * ydiv);
+
+    delta = intVertices[j];
+    if (gridIdx === prevGridIdx){
+      delta += prevDelta;
+    }
+
+    vertices[j]     = grid.lowerBoundx +
+      x * grid.sizex + precision * delta;
+    vertices[j + 1] = grid.lowerBoundy +
+      y * grid.sizey + precision * intVertices[j + 1];
+    vertices[j + 2] = grid.lowerBoundz +
+      z * grid.sizez + precision * intVertices[j + 2];
+
+    prevGridIdx = gridIdx;
+    prevDelta = delta;
+  }
+};
+
+CTM.restoreNormals = function(normals, smooth, precision){
+  var ro, phi, theta, sinPhi,
+      nx, ny, nz, by, bz, len,
+      intNormals = new Uint32Array(normals.buffer, normals.byteOffset, normals.length),
+      i = 0, k = normals.length,
+      PI_DIV_2 = 3.141592653589793238462643 * 0.5;
+
+  for (; i < k; i += 3){
+    ro = intNormals[i] * precision;
+    phi = intNormals[i + 1];
+
+    if (phi === 0){
+      normals[i]     = smooth[i]     * ro;
+      normals[i + 1] = smooth[i + 1] * ro;
+      normals[i + 2] = smooth[i + 2] * ro;
+    }else{
+      
+      if (phi <= 4){
+        theta = (intNormals[i + 2] - 2) * PI_DIV_2;
+      }else{
+        theta = ( (intNormals[i + 2] * 4 / phi) - 2) * PI_DIV_2;
+      }
+      
+      phi *= precision * PI_DIV_2;
+      sinPhi = ro * Math.sin(phi);
+
+      nx = sinPhi * Math.cos(theta);
+      ny = sinPhi * Math.sin(theta);
+      nz = ro * Math.cos(phi);
+
+      bz = smooth[i + 1];
+      by = smooth[i] - smooth[i + 2];
+
+      len = Math.sqrt(2 * bz * bz + by * by);
+      if (len > 1e-20){
+        by /= len;
+        bz /= len;
+      }
+
+      normals[i]     = smooth[i]     * nz +
+        (smooth[i + 1] * bz - smooth[i + 2] * by) * ny - bz * nx;
+      normals[i + 1] = smooth[i + 1] * nz -
+        (smooth[i + 2]      + smooth[i]   ) * bz  * ny + by * nx;
+      normals[i + 2] = smooth[i + 2] * nz +
+        (smooth[i]     * by + smooth[i + 1] * bz) * ny + bz * nx;
+    }
+  }
+};
+
+CTM.restoreMap = function(map, count, precision){
+  var delta, value,
+      intMap = new Uint32Array(map.buffer, map.byteOffset, map.length),
+      i = 0, j, len = map.length;
+
+  for (; i < count; ++ i){
+    delta = 0;
+
+    for (j = i; j < len; j += count){
+      value = intMap[j];
+      
+      delta += value & 1? -( (value + 1) >> 1): value >> 1;
+      
+      map[j] = delta * precision;
+    }
+  }
+};
+
+CTM.calcSmoothNormals = function(indices, vertices){
+  var smooth = new Float32Array(vertices.length),
+      indx, indy, indz, nx, ny, nz,
+      v1x, v1y, v1z, v2x, v2y, v2z, len,
+      i, k;
+
+  for (i = 0, k = indices.length; i < k;){
+    indx = indices[i ++] * 3;
+    indy = indices[i ++] * 3;
+    indz = indices[i ++] * 3;
+
+    v1x = vertices[indy]     - vertices[indx];
+    v2x = vertices[indz]     - vertices[indx];
+    v1y = vertices[indy + 1] - vertices[indx + 1];
+    v2y = vertices[indz + 1] - vertices[indx + 1];
+    v1z = vertices[indy + 2] - vertices[indx + 2];
+    v2z = vertices[indz + 2] - vertices[indx + 2];
+    
+    nx = v1y * v2z - v1z * v2y;
+    ny = v1z * v2x - v1x * v2z;
+    nz = v1x * v2y - v1y * v2x;
+    
+    len = Math.sqrt(nx * nx + ny * ny + nz * nz);
+    if (len > 1e-10){
+      nx /= len;
+      ny /= len;
+      nz /= len;
+    }
+    
+    smooth[indx]     += nx;
+    smooth[indx + 1] += ny;
+    smooth[indx + 2] += nz;
+    smooth[indy]     += nx;
+    smooth[indy + 1] += ny;
+    smooth[indy + 2] += nz;
+    smooth[indz]     += nx;
+    smooth[indz + 1] += ny;
+    smooth[indz + 2] += nz;
+  }
+
+  for (i = 0, k = smooth.length; i < k; i += 3){
+    len = Math.sqrt(smooth[i] * smooth[i] + 
+      smooth[i + 1] * smooth[i + 1] +
+      smooth[i + 2] * smooth[i + 2]);
+
+    if(len > 1e-10){
+      smooth[i]     /= len;
+      smooth[i + 1] /= len;
+      smooth[i + 2] /= len;
+    }
+  }
+
+  return smooth;
+};
+
+CTM.isLittleEndian = (function(){
+  var buffer = new ArrayBuffer(2),
+      bytes = new Uint8Array(buffer),
+      ints = new Uint16Array(buffer);
+
+  bytes[0] = 1;
+
+  return ints[0] === 1;
+}());
+
+CTM.InterleavedStream = function(data, count){
+  this.data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+  this.offset = CTM.isLittleEndian? 3: 0;
+  this.count = count * 4;
+  this.len = this.data.length;
+};
+
+CTM.InterleavedStream.prototype.writeByte = function(value){
+  this.data[this.offset] = value;
+  
+  this.offset += this.count;
+  if (this.offset >= this.len){
+  
+    this.offset -= this.len - 4;
+    if (this.offset >= this.count){
+    
+      this.offset -= this.count + (CTM.isLittleEndian? 1: -1);
+    }
+  }
+};
+
+CTM.Stream = function(data){
+  this.data = data;
+  this.offset = 0;
+};
+
+CTM.Stream.prototype.TWO_POW_MINUS23 = Math.pow(2, -23);
+
+CTM.Stream.prototype.TWO_POW_MINUS126 = Math.pow(2, -126);
+
+CTM.Stream.prototype.readByte = function(){
+  return this.data.charCodeAt(this.offset ++) & 0xff;
+};
+
+CTM.Stream.prototype.readInt32 = function(){
+  var i = this.readByte();
+  i |= this.readByte() << 8;
+  i |= this.readByte() << 16;
+  return i | (this.readByte() << 24);
+};
+
+CTM.Stream.prototype.readFloat32 = function(){
+  var m = this.readByte();
+  m += this.readByte() << 8;
+
+  var b1 = this.readByte();
+  var b2 = this.readByte();
+
+  m += (b1 & 0x7f) << 16; 
+  var e = ( (b2 & 0x7f) << 1) | ( (b1 & 0x80) >>> 7);
+  var s = b2 & 0x80? -1: 1;
+
+  if (e === 255){
+    return m !== 0? NaN: s * Infinity;
+  }
+  if (e > 0){
+    return s * (1 + (m * this.TWO_POW_MINUS23) ) * Math.pow(2, e - 127);
+  }
+  if (m !== 0){
+    return s * m * this.TWO_POW_MINUS126;
+  }
+  return s * 0;
+};
+
+CTM.Stream.prototype.readString = function(){
+  var len = this.readInt32();
+
+  this.offset += len;
+
+  return this.data.substr(this.offset - len, len);
+};
+
+CTM.Stream.prototype.readArrayInt32 = function(array){
+  var i = 0, len = array.length;
+  
+  while(i < len){
+    array[i ++] = this.readInt32();
+  }
+
+  return array;
+};
+
+CTM.Stream.prototype.readArrayFloat32 = function(array){
+  var i = 0, len = array.length;
+
+  while(i < len){
+    array[i ++] = this.readFloat32();
+  }
+
+  return array;
+};
diff --git a/emperor/support_files/js/js/ctm/license/OpenCTM.txt b/emperor/support_files/js/js/ctm/license/OpenCTM.txt
new file mode 100644
index 0000000..0e66fa7
--- /dev/null
+++ b/emperor/support_files/js/js/ctm/license/OpenCTM.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2009-2010 Marcus Geelnard
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+    1. The origin of this software must not be misrepresented; you must not
+    claim that you wrote the original software. If you use this software
+    in a product, an acknowledgment in the product documentation would be
+    appreciated but is not required.
+
+    2. Altered source versions must be plainly marked as such, and must not
+    be misrepresented as being the original software.
+
+    3. This notice may not be removed or altered from any source
+    distribution.
diff --git a/emperor/support_files/js/js/ctm/license/js-lzma.txt b/emperor/support_files/js/js/ctm/license/js-lzma.txt
new file mode 100644
index 0000000..8abd005
--- /dev/null
+++ b/emperor/support_files/js/js/ctm/license/js-lzma.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/emperor/support_files/js/js/ctm/license/js-openctm.txt b/emperor/support_files/js/js/ctm/license/js-openctm.txt
new file mode 100644
index 0000000..8abd005
--- /dev/null
+++ b/emperor/support_files/js/js/ctm/license/js-openctm.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/emperor/support_files/js/js/ctm/lzma.js b/emperor/support_files/js/js/ctm/lzma.js
new file mode 100644
index 0000000..d5c5db3
--- /dev/null
+++ b/emperor/support_files/js/js/ctm/lzma.js
@@ -0,0 +1,510 @@
+
+var LZMA = LZMA || {};
+
+LZMA.OutWindow = function(){
+  this._windowSize = 0;
+};
+
+LZMA.OutWindow.prototype.create = function(windowSize){
+  if ( (!this._buffer) || (this._windowSize !== windowSize) ){
+    this._buffer = [];
+  }
+  this._windowSize = windowSize;
+  this._pos = 0;
+  this._streamPos = 0;
+};
+
+LZMA.OutWindow.prototype.flush = function(){
+  var size = this._pos - this._streamPos;
+  if (size !== 0){
+    while(size --){
+      this._stream.writeByte(this._buffer[this._streamPos ++]);
+    }
+    if (this._pos >= this._windowSize){
+      this._pos = 0;
+    }
+    this._streamPos = this._pos;
+  }
+};
+
+LZMA.OutWindow.prototype.releaseStream = function(){
+  this.flush();
+  this._stream = null;
+};
+
+LZMA.OutWindow.prototype.setStream = function(stream){
+  this.releaseStream();
+  this._stream = stream;
+};
+
+LZMA.OutWindow.prototype.init = function(solid){
+  if (!solid){
+    this._streamPos = 0;
+    this._pos = 0;
+  }
+};
+
+LZMA.OutWindow.prototype.copyBlock = function(distance, len){
+  var pos = this._pos - distance - 1;
+  if (pos < 0){
+    pos += this._windowSize;
+  }
+  while(len --){
+    if (pos >= this._windowSize){
+      pos = 0;
+    }
+    this._buffer[this._pos ++] = this._buffer[pos ++];
+    if (this._pos >= this._windowSize){
+      this.flush();
+    }
+  }
+};
+
+LZMA.OutWindow.prototype.putByte = function(b){
+  this._buffer[this._pos ++] = b;
+  if (this._pos >= this._windowSize){
+    this.flush();
+  }
+};
+
+LZMA.OutWindow.prototype.getByte = function(distance){
+  var pos = this._pos - distance - 1;
+  if (pos < 0){
+    pos += this._windowSize;
+  }
+  return this._buffer[pos];
+};
+
+LZMA.RangeDecoder = function(){
+};
+
+LZMA.RangeDecoder.prototype.setStream = function(stream){
+  this._stream = stream;
+};
+
+LZMA.RangeDecoder.prototype.releaseStream = function(){
+  this._stream = null;
+};
+
+LZMA.RangeDecoder.prototype.init = function(){
+  var i = 5;
+
+  this._code = 0;
+  this._range = -1;
+  
+  while(i --){
+    this._code = (this._code << 8) | this._stream.readByte();
+  }
+};
+
+LZMA.RangeDecoder.prototype.decodeDirectBits = function(numTotalBits){
+  var result = 0, i = numTotalBits, t;
+
+  while(i --){
+    this._range >>>= 1;
+    t = (this._code - this._range) >>> 31;
+    this._code -= this._range & (t - 1);
+    result = (result << 1) | (1 - t);
+
+    if ( (this._range & 0xff000000) === 0){
+      this._code = (this._code << 8) | this._stream.readByte();
+      this._range <<= 8;
+    }
+  }
+
+  return result;
+};
+
+LZMA.RangeDecoder.prototype.decodeBit = function(probs, index){
+  var prob = probs[index],
+      newBound = (this._range >>> 11) * prob;
+
+  if ( (this._code ^ 0x80000000) < (newBound ^ 0x80000000) ){
+    this._range = newBound;
+    probs[index] += (2048 - prob) >>> 5;
+    if ( (this._range & 0xff000000) === 0){
+      this._code = (this._code << 8) | this._stream.readByte();
+      this._range <<= 8;
+    }
+    return 0;
+  }
+
+  this._range -= newBound;
+  this._code -= newBound;
+  probs[index] -= prob >>> 5;
+  if ( (this._range & 0xff000000) === 0){
+    this._code = (this._code << 8) | this._stream.readByte();
+    this._range <<= 8;
+  }
+  return 1;
+};
+
+LZMA.initBitModels = function(probs, len){
+  while(len --){
+    probs[len] = 1024;
+  }
+};
+
+LZMA.BitTreeDecoder = function(numBitLevels){
+  this._models = [];
+  this._numBitLevels = numBitLevels;
+};
+
+LZMA.BitTreeDecoder.prototype.init = function(){
+  LZMA.initBitModels(this._models, 1 << this._numBitLevels);
+};
+
+LZMA.BitTreeDecoder.prototype.decode = function(rangeDecoder){
+  var m = 1, i = this._numBitLevels;
+
+  while(i --){
+    m = (m << 1) | rangeDecoder.decodeBit(this._models, m);
+  }
+  return m - (1 << this._numBitLevels);
+};
+
+LZMA.BitTreeDecoder.prototype.reverseDecode = function(rangeDecoder){
+  var m = 1, symbol = 0, i = 0, bit;
+
+  for (; i < this._numBitLevels; ++ i){
+    bit = rangeDecoder.decodeBit(this._models, m);
+    m = (m << 1) | bit;
+    symbol |= bit << i;
+  }
+  return symbol;
+};
+
+LZMA.reverseDecode2 = function(models, startIndex, rangeDecoder, numBitLevels){
+  var m = 1, symbol = 0, i = 0, bit;
+
+  for (; i < numBitLevels; ++ i){
+    bit = rangeDecoder.decodeBit(models, startIndex + m);
+    m = (m << 1) | bit;
+    symbol |= bit << i;
+  }
+  return symbol;
+};
+
+LZMA.LenDecoder = function(){
+  this._choice = [];
+  this._lowCoder = [];
+  this._midCoder = [];
+  this._highCoder = new LZMA.BitTreeDecoder(8);
+  this._numPosStates = 0;
+};
+
+LZMA.LenDecoder.prototype.create = function(numPosStates){
+  for (; this._numPosStates < numPosStates; ++ this._numPosStates){
+    this._lowCoder[this._numPosStates] = new LZMA.BitTreeDecoder(3);
+    this._midCoder[this._numPosStates] = new LZMA.BitTreeDecoder(3);
+  }
+};
+
+LZMA.LenDecoder.prototype.init = function(){
+  var i = this._numPosStates;
+  LZMA.initBitModels(this._choice, 2);
+  while(i --){
+    this._lowCoder[i].init();
+    this._midCoder[i].init();
+  }
+  this._highCoder.init();
+};
+
+LZMA.LenDecoder.prototype.decode = function(rangeDecoder, posState){
+  if (rangeDecoder.decodeBit(this._choice, 0) === 0){
+    return this._lowCoder[posState].decode(rangeDecoder);
+  }
+  if (rangeDecoder.decodeBit(this._choice, 1) === 0){
+    return 8 + this._midCoder[posState].decode(rangeDecoder);
+  }
+  return 16 + this._highCoder.decode(rangeDecoder);
+};
+
+LZMA.Decoder2 = function(){
+  this._decoders = [];
+};
+
+LZMA.Decoder2.prototype.init = function(){
+  LZMA.initBitModels(this._decoders, 0x300);
+};
+
+LZMA.Decoder2.prototype.decodeNormal = function(rangeDecoder){
+  var symbol = 1;
+
+  do{
+    symbol = (symbol << 1) | rangeDecoder.decodeBit(this._decoders, symbol);
+  }while(symbol < 0x100);
+
+  return symbol & 0xff;
+};
+
+LZMA.Decoder2.prototype.decodeWithMatchByte = function(rangeDecoder, matchByte){
+  var symbol = 1, matchBit, bit;
+
+  do{
+    matchBit = (matchByte >> 7) & 1;
+    matchByte <<= 1;
+    bit = rangeDecoder.decodeBit(this._decoders, ( (1 + matchBit) << 8) + symbol);
+    symbol = (symbol << 1) | bit;
+    if (matchBit !== bit){
+      while(symbol < 0x100){
+        symbol = (symbol << 1) | rangeDecoder.decodeBit(this._decoders, symbol);
+      }
+      break;
+    }
+  }while(symbol < 0x100);
+
+  return symbol & 0xff;
+};
+
+LZMA.LiteralDecoder = function(){
+};
+
+LZMA.LiteralDecoder.prototype.create = function(numPosBits, numPrevBits){
+  var i;
+
+  if (this._coders
+    && (this._numPrevBits === numPrevBits)
+    && (this._numPosBits === numPosBits) ){
+    return;
+  }
+  this._numPosBits = numPosBits;
+  this._posMask = (1 << numPosBits) - 1;
+  this._numPrevBits = numPrevBits;
+
+  this._coders = [];
+
+  i = 1 << (this._numPrevBits + this._numPosBits);
+  while(i --){
+    this._coders[i] = new LZMA.Decoder2();
+  }
+};
+
+LZMA.LiteralDecoder.prototype.init = function(){
+  var i = 1 << (this._numPrevBits + this._numPosBits);
+  while(i --){
+    this._coders[i].init();
+  }
+};
+
+LZMA.LiteralDecoder.prototype.getDecoder = function(pos, prevByte){
+  return this._coders[( (pos & this._posMask) << this._numPrevBits)
+    + ( (prevByte & 0xff) >>> (8 - this._numPrevBits) )];
+};
+
+LZMA.Decoder = function(){
+  this._outWindow = new LZMA.OutWindow();
+  this._rangeDecoder = new LZMA.RangeDecoder();
+  this._isMatchDecoders = [];
+  this._isRepDecoders = [];
+  this._isRepG0Decoders = [];
+  this._isRepG1Decoders = [];
+  this._isRepG2Decoders = [];
+  this._isRep0LongDecoders = [];
+  this._posSlotDecoder = [];
+  this._posDecoders = [];
+  this._posAlignDecoder = new LZMA.BitTreeDecoder(4);
+  this._lenDecoder = new LZMA.LenDecoder();
+  this._repLenDecoder = new LZMA.LenDecoder();
+  this._literalDecoder = new LZMA.LiteralDecoder();
+  this._dictionarySize = -1;
+  this._dictionarySizeCheck = -1;
+
+  this._posSlotDecoder[0] = new LZMA.BitTreeDecoder(6);
+  this._posSlotDecoder[1] = new LZMA.BitTreeDecoder(6);
+  this._posSlotDecoder[2] = new LZMA.BitTreeDecoder(6);
+  this._posSlotDecoder[3] = new LZMA.BitTreeDecoder(6);
+};
+
+LZMA.Decoder.prototype.setDictionarySize = function(dictionarySize){
+  if (dictionarySize < 0){
+    return false;
+  }
+  if (this._dictionarySize !== dictionarySize){
+    this._dictionarySize = dictionarySize;
+    this._dictionarySizeCheck = Math.max(this._dictionarySize, 1);
+    this._outWindow.create( Math.max(this._dictionarySizeCheck, 4096) );
+  }
+  return true;
+};
+
+LZMA.Decoder.prototype.setLcLpPb = function(lc, lp, pb){
+  var numPosStates = 1 << pb;
+
+  if (lc > 8 || lp > 4 || pb > 4){
+    return false;
+  }
+
+  this._literalDecoder.create(lp, lc);
+
+  this._lenDecoder.create(numPosStates);
+  this._repLenDecoder.create(numPosStates);
+  this._posStateMask = numPosStates - 1;
+
+  return true;
+};
+
+LZMA.Decoder.prototype.init = function(){
+  var i = 4;
+
+  this._outWindow.init(false);
+
+  LZMA.initBitModels(this._isMatchDecoders, 192);
+  LZMA.initBitModels(this._isRep0LongDecoders, 192);
+  LZMA.initBitModels(this._isRepDecoders, 12);
+  LZMA.initBitModels(this._isRepG0Decoders, 12);
+  LZMA.initBitModels(this._isRepG1Decoders, 12);
+  LZMA.initBitModels(this._isRepG2Decoders, 12);
+  LZMA.initBitModels(this._posDecoders, 114);
+
+  this._literalDecoder.init();
+
+  while(i --){
+    this._posSlotDecoder[i].init();
+  }
+
+  this._lenDecoder.init();
+  this._repLenDecoder.init();
+  this._posAlignDecoder.init();
+  this._rangeDecoder.init();
+};
+
+LZMA.Decoder.prototype.decode = function(inStream, outStream, outSize){
+  var state = 0, rep0 = 0, rep1 = 0, rep2 = 0, rep3 = 0, nowPos64 = 0, prevByte = 0,
+      posState, decoder2, len, distance, posSlot, numDirectBits;
+
+  this._rangeDecoder.setStream(inStream);
+  this._outWindow.setStream(outStream);
+
+  this.init();
+
+  while(outSize < 0 || nowPos64 < outSize){
+    posState = nowPos64 & this._posStateMask;
+
+    if (this._rangeDecoder.decodeBit(this._isMatchDecoders, (state << 4) + posState) === 0){
+      decoder2 = this._literalDecoder.getDecoder(nowPos64 ++, prevByte);
+
+      if (state >= 7){
+        prevByte = decoder2.decodeWithMatchByte(this._rangeDecoder, this._outWindow.getByte(rep0) );
+      }else{
+        prevByte = decoder2.decodeNormal(this._rangeDecoder);
+      }
+      this._outWindow.putByte(prevByte);
+
+      state = state < 4? 0: state - (state < 10? 3: 6);
+
+    }else{
+
+      if (this._rangeDecoder.decodeBit(this._isRepDecoders, state) === 1){
+        len = 0;
+        if (this._rangeDecoder.decodeBit(this._isRepG0Decoders, state) === 0){
+          if (this._rangeDecoder.decodeBit(this._isRep0LongDecoders, (state << 4) + posState) === 0){
+            state = state < 7? 9: 11;
+            len = 1;
+          }
+        }else{
+          if (this._rangeDecoder.decodeBit(this._isRepG1Decoders, state) === 0){
+            distance = rep1;
+          }else{
+            if (this._rangeDecoder.decodeBit(this._isRepG2Decoders, state) === 0){
+              distance = rep2;
+            }else{
+              distance = rep3;
+              rep3 = rep2;
+            }
+            rep2 = rep1;
+          }
+          rep1 = rep0;
+          rep0 = distance;
+        }
+        if (len === 0){
+          len = 2 + this._repLenDecoder.decode(this._rangeDecoder, posState);
+          state = state < 7? 8: 11;
+        }
+      }else{
+        rep3 = rep2;
+        rep2 = rep1;
+        rep1 = rep0;
+
+        len = 2 + this._lenDecoder.decode(this._rangeDecoder, posState);
+        state = state < 7? 7: 10;
+
+        posSlot = this._posSlotDecoder[len <= 5? len - 2: 3].decode(this._rangeDecoder);
+        if (posSlot >= 4){
+
+          numDirectBits = (posSlot >> 1) - 1;
+          rep0 = (2 | (posSlot & 1) ) << numDirectBits;
+
+          if (posSlot < 14){
+            rep0 += LZMA.reverseDecode2(this._posDecoders,
+                rep0 - posSlot - 1, this._rangeDecoder, numDirectBits);
+          }else{
+            rep0 += this._rangeDecoder.decodeDirectBits(numDirectBits - 4) << 4;
+            rep0 += this._posAlignDecoder.reverseDecode(this._rangeDecoder);
+            if (rep0 < 0){
+              if (rep0 === -1){
+                break;
+              }
+              return false;
+            }
+          }
+        }else{
+          rep0 = posSlot;
+        }
+      }
+
+      if (rep0 >= nowPos64 || rep0 >= this._dictionarySizeCheck){
+        return false;
+      }
+
+      this._outWindow.copyBlock(rep0, len);
+      nowPos64 += len;
+      prevByte = this._outWindow.getByte(0);
+    }
+  }
+
+  this._outWindow.flush();
+  this._outWindow.releaseStream();
+  this._rangeDecoder.releaseStream();
+
+  return true;
+};
+
+LZMA.Decoder.prototype.setDecoderProperties = function(properties){
+  var value, lc, lp, pb, dictionarySize;
+
+  if (properties.size < 5){
+    return false;
+  }
+
+  value = properties.readByte();
+  lc = value % 9;
+  value = ~~(value / 9);
+  lp = value % 5;
+  pb = ~~(value / 5);
+
+  if ( !this.setLcLpPb(lc, lp, pb) ){
+    return false;
+  }
+
+  dictionarySize = properties.readByte();
+  dictionarySize |= properties.readByte() << 8;
+  dictionarySize |= properties.readByte() << 16;
+  dictionarySize += properties.readByte() * 16777216;
+
+  return this.setDictionarySize(dictionarySize);
+};
+
+LZMA.decompress = function(properties, inStream, outStream, outSize){
+  var decoder = new LZMA.Decoder();
+
+  if ( !decoder.setDecoderProperties(properties) ){
+    throw "Incorrect stream properties";
+  }
+
+  if ( !decoder.decode(inStream, outStream, outSize) ){
+    throw "Error in data stream";
+  }
+
+  return true;
+};
diff --git a/emperor/support_files/js/js/postprocessing/BloomPass.js b/emperor/support_files/js/js/postprocessing/BloomPass.js
new file mode 100644
index 0000000..ca2de01
--- /dev/null
+++ b/emperor/support_files/js/js/postprocessing/BloomPass.js
@@ -0,0 +1,100 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.BloomPass = function( strength, kernelSize, sigma, resolution ) {
+
+	strength = ( strength !== undefined ) ? strength : 1;
+	kernelSize = ( kernelSize !== undefined ) ? kernelSize : 25;
+	sigma = ( sigma !== undefined ) ? sigma : 4.0;
+	resolution = ( resolution !== undefined ) ? resolution : 256;
+
+	// render targets
+
+	var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat };
+
+	this.renderTargetX = new THREE.WebGLRenderTarget( resolution, resolution, pars );
+	this.renderTargetY = new THREE.WebGLRenderTarget( resolution, resolution, pars );
+
+	// screen material
+
+	var screenShader = THREE.ShaderExtras[ "screen" ];
+
+	this.screenUniforms = THREE.UniformsUtils.clone( screenShader.uniforms );
+
+	this.screenUniforms[ "opacity" ].value = strength;
+
+	this.materialScreen = new THREE.ShaderMaterial( {
+
+		uniforms: this.screenUniforms,
+		vertexShader: screenShader.vertexShader,
+		fragmentShader: screenShader.fragmentShader,
+		blending: THREE.AdditiveBlending,
+		transparent: true
+
+	} );
+
+	// convolution material
+
+	var convolutionShader = THREE.ShaderExtras[ "convolution" ];
+
+	this.convolutionUniforms = THREE.UniformsUtils.clone( convolutionShader.uniforms );
+
+	this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurx;
+	this.convolutionUniforms[ "cKernel" ].value = THREE.ShaderExtras.buildKernel( sigma );
+
+	this.materialConvolution = new THREE.ShaderMaterial( {
+
+		uniforms: this.convolutionUniforms,
+		vertexShader:   "#define KERNEL_SIZE " + kernelSize + ".0\n" + convolutionShader.vertexShader,
+		fragmentShader: "#define KERNEL_SIZE " + kernelSize + "\n"   + convolutionShader.fragmentShader
+
+	} );
+
+	this.enabled = true;
+	this.needsSwap = false;
+	this.clear = false;
+
+};
+
+THREE.BloomPass.prototype = {
+
+	render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) {
+
+		if ( maskActive ) renderer.context.disable( renderer.context.STENCIL_TEST );
+
+		// Render quad with blured scene into texture (convolution pass 1)
+
+		THREE.EffectComposer.quad.material = this.materialConvolution;
+
+		this.convolutionUniforms[ "tDiffuse" ].texture = readBuffer;
+		this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurX;
+
+		renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera, this.renderTargetX, true );
+
+
+		// Render quad with blured scene into texture (convolution pass 2)
+
+		this.convolutionUniforms[ "tDiffuse" ].texture = this.renderTargetX;
+		this.convolutionUniforms[ "uImageIncrement" ].value = THREE.BloomPass.blurY;
+
+		renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera, this.renderTargetY, true );
+
+		// Render original scene with superimposed blur to texture
+
+		THREE.EffectComposer.quad.material = this.materialScreen;
+
+		this.screenUniforms[ "tDiffuse" ].texture = this.renderTargetY;
+
+		if ( maskActive ) renderer.context.enable( renderer.context.STENCIL_TEST );
+
+		renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera, readBuffer, this.clear );
+
+	}
+
+};
+
+THREE.BloomPass.blurX = new THREE.Vector2( 0.001953125, 0.0 );
+THREE.BloomPass.blurY = new THREE.Vector2( 0.0, 0.001953125 );
+
+
diff --git a/emperor/support_files/js/js/postprocessing/DotScreenPass.js b/emperor/support_files/js/js/postprocessing/DotScreenPass.js
new file mode 100644
index 0000000..0cafe26
--- /dev/null
+++ b/emperor/support_files/js/js/postprocessing/DotScreenPass.js
@@ -0,0 +1,52 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.DotScreenPass = function( center, angle, scale ) {
+
+	var shader = THREE.ShaderExtras[ "dotscreen" ];
+
+	this.uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+	if ( center !== undefined )
+		this.uniforms[ "center" ].value.copy( center );
+
+	if ( angle !== undefined )	this.uniforms[ "angle"].value = angle;
+	if ( scale !== undefined )	this.uniforms[ "scale"].value = scale;
+
+	this.material = new THREE.ShaderMaterial( {
+
+		uniforms: this.uniforms,
+		vertexShader: shader.vertexShader,
+		fragmentShader: shader.fragmentShader
+
+	} );
+
+	this.enabled = true;
+	this.renderToScreen = false;
+	this.needsSwap = true;
+
+};
+
+THREE.DotScreenPass.prototype = {
+
+	render: function ( renderer, writeBuffer, readBuffer, delta ) {
+
+		this.uniforms[ "tDiffuse" ].texture = readBuffer;
+		this.uniforms[ "tSize" ].value.set( readBuffer.width, readBuffer.height );
+
+		THREE.EffectComposer.quad.material = this.material;
+
+		if ( this.renderToScreen ) {
+
+			renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera );
+
+		} else {
+
+			renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera, writeBuffer, false );
+
+		}
+
+	}
+
+};
diff --git a/emperor/support_files/js/js/postprocessing/EffectComposer.js b/emperor/support_files/js/js/postprocessing/EffectComposer.js
new file mode 100644
index 0000000..25ed074
--- /dev/null
+++ b/emperor/support_files/js/js/postprocessing/EffectComposer.js
@@ -0,0 +1,136 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.EffectComposer = function( renderer, renderTarget ) {
+
+	this.renderer = renderer;
+
+	this.renderTarget1 = renderTarget;
+
+	if ( this.renderTarget1 === undefined ) {
+
+		this.renderTargetParameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBufer: false };
+		this.renderTarget1 = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, this.renderTargetParameters );
+
+	}
+
+	this.renderTarget2 = this.renderTarget1.clone();
+
+	this.writeBuffer = this.renderTarget1;
+	this.readBuffer = this.renderTarget2;
+
+	this.passes = [];
+
+	this.copyPass = new THREE.ShaderPass( THREE.ShaderExtras[ "screen" ] );
+
+};
+
+THREE.EffectComposer.prototype = {
+
+	swapBuffers: function() {
+
+		var tmp = this.readBuffer;
+		this.readBuffer = this.writeBuffer;
+		this.writeBuffer = tmp;
+
+	},
+
+	addPass: function ( pass ) {
+
+		this.passes.push( pass );
+
+	},
+
+	render: function ( delta ) {
+
+		this.writeBuffer = this.renderTarget1;
+		this.readBuffer = this.renderTarget2;
+
+		var maskActive = false;
+
+		var pass, i, il = this.passes.length;
+
+		for ( i = 0; i < il; i ++ ) {
+
+			pass = this.passes[ i ];
+
+			if ( !pass.enabled ) continue;
+
+			pass.render( this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive );
+
+			if ( pass.needsSwap ) {
+
+				if ( maskActive ) {
+
+					var context = this.renderer.context;
+
+					context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff );
+
+					this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, delta );
+
+					context.stencilFunc( context.EQUAL, 1, 0xffffffff );
+
+				}
+
+				this.swapBuffers();
+
+			}
+
+			if ( pass instanceof THREE.MaskPass ) {
+
+				maskActive = true;
+
+			} else if ( pass instanceof THREE.ClearMaskPass ) {
+
+				maskActive = false;
+
+			}
+
+		}
+
+	},
+
+	reset: function ( renderTarget ) {
+
+		this.renderTarget1 = renderTarget;
+
+		if ( this.renderTarget1 === undefined ) {
+
+			this.renderTarget1 = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, this.renderTargetParameters );
+
+		}
+
+		this.renderTarget2 = this.renderTarget1.clone();
+
+		this.writeBuffer = this.renderTarget1;
+		this.readBuffer = this.renderTarget2;
+
+		THREE.EffectComposer.quad.scale.set( window.innerWidth, window.innerHeight, 1 );
+
+		THREE.EffectComposer.camera.left = window.innerWidth / - 2;
+		THREE.EffectComposer.camera.right = window.innerWidth / 2;
+		THREE.EffectComposer.camera.top = window.innerHeight / 2;
+		THREE.EffectComposer.camera.bottom = window.innerHeight / - 2;
+
+		THREE.EffectComposer.camera.updateProjectionMatrix();
+
+	}
+
+};
+
+// shared ortho camera
+
+THREE.EffectComposer.camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, -10000, 10000 );
+
+// shared fullscreen quad scene
+
+THREE.EffectComposer.geometry = new THREE.PlaneGeometry( 1, 1 );
+
+THREE.EffectComposer.quad = new THREE.Mesh( THREE.EffectComposer.geometry, null );
+THREE.EffectComposer.quad.position.z = -100;
+THREE.EffectComposer.quad.scale.set( window.innerWidth, window.innerHeight, 1 );
+
+THREE.EffectComposer.scene = new THREE.Scene();
+THREE.EffectComposer.scene.add( THREE.EffectComposer.quad );
+THREE.EffectComposer.scene.add( THREE.EffectComposer.camera );
diff --git a/emperor/support_files/js/js/postprocessing/FilmPass.js b/emperor/support_files/js/js/postprocessing/FilmPass.js
new file mode 100644
index 0000000..3ff6884
--- /dev/null
+++ b/emperor/support_files/js/js/postprocessing/FilmPass.js
@@ -0,0 +1,51 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.FilmPass = function( noiseIntensity, scanlinesIntensity, scanlinesCount, grayscale ) {
+
+	var shader = THREE.ShaderExtras[ "film" ];
+
+	this.uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+	this.material = new THREE.ShaderMaterial( {
+
+		uniforms: this.uniforms,
+		vertexShader: shader.vertexShader,
+		fragmentShader: shader.fragmentShader
+
+	} );
+
+	if ( grayscale !== undefined )	this.uniforms.grayscale.value = grayscale;
+	if ( noiseIntensity !== undefined ) this.uniforms.nIntensity.value = noiseIntensity;
+	if ( scanlinesIntensity !== undefined ) this.uniforms.sIntensity.value = scanlinesIntensity;
+	if ( scanlinesCount !== undefined ) this.uniforms.sCount.value = scanlinesCount;
+
+	this.enabled = true;
+	this.renderToScreen = false;
+	this.needsSwap = true;
+
+};
+
+THREE.FilmPass.prototype = {
+
+	render: function ( renderer, writeBuffer, readBuffer, delta ) {
+
+		this.uniforms[ "tDiffuse" ].texture = readBuffer;
+		this.uniforms[ "time" ].value += delta;
+
+		THREE.EffectComposer.quad.material = this.material;
+
+		if ( this.renderToScreen ) {
+
+			renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera );
+
+		} else {
+
+			renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera, writeBuffer, false );
+
+		}
+
+	}
+
+};
diff --git a/emperor/support_files/js/js/postprocessing/MaskPass.js b/emperor/support_files/js/js/postprocessing/MaskPass.js
new file mode 100644
index 0000000..23238b0
--- /dev/null
+++ b/emperor/support_files/js/js/postprocessing/MaskPass.js
@@ -0,0 +1,86 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.MaskPass = function ( scene, camera ) {
+
+	this.scene = scene;
+	this.camera = camera;
+
+	this.enabled = true;
+	this.clear = true;
+	this.needsSwap = false;
+
+	this.inverse = false;
+
+};
+
+THREE.MaskPass.prototype = {
+
+	render: function ( renderer, writeBuffer, readBuffer, delta ) {
+
+		var context = renderer.context;
+
+		// don't update color or depth
+
+		context.colorMask( false, false, false, false );
+		context.depthMask( false );
+
+		// set up stencil
+
+		var writeValue, clearValue;
+
+		if ( this.inverse ) {
+
+			writeValue = 0;
+			clearValue = 1;
+
+		} else {
+
+			writeValue = 1;
+			clearValue = 0;
+
+		}
+
+		context.enable( context.STENCIL_TEST );
+		context.stencilOp( context.REPLACE, context.REPLACE, context.REPLACE );
+		context.stencilFunc( context.ALWAYS, writeValue, 0xffffffff );
+		context.clearStencil( clearValue );
+
+		// draw into the stencil buffer
+
+		renderer.render( this.scene, this.camera, readBuffer, this.clear );
+		renderer.render( this.scene, this.camera, writeBuffer, this.clear );
+
+		// re-enable update of color and depth
+
+		context.colorMask( true, true, true, true );
+		context.depthMask( true );
+
+		// only render where stencil is set to 1
+
+		context.stencilFunc( context.EQUAL, 1, 0xffffffff );  // draw if == 1
+		context.stencilOp( context.KEEP, context.KEEP, context.KEEP );
+
+	}
+
+};
+
+
+THREE.ClearMaskPass = function () {
+
+	this.enabled = true;
+
+};
+
+THREE.ClearMaskPass.prototype = {
+
+	render: function ( renderer, writeBuffer, readBuffer, delta ) {
+
+		var context = renderer.context;
+
+		context.disable( context.STENCIL_TEST );
+
+	}
+
+};
diff --git a/emperor/support_files/js/js/postprocessing/RenderPass.js b/emperor/support_files/js/js/postprocessing/RenderPass.js
new file mode 100644
index 0000000..0dcc13f
--- /dev/null
+++ b/emperor/support_files/js/js/postprocessing/RenderPass.js
@@ -0,0 +1,51 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.RenderPass = function ( scene, camera, overrideMaterial, clearColor, clearAlpha ) {
+
+	this.scene = scene;
+	this.camera = camera;
+
+	this.overrideMaterial = overrideMaterial;
+
+	this.clearColor = clearColor;
+	this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 1;
+
+	this.oldClearColor = new THREE.Color();
+	this.oldClearAlpha = 1;
+
+	this.enabled = true;
+	this.clear = true;
+	this.needsSwap = false;
+
+};
+
+THREE.RenderPass.prototype = {
+
+	render: function ( renderer, writeBuffer, readBuffer, delta ) {
+
+		this.scene.overrideMaterial = this.overrideMaterial;
+
+		if ( this.clearColor ) {
+
+			this.oldClearColor.copy( renderer.getClearColor() );
+			this.oldClearAlpha = renderer.getClearAlpha();
+
+			renderer.setClearColor( this.clearColor, this.clearAlpha );
+
+		}
+
+		renderer.render( this.scene, this.camera, readBuffer, this.clear );
+
+		if ( this.clearColor ) {
+
+			renderer.setClearColor( this.oldClearColor, this.oldClearAlpha );
+
+		}
+
+		this.scene.overrideMaterial = null;
+
+	}
+
+};
diff --git a/emperor/support_files/js/js/postprocessing/SavePass.js b/emperor/support_files/js/js/postprocessing/SavePass.js
new file mode 100644
index 0000000..ea4478f
--- /dev/null
+++ b/emperor/support_files/js/js/postprocessing/SavePass.js
@@ -0,0 +1,52 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SavePass = function( renderTarget ) {
+
+	var shader = THREE.ShaderExtras[ "screen" ];
+
+	this.textureID = "tDiffuse";
+
+	this.uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+	this.material = new THREE.ShaderMaterial( {
+
+		uniforms: this.uniforms,
+		vertexShader: shader.vertexShader,
+		fragmentShader: shader.fragmentShader
+
+	} );
+
+	this.renderTarget = renderTarget;
+
+	if ( this.renderTarget === undefined ) {
+
+		this.renderTargetParameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBufer: false };
+		this.renderTarget = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, this.renderTargetParameters );
+
+	}
+
+	this.enabled = true;
+	this.needsSwap = false;
+	this.clear = false;
+
+};
+
+THREE.SavePass.prototype = {
+
+	render: function ( renderer, writeBuffer, readBuffer, delta ) {
+
+		if ( this.uniforms[ this.textureID ] ) {
+
+			this.uniforms[ this.textureID ].texture = readBuffer;
+
+		}
+
+		THREE.EffectComposer.quad.material = this.material;
+
+		renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera, this.renderTarget, this.clear );
+
+	}
+
+};
diff --git a/emperor/support_files/js/js/postprocessing/ShaderPass.js b/emperor/support_files/js/js/postprocessing/ShaderPass.js
new file mode 100644
index 0000000..13c9e75
--- /dev/null
+++ b/emperor/support_files/js/js/postprocessing/ShaderPass.js
@@ -0,0 +1,51 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.ShaderPass = function( shader, textureID ) {
+
+	this.textureID = ( textureID !== undefined ) ? textureID : "tDiffuse";
+
+	this.uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+	this.material = new THREE.ShaderMaterial( {
+
+		uniforms: this.uniforms,
+		vertexShader: shader.vertexShader,
+		fragmentShader: shader.fragmentShader
+
+	} );
+
+	this.renderToScreen = false;
+
+	this.enabled = true;
+	this.needsSwap = true;
+	this.clear = false;
+
+};
+
+THREE.ShaderPass.prototype = {
+
+	render: function ( renderer, writeBuffer, readBuffer, delta ) {
+
+		if ( this.uniforms[ this.textureID ] ) {
+
+			this.uniforms[ this.textureID ].texture = readBuffer;
+
+		}
+
+		THREE.EffectComposer.quad.material = this.material;
+
+		if ( this.renderToScreen ) {
+
+			renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera );
+
+		} else {
+
+			renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera, writeBuffer, this.clear );
+
+		}
+
+	}
+
+};
diff --git a/emperor/support_files/js/js/postprocessing/TexturePass.js b/emperor/support_files/js/js/postprocessing/TexturePass.js
new file mode 100644
index 0000000..e0fb138
--- /dev/null
+++ b/emperor/support_files/js/js/postprocessing/TexturePass.js
@@ -0,0 +1,37 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.TexturePass = function( texture, opacity ) {
+
+	var shader = THREE.ShaderExtras[ "screen" ];
+
+	this.uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+	this.uniforms[ "opacity" ].value = ( opacity !== undefined ) ? opacity : 1.0;
+	this.uniforms[ "tDiffuse" ].texture = texture;
+
+	this.material = new THREE.ShaderMaterial( {
+
+		uniforms: this.uniforms,
+		vertexShader: shader.vertexShader,
+		fragmentShader: shader.fragmentShader
+
+	} );
+
+	this.enabled = true;
+	this.needsSwap = false;
+
+};
+
+THREE.TexturePass.prototype = {
+
+	render: function ( renderer, writeBuffer, readBuffer, delta ) {
+
+		THREE.EffectComposer.quad.material = this.material;
+
+		renderer.render( THREE.EffectComposer.scene, THREE.EffectComposer.camera, readBuffer );
+
+	}
+
+};
diff --git a/emperor/support_files/js/specifications.txt b/emperor/support_files/js/specifications.txt
new file mode 100644
index 0000000..e499fd1
--- /dev/null
+++ b/emperor/support_files/js/specifications.txt
@@ -0,0 +1 @@
+Study names are passed as all lowercase with no spaces
\ No newline at end of file
diff --git a/emperor/support_files/js/spectrum.js b/emperor/support_files/js/spectrum.js
new file mode 100755
index 0000000..2680f80
--- /dev/null
+++ b/emperor/support_files/js/spectrum.js
@@ -0,0 +1,1566 @@
+// Spectrum: The No Hassle Colorpicker
+// https://github.com/bgrins/spectrum
+// Author: Brian Grinstead
+// License: MIT
+// Requires: jQuery, spectrum.css
+
+(function (window, $, undefined) {
+    var defaultOpts = {
+        
+        // Events
+        beforeShow: noop,
+        move: noop,
+        change: noop,
+        show: noop,
+        hide: noop,
+        
+        // Options
+        color: false,
+        flat: false,
+        showInput: false,
+        showButtons: true,
+        showInitial: false,
+        showPalette: false,
+        showPaletteOnly: false,
+        showSelectionPalette: true,
+        localStorageKey: false,
+        maxSelectionSize: 7,
+        cancelText: "cancel",
+        chooseText: "choose",
+        preferredFormat: false,
+        className: "",
+        theme: "sp-light",
+        palette: ['fff', '000'],
+        selectionPalette: []
+    },
+    spectrums = [],
+    IE = $.browser.msie,
+    replaceInput = [
+        "<div class='sp-replacer'>",
+            "<div class='sp-preview'></div>",
+            "<div class='sp-dd'>▼</div>",
+        "</div>"
+    ].join(''),
+    markup = (function () {
+
+        // IE does not support gradients with multiple stops, so we need to simulate            
+        //  that for the rainbow slider with 8 divs that each have a single gradient
+        var gradientFix = "";
+        if (IE) {
+            for (var i = 1; i <= 6; i++) {
+                gradientFix += "<div class='sp-" + i + "'></div>";
+            }
+        }
+
+        return [
+            "<div class='sp-container'>",
+                "<div class='sp-palette-container'>",
+                    "<div class='sp-palette sp-thumb sp-cf'></div>",
+                "</div>",
+                "<div class='sp-picker-container'>",
+                    "<div class='sp-top sp-cf'>",
+                        "<div class='sp-fill'></div>",
+                        "<div class='sp-top-inner'>",
+                            "<div class='sp-color'>",
+                                "<div class='sp-sat'>",
+                                    "<div class='sp-val'>",
+                                        "<div class='sp-dragger'></div>",
+                                    "</div>",
+                                "</div>",
+                            "</div>",
+                            "<div class='sp-hue'>",
+                                "<div class='sp-slider'></div>",
+                                gradientFix,
+                            "</div>",
+                        "</div>",
+                    "</div>",
+                    "<div class='sp-input-container sp-cf'>",
+                        "<input class='sp-input' type='text' spellcheck='false'  />",
+                    "</div>",
+                    "<div class='sp-initial sp-thumb sp-cf'></div>",
+                    "<div class='sp-button-container sp-cf'>",
+                        "<a class='sp-cancel' href='#'></a>",
+                        "<button class='sp-choose'></button>",
+                    "</div>",
+                "</div>",
+            "</div>"
+        ].join("");
+    })(),
+    paletteTemplate = function (p, color, className) {
+        var html = [];
+        for (var i = 0; i < p.length; i++) {
+            var tiny = tinycolor(p[i]);
+            var c = tiny.toHsl().l < .5 ? "sp-thumb-dark" : "sp-thumb-light";
+            c += (tinycolor.equals(color, p[i])) ? " sp-thumb-active" : "";
+            html.push('<span title="' + tiny.toHexString() + '" data-color="' + tiny.toHexString() + '" style="background-color:' + tiny.toRgbString() + ';" class="' + c + '"></span>');
+        }
+        return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>";
+    };
+
+    function hideAll() {
+        for (var i = 0; i < spectrums.length; i++) {
+            if (spectrums[i]) {
+                spectrums[i].hide();
+            }
+        }
+    }
+    function instanceOptions(o, callbackContext) {
+        var opts = $.extend({}, defaultOpts, o);
+        opts.callbacks = {
+            'move': bind(opts.move, callbackContext),
+            'change': bind(opts.change, callbackContext),
+            'show': bind(opts.show, callbackContext),
+            'hide': bind(opts.hide, callbackContext),
+            'beforeShow': bind(opts.beforeShow, callbackContext)
+        };
+
+        return opts;
+    }
+
+    function spectrum(element, o) {
+
+        var opts = instanceOptions(o, element),
+            flat = opts.flat,
+            showPaletteOnly = opts.showPaletteOnly,
+            showPalette = opts.showPalette || showPaletteOnly,
+            showInitial = opts.showInitial && !flat,
+            showInput = opts.showInput,
+            showSelectionPalette = opts.showSelectionPalette,
+            localStorageKey = opts.localStorageKey,
+            theme = opts.theme,
+            callbacks = opts.callbacks,
+            resize = throttle(reflow, 10),
+            visible = false,
+            dragWidth = 0,
+            dragHeight = 0,
+            dragHelperHeight = 0,
+            slideHeight = 0,
+            slideWidth = 0,
+            slideHelperHeight = 0,
+            currentHue = 0,
+            currentSaturation = 0,
+            currentValue = 0,
+            palette = opts.palette.slice(0),
+            paletteArray = $.isArray(palette[0]) ? palette : [palette],
+            selectionPalette = opts.selectionPalette.slice(0),
+            draggingClass = "sp-dragging";
+
+        var doc = element.ownerDocument,
+            body = doc.body,
+            boundElement = $(element),
+            container = $(markup, doc).addClass(theme),
+            dragger = container.find(".sp-color"),
+            dragHelper = container.find(".sp-dragger"),
+            slider = container.find(".sp-hue"),
+            slideHelper = container.find(".sp-slider"),
+            textInput = container.find(".sp-input"),
+            paletteContainer = container.find(".sp-palette"),
+            initialColorContainer = container.find(".sp-initial"),
+            cancelButton = container.find(".sp-cancel"),
+            chooseButton = container.find(".sp-choose"),
+            isInput = boundElement.is("input"),
+            shouldReplace = isInput && !flat,
+            replacer = (shouldReplace) ? $(replaceInput).addClass(theme) : $([]),
+            offsetElement = (shouldReplace) ? replacer : boundElement,
+            previewElement = replacer.find(".sp-preview"),
+            initialColor = opts.color || (isInput && boundElement.val()),
+            colorOnShow = false,
+            preferredFormat = opts.preferredFormat,
+            currentPreferredFormat = preferredFormat,
+            clickoutFiresChange = !opts.showButtons;
+            
+        chooseButton.text(opts.chooseText);
+        cancelButton.text(opts.cancelText);
+
+        function initialize() {
+
+            if (IE) {
+                container.find("*:not(input)").attr("unselectable", "on");
+            }
+
+            container.toggleClass("sp-flat", flat);
+            container.toggleClass("sp-input-disabled", !showInput);
+            container.toggleClass("sp-buttons-disabled", !opts.showButtons || flat);
+            container.toggleClass("sp-palette-disabled", !showPalette);
+            container.toggleClass("sp-palette-only", showPaletteOnly);
+            container.toggleClass("sp-initial-disabled", !showInitial);
+            container.addClass(opts.className);
+
+            if (shouldReplace) {
+                boundElement.hide().after(replacer);
+            }
+
+            if (flat) {
+                boundElement.after(container).hide();
+            }
+            else {
+                $(body).append(container.hide());
+            }
+            if (localStorageKey && window.localStorage) {
+                try {
+                    selectionPalette = window.localStorage[localStorageKey].split(",");
+                }
+                catch (e) {
+
+                }
+            }
+
+            offsetElement.bind("click.spectrum touchstart.spectrum", function (e) {
+                toggle();
+
+                e.stopPropagation();
+
+                if (!$(e.target).is("input")) {
+                    e.preventDefault();
+                }
+            });
+
+            // Prevent clicks from bubbling up to document.  This would cause it to be hidden.
+            container.click(stopPropagation);
+
+            // Handle user typed input
+            textInput.change(setFromTextInput);
+            textInput.bind("paste", function () {
+                setTimeout(setFromTextInput, 1);
+            });
+            textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } });
+
+            cancelButton.bind("click.spectrum", function (e) {
+                e.stopPropagation();
+                e.preventDefault();
+
+                hide();
+            });
+
+            chooseButton.bind("click.spectrum", function (e) {
+                e.stopPropagation();
+                e.preventDefault();
+
+                if (isValid()) {
+                    updateOriginalInput();
+                    hide();
+                }
+            });
+
+            draggable(slider, function (dragX, dragY) {
+                currentHue = (dragY / slideHeight);
+                move();
+            }, dragStart, dragStop);
+
+            draggable(dragger, function (dragX, dragY) {
+                currentSaturation = dragX / dragWidth;
+                currentValue = (dragHeight - dragY) / dragHeight;
+                move();
+            }, dragStart, dragStop);
+            
+            if (!!initialColor) {
+                set(initialColor, true);
+                addColorToSelectionPalette(initialColor);
+            }
+            else {
+                updateUI();
+            }
+
+            if (flat) {
+                show();
+            }
+
+            function palletElementClick(e) {
+                if (e.data && e.data.ignore) {
+                    set($(this).data("color"), true);
+                    move();
+                }
+                else {
+                    set($(this).data("color"));
+                    move();
+                    hide();
+                }
+
+                return false;
+            }
+
+            var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum";
+            paletteContainer.delegate("span", paletteEvent, palletElementClick);
+            initialColorContainer.delegate("span::nth-child(1)", paletteEvent, { ignore: true }, palletElementClick);
+        }
+        function addColorToSelectionPalette(color) {
+            if (showSelectionPalette) {
+                selectionPalette.push(tinycolor(color).toHexString());
+                if (localStorageKey && window.localStorage) {
+                    window.localStorage[localStorageKey] = selectionPalette.join(",");
+                }
+            }
+        }
+
+        function getUniqueSelectionPalette() {
+            var unique = [];
+            var p = selectionPalette;
+            var paletteLookup = {};
+
+            if (showPalette) {
+
+                for (var i = 0; i < paletteArray.length; i++) {
+                    for (var j = 0; j < paletteArray[i].length; j++) {
+                        var hex = tinycolor(paletteArray[i][j]).toHexString();
+                        paletteLookup[hex] = true;
+                    }
+                }
+
+                for (var i = 0; i < p.length; i++) {
+                    var color = tinycolor(p[i]);
+                    var hex = color.toHexString();
+
+                    if (!paletteLookup.hasOwnProperty(hex)) {
+                        unique.push(p[i]);
+                        paletteLookup[hex] = true;
+                    }
+                }
+            }
+
+            return unique.reverse().slice(0, opts.maxSelectionSize);
+        }
+        function drawPalette() {
+
+            var currentColor = get();
+
+            var html = $.map(paletteArray, function (palette, i) {
+                return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i);
+            });
+
+            if (selectionPalette) {
+                html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection"));
+            }
+
+            paletteContainer.html(html.join(""));
+        }
+        function drawInitial() {
+            if (showInitial) {
+                var initial = colorOnShow;
+                var current = get();
+                initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial"));
+            }
+        }
+        function dragStart() {
+            if (dragHeight === 0 || dragWidth === 0 || slideHeight === 0) {
+                reflow();
+            }
+            container.addClass(draggingClass);
+        }
+        function dragStop() {
+            container.removeClass(draggingClass);
+        }
+        function setFromTextInput() {
+            var tiny = tinycolor(textInput.val());
+            if (tiny.ok) {
+                set(tiny, true);
+            }
+            else {
+                textInput.addClass("sp-validation-error");
+            }
+        }
+
+        function toggle() {
+            (visible) ? hide() : show();
+        }
+
+        function show() {
+            if (visible) { 
+                reflow();
+                return;
+            }
+            if (callbacks.beforeShow(get()) === false) return;
+
+            hideAll();
+            visible = true;
+
+            $(doc).bind("click.spectrum", hide);
+            $(window).bind("resize.spectrum", resize);
+            replacer.addClass("sp-active");
+            container.show();
+
+            if (showPalette) {
+                drawPalette();
+            }
+            reflow();
+            updateUI();
+            
+            colorOnShow = get();
+            
+            drawInitial();
+            callbacks.show(colorOnShow);
+        }
+
+        function hide() {
+            if (!visible || flat) { return; }
+            visible = false;
+
+            $(doc).unbind("click.spectrum", hide);
+            $(window).unbind("resize.spectrum", resize);
+
+            replacer.removeClass("sp-active");
+            container.hide();
+
+            var colorHasChanged = !tinycolor.equals(get(), colorOnShow);
+
+            if (colorHasChanged) {
+                if (clickoutFiresChange) {
+                    updateOriginalInput();
+                }
+                else {
+                    revert();
+                }
+            }
+
+            callbacks.hide(get());
+        }
+
+        function revert() {
+            set(colorOnShow, true, true);
+        }
+        
+        function set(color, ignoreChange, ignoreFormatChange) {
+            if (tinycolor.equals(color, get())) {
+                return;
+            }
+
+            var newColor = tinycolor(color);
+            var newHsv = newColor.toHsv();
+            
+            currentHue = newHsv.h;
+            currentSaturation = newHsv.s;
+            currentValue = newHsv.v;
+
+            updateUI();
+
+            if (!ignoreFormatChange) {
+                currentPreferredFormat = preferredFormat || newColor.format;
+            }
+            
+            // set can be called from a default value,  don't want to trigger a change in that case
+            if (!ignoreChange && !tinycolor.equals(color, colorOnShow)) {
+                updateOriginalInput();
+            }
+        }
+
+        function get() {
+            return tinycolor.fromRatio({ h: currentHue, s: currentSaturation, v: currentValue });
+        }
+
+        function isValid() {
+            return !textInput.hasClass("sp-validation-error");
+        }
+
+        function move() {
+            updateUI();
+            
+            callbacks.move(get());
+        }
+
+        function updateUI() {
+
+            textInput.removeClass("sp-validation-error");
+
+            updateHelperLocations();
+
+            // Update dragger background color (gradients take care of saturation and value).
+            var flatColor = tinycolor({ h: currentHue, s: "1.0", v: "1.0" });
+            dragger.css("background-color", flatColor.toHexString());
+
+            var realColor = get(),
+                realHex = realColor.toHexString();
+
+            // Update the replaced elements background color (with actual selected color)
+            previewElement.css("background-color", realHex);
+
+            // Update the text entry input as it changes happen
+            if (showInput) {
+                textInput.val(realColor.toString(currentPreferredFormat));
+            }
+
+            if (showPalette) {
+                drawPalette();
+            }
+
+            drawInitial();
+        }
+
+        function updateHelperLocations() {
+            var h = currentHue;
+            var s = currentSaturation;
+            var v = currentValue;
+
+            // Where to show the little circle in that displays your current selected color
+            var dragX = s * dragWidth;
+            var dragY = dragHeight - (v * dragHeight);
+            dragX = Math.max(
+                -dragHelperHeight,
+                Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight)
+            );
+            dragY = Math.max(
+                -dragHelperHeight,
+                Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight)
+            );
+            dragHelper.css({
+                "top": dragY,
+                "left": dragX
+            });
+
+            // Where to show the bar that displays your current selected hue
+            var slideY = (currentHue) * slideHeight;
+            slideHelper.css({
+                "top": slideY - slideHelperHeight
+            });
+        }
+
+        function updateOriginalInput() {
+            var color = get();
+            
+            if (isInput) {
+                boundElement.val(color.toString(currentPreferredFormat)).change();
+            }
+            
+            colorOnShow = color;
+
+            // Update the selection palette with the current color
+            addColorToSelectionPalette(color);
+                
+            callbacks.change(color);
+        }
+
+        function reflow() {
+            dragWidth = dragger.width();
+            dragHeight = dragger.height();
+            dragHelperHeight = dragHelper.height();
+            slideWidth = slider.width();
+            slideHeight = slider.height();
+            slideHelperHeight = slideHelper.height();
+
+            if (!flat) {
+                container.offset(getOffset(container, offsetElement));
+            }
+
+            updateHelperLocations();
+        }
+
+        function destroy() {
+            boundElement.show();
+            offsetElement.unbind("click.spectrum touchstart.spectrum");
+            container.remove();
+            replacer.remove();
+            spectrums[spect.id] = null;
+        }
+
+        initialize();
+
+        var spect = {
+            show: show,
+            hide: hide,
+            set: function (c) {
+                set(c, true);
+            },
+            get: get,
+            destroy: destroy,
+            container: container
+        };
+
+        spect.id = spectrums.push(spect) - 1;
+
+        return spect;
+    }
+
+    /**
+    * checkOffset - get the offset below/above and left/right element depending on screen position
+    * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js
+    */
+    function getOffset(picker, input) {
+        var extraY = 0;
+        var dpWidth = picker.outerWidth();
+        var dpHeight = picker.outerHeight();
+        var inputWidth = input.outerWidth();
+        var inputHeight = input.outerHeight();
+        var doc = picker[0].ownerDocument;
+        var docElem = doc.documentElement;
+        var viewWidth = docElem.clientWidth + $(doc).scrollLeft();
+        var viewHeight = docElem.clientHeight + $(doc).scrollTop();
+        var offset = input.offset();
+        offset.top += inputHeight;
+
+        offset.left -=
+            Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
+            Math.abs(offset.left + dpWidth - viewWidth) : 0);
+
+        offset.top -=
+            Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
+            Math.abs(dpHeight + inputHeight - extraY) : extraY));
+
+        return offset;
+    }
+
+    /** 
+    * noop - do nothing
+    */
+    function noop() {
+
+    }
+
+    /**
+    * stopPropagation - makes the code only doing this a little easier to read in line
+    */
+    function stopPropagation(e) {
+        e.stopPropagation();
+    }
+
+    /**
+    * Create a function bound to a given object
+    * Thanks to underscore.js
+    */
+    function bind(func, obj) {
+        var slice = Array.prototype.slice;
+        var args = slice.call(arguments, 2);
+        return function () {
+            return func.apply(obj, args.concat(slice.call(arguments)));
+        }
+    }
+
+    /**
+    * Lightweight drag helper.  Handles containment within the element, so that
+    * when dragging, the x is within [0,element.width] and y is within [0,element.height]
+    */
+    function draggable(element, onmove, onstart, onstop) {
+        onmove = onmove || function () { };
+        onstart = onstart || function () { };
+        onstop = onstop || function () { };
+        var doc = element.ownerDocument || document;
+        var dragging = false;
+        var offset = {};
+        var maxHeight = 0;
+        var maxWidth = 0;
+        var IE = $.browser.msie;
+        var hasTouch = ('ontouchstart' in window);
+
+        var duringDragEvents = {};
+        duringDragEvents["selectstart"] = prevent;
+        duringDragEvents["dragstart"] = prevent;
+        duringDragEvents[(hasTouch ? "touchmove" : "mousemove")] = move;
+        duringDragEvents[(hasTouch ? "touchend" : "mouseup")] = stop;
+
+        function prevent(e) {
+            if (e.stopPropagation) {
+                e.stopPropagation();
+            }
+            if (e.preventDefault) {
+                e.preventDefault();
+            }
+            e.returnValue = false;
+        }
+
+        function move(e) {
+            if (dragging) {
+                // Mouseup happened outside of window
+                if (IE && !(document.documentMode >= 9) && !e.button) {
+                    return stop();
+                }
+
+                var touches = e.originalEvent.touches;
+                var pageX = touches ? touches[0].pageX : e.pageX;
+                var pageY = touches ? touches[0].pageY : e.pageY;
+
+                var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
+                var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));
+
+                if (hasTouch) {
+                    // Stop scrolling in iOS
+                    prevent(e);
+                }
+
+                onmove.apply(element, [dragX, dragY]);
+            }
+        }
+        function start(e) {
+            var rightclick = (e.which) ? (e.which == 3) : (e.button == 2);
+            var touches = e.originalEvent.touches;
+
+            if (!rightclick && !dragging) {
+                if (onstart.apply(element, arguments) !== false) {
+                    dragging = true;
+                    maxHeight = $(element).height();
+                    maxWidth = $(element).width();
+                    offset = $(element).offset();
+
+                    $(doc).bind(duringDragEvents);
+                    $(doc.body).addClass("sp-dragging");
+
+                    if (!hasTouch) {
+                        move(e);
+                    }
+                    
+                    prevent(e);
+                }
+            }
+        }
+        function stop() {
+            if (dragging) {
+                $(doc).unbind(duringDragEvents);
+                $(doc.body).removeClass("sp-dragging");
+                onstop.apply(element, arguments);
+            }
+            dragging = false;
+        }
+
+        $(element).bind(hasTouch ? "touchstart" : "mousedown", start);
+    }
+
+    function throttle(func, wait, debounce) {
+        var timeout;
+        return function () {
+            var context = this, args = arguments;
+            var throttler = function () {
+                timeout = null;
+                func.apply(context, args);
+            };
+            if (debounce) clearTimeout(timeout);
+            if (debounce || !timeout) timeout = setTimeout(throttler, wait);
+        };
+    }
+
+
+    /**
+    * Define a jQuery plugin
+    */
+    var dataID = "spectrum.id";
+    $.fn.spectrum = function (opts, extra) {
+        if (typeof opts == "string") {
+            if (opts == "get") {
+                return spectrums[this.eq(0).data(dataID)].get();
+            } else if (opts == "container") {
+                return spectrums[$(this).data(dataID)].container;
+            }
+
+            return this.each(function () {
+                var spect = spectrums[$(this).data(dataID)];
+                if (spect) {
+                    if (opts == "show") { spect.show(); }
+                    if (opts == "hide") { spect.hide(); }
+                    if (opts == "set") { spect.set(extra); }
+                    if (opts == "destroy") {
+                        spect.destroy();
+                        $(this).removeData(dataID);
+                    }
+                }
+            });
+        }
+
+        // Initializing a new one
+        return this.spectrum("destroy").each(function () {
+            var spect = spectrum(this, opts);
+            $(this).data(dataID, spect.id);
+        });
+    };
+
+    $.fn.spectrum.load = true;
+    $.fn.spectrum.loadOpts = {};
+    $.fn.spectrum.draggable = draggable;
+
+    $.fn.spectrum.processNativeColorInputs = function() {
+        var supportsColor = $("<input type='color' />")[0].type === "color";       
+        if (!supportsColor) {
+            $("input[type=color]").spectrum({
+                preferredFormat: "hex6"
+            });
+        }
+    };
+    
+    $(function () {
+        if ($.fn.spectrum.load) {
+            $.fn.spectrum.processNativeColorInputs();
+        }
+    });
+
+})(this, jQuery);
+
+
+
+
+
+
+
+// TinyColor.js - <https://github.com/bgrins/TinyColor> - 2011 Brian Grinstead - v0.5
+
+(function (window) {
+
+    var trimLeft = /^[\s,#]+/,
+    trimRight = /\s+$/,
+    tinyCounter = 0,
+    math = Math,
+    mathRound = math.round,
+    mathMin = math.min,
+    mathMax = math.max,
+    mathRandom = math.random,
+    parseFloat = window.parseFloat;
+
+    function tinycolor(color, opts) {
+
+        // If input is already a tinycolor, return itself
+        if (typeof color == "object" && color.hasOwnProperty("_tc_id")) {
+            return color;
+        }
+
+        var rgb = inputToRGB(color);
+        var r = rgb.r, g = rgb.g, b = rgb.b, a = parseFloat(rgb.a), format = rgb.format;
+
+        return {
+            ok: rgb.ok,
+            format: format,
+            _tc_id: tinyCounter++,
+            alpha: a,
+            toHsv: function () {
+                var hsv = rgbToHsv(r, g, b);
+                return { h: hsv.h, s: hsv.s, v: hsv.v, a: a };
+            },
+            toHsvString: function () {
+                var hsv = rgbToHsv(r, g, b);
+                var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
+                return (a == 1) ?
+              "hsv(" + h + ", " + s + "%, " + v + "%)" :
+              "hsva(" + h + ", " + s + "%, " + v + "%, " + a + ")";
+            },
+            toHsl: function () {
+                var hsl = rgbToHsl(r, g, b);
+                return { h: hsl.h, s: hsl.s, l: hsl.l, a: a };
+            },
+            toHslString: function () {
+                var hsl = rgbToHsl(r, g, b);
+                var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
+                return (a == 1) ?
+              "hsl(" + h + ", " + s + "%, " + l + "%)" :
+              "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")";
+            },
+            toHex: function () {
+                return rgbToHex(r, g, b);
+            },
+            toHexString: function (force6Char) {
+                return '#' + rgbToHex(r, g, b, force6Char);
+            },
+            toRgb: function () {
+                return { r: mathRound(r), g: mathRound(g), b: mathRound(b), a: a };
+            },
+            toRgbString: function () {
+                return (a == 1) ?
+              "rgb(" + mathRound(r) + ", " + mathRound(g) + ", " + mathRound(b) + ")" :
+              "rgba(" + mathRound(r) + ", " + mathRound(g) + ", " + mathRound(b) + ", " + a + ")";
+            },
+            toName: function () {
+                return hexNames[rgbToHex(r, g, b)] || false;
+            },
+            toFilter: function () {
+                var hex = rgbToHex(r, g, b);
+                var alphaHex = Math.round(parseFloat(a) * 255).toString(16);
+                return "progid:DXImageTransform.Microsoft.gradient(startColorstr=#" +
+                alphaHex + hex + ",endColorstr=#" + alphaHex + hex + ")";
+            },
+            toString: function (format) {
+                format = format || this.format;
+                var formattedString = false;
+                if (format === "rgb") {
+                    formattedString = this.toRgbString();
+                }
+                if (format === "hex") {
+                    formattedString = this.toHexString();
+                }
+                if (format === "hex6") {
+                    formattedString = this.toHexString(true);
+                }
+                if (format === "name") {
+                    formattedString = this.toName();
+                }
+                if (format === "hsl") {
+                    formattedString = this.toHslString();
+                }
+                if (format === "hsv") {
+                    formattedString = this.toHsvString();
+                }
+
+                return formattedString || this.toHexString();
+            }
+        };
+    }
+    
+    // If input is an object, force 1 into "1.0" to handle ratios properly
+    // String input requires "1.0" as input, so 1 will be treated as 1
+    tinycolor.fromRatio = function(color) {
+
+        if (typeof color == "object") {
+            for (var i in color) {
+                if (color[i] === 1) {
+                    color[i] = "1.0";
+                }
+            }
+        }
+
+        return tinycolor(color);
+
+    }
+
+    // Given a string or object, convert that input to RGB
+    // Possible string inputs:
+    //
+    //     "red"
+    //     "#f00" or "f00"
+    //     "#ff0000" or "ff0000"
+    //     "rgb 255 0 0" or "rgb (255, 0, 0)"
+    //     "rgb 1.0 0 0" or "rgb (1, 0, 0)"
+    //     "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
+    //     "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1" 
+    //     "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
+    //     "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
+    //     "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
+    //
+    function inputToRGB(color) {
+
+        var rgb = { r: 0, g: 0, b: 0 };
+        var a = 1;
+        var ok = false;
+        var format = false;
+
+        if (typeof color == "string") {
+            color = stringInputToObject(color);
+        }
+
+        if (typeof color == "object") {
+            if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
+                rgb = rgbToRgb(color.r, color.g, color.b);
+                ok = true;
+                format = "rgb";
+            }
+            else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
+                rgb = hsvToRgb(color.h, color.s, color.v);
+                ok = true;
+                format = "hsv";
+            }
+            else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
+                var rgb = hslToRgb(color.h, color.s, color.l);
+                ok = true;
+                format = "hsl";
+            }
+
+            if (color.hasOwnProperty("a")) {
+                a = color.a;
+            }
+        }
+
+        rgb.r = mathMin(255, mathMax(rgb.r, 0));
+        rgb.g = mathMin(255, mathMax(rgb.g, 0));
+        rgb.b = mathMin(255, mathMax(rgb.b, 0));
+
+
+        // Don't let the range of [0,255] come back in [0,1].  
+        // Potentially lose a little bit of precision here, but will fix issues where
+        // .5 gets interpreted as half of the total, instead of half of 1.
+        // If it was supposed to be 128, this was already taken care of in the conversion function
+        if (rgb.r < 1) { rgb.r = mathRound(rgb.r); }
+        if (rgb.g < 1) { rgb.g = mathRound(rgb.g); }
+        if (rgb.b < 1) { rgb.b = mathRound(rgb.b); }
+
+        return {
+            ok: ok,
+            format: (color && color.format) || format,
+            r: rgb.r,
+            g: rgb.g,
+            b: rgb.b,
+            a: a
+        };
+    }
+
+
+
+    // Conversion Functions
+    // --------------------
+
+    // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:   
+    // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
+
+    // `rgbToRgb`  
+    // Handle bounds / percentage checking to conform to CSS color spec
+    // <http://www.w3.org/TR/css3-color/>  
+    // *Assumes:* r, g, b in [0, 255] or [0, 1]  
+    // *Returns:* { r, g, b } in [0, 255]
+    function rgbToRgb(r, g, b) {
+        return {
+            r: bound01(r, 255) * 255,
+            g: bound01(g, 255) * 255,
+            b: bound01(b, 255) * 255
+        };
+    }
+
+    // `rgbToHsl`  
+    // Converts an RGB color value to HSL.  
+    // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]  
+    // *Returns:* { h, s, l } in [0,1]  
+    function rgbToHsl(r, g, b) {
+
+        r = bound01(r, 255);
+        g = bound01(g, 255);
+        b = bound01(b, 255);
+
+        var max = mathMax(r, g, b), min = mathMin(r, g, b);
+        var h, s, l = (max + min) / 2;
+
+        if (max == min) {
+            h = s = 0; // achromatic
+        }
+        else {
+            var d = max - min;
+            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+            switch (max) {
+                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+                case g: h = (b - r) / d + 2; break;
+                case b: h = (r - g) / d + 4; break;
+            }
+
+            h /= 6;
+        }
+
+        return { h: h, s: s, l: l };
+    }
+
+    // `hslToRgb`  
+    // Converts an HSL color value to RGB.  
+    // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]  
+    // *Returns:* { r, g, b } in the set [0, 255]  
+    function hslToRgb(h, s, l) {
+        var r, g, b;
+
+        h = bound01(h, 360);
+        s = bound01(s, 100);
+        l = bound01(l, 100);
+
+        function hue2rgb(p, q, t) {
+            if (t < 0) t += 1;
+            if (t > 1) t -= 1;
+            if (t < 1 / 6) return p + (q - p) * 6 * t;
+            if (t < 1 / 2) return q;
+            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
+            return p;
+        }
+
+        if (s == 0) {
+            r = g = b = l; // achromatic
+        }
+        else {
+            var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+            var p = 2 * l - q;
+            r = hue2rgb(p, q, h + 1 / 3);
+            g = hue2rgb(p, q, h);
+            b = hue2rgb(p, q, h - 1 / 3);
+        }
+
+        return { r: r * 255, g: g * 255, b: b * 255 };
+    }
+
+    // `rgbToHsv`  
+    // Converts an RGB color value to HSV  
+    // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]  
+    // *Returns:* { h, s, v } in [0,1]  
+    function rgbToHsv(r, g, b) {
+
+        r = bound01(r, 255);
+        g = bound01(g, 255);
+        b = bound01(b, 255);
+
+        var max = mathMax(r, g, b), min = mathMin(r, g, b);
+        var h, s, v = max;
+
+        var d = max - min;
+        s = max == 0 ? 0 : d / max;
+
+        if (max == min) {
+            h = 0; // achromatic
+        }
+        else {
+            switch (max) {
+                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+                case g: h = (b - r) / d + 2; break;
+                case b: h = (r - g) / d + 4; break;
+            }
+            h /= 6;
+        }
+        return { h: h, s: s, v: v };
+    }
+
+    // `hsvToRgb`  
+    // Converts an HSV color value to RGB. 
+    // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
+    // *Returns:* { r, g, b } in the set [0, 255]
+    function hsvToRgb(h, s, v) {
+        var r, g, b;
+
+        h = bound01(h, 360) * 6;
+        s = bound01(s, 100);
+        v = bound01(v, 100);
+
+        var i = math.floor(h),
+        f = h - i,
+        p = v * (1 - s),
+        q = v * (1 - f * s),
+        t = v * (1 - (1 - f) * s),
+        mod = i % 6,
+        r = [v, q, p, p, t, v][mod],
+        g = [t, v, v, q, p, p][mod],
+        b = [p, p, t, v, v, q][mod];
+
+        return { r: r * 255, g: g * 255, b: b * 255 };
+    }
+
+    // `rgbToHex`  
+    // Converts an RGB color to hex  
+    // Assumes r, g, and b are contained in the set [0, 255]  
+    // Returns a 3 or 6 character hex  
+    function rgbToHex(r, g, b, force6Char) {
+        function pad(c) {
+            return c.length == 1 ? '0' + c : '' + c;
+        }
+
+        var hex = [
+            pad(mathRound(r).toString(16)),
+            pad(mathRound(g).toString(16)),
+            pad(mathRound(b).toString(16))
+        ];
+
+        // Return a 3 character hex if possible
+        if (!force6Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
+            return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
+        }
+
+        return hex.join("");
+    }
+
+    // `equals`  
+    // Can be called with any tinycolor input
+    tinycolor.equals = function (color1, color2) {
+        if (!color1 || !color2) { return false; }
+        return tinycolor(color1).toHex() == tinycolor(color2).toHex();
+    };
+    tinycolor.random = function () {
+        return tinycolor.fromRatio({
+            r: mathRandom(),
+            g: mathRandom(),
+            b: mathRandom()
+        });
+    };
+
+
+    // Modification Functions
+    // ----------------------
+    // Thanks to less.js for some of the basics here  
+    // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>
+
+
+    tinycolor.desaturate = function (color, amount) {
+        var hsl = tinycolor(color).toHsl();
+        hsl.s -= ((amount || 10) / 100);
+        hsl.s = clamp01(hsl.s);
+        return tinycolor(hsl);
+    };
+    tinycolor.saturate = function (color, amount) {
+        var hsl = tinycolor(color).toHsl();
+        hsl.s += ((amount || 10) / 100);
+        hsl.s = clamp01(hsl.s);
+        return tinycolor(hsl);
+    };
+    tinycolor.greyscale = function (color) {
+        return tinycolor.desaturate(color, 100);
+    };
+    tinycolor.lighten = function (color, amount) {
+        var hsl = tinycolor(color).toHsl();
+        hsl.l += ((amount || 10) / 100);
+        hsl.l = clamp01(hsl.l);
+        return tinycolor(hsl);
+    };
+    tinycolor.darken = function (color, amount) {
+        var hsl = tinycolor(color).toHsl();
+        hsl.l -= ((amount || 10) / 100);
+        hsl.l = clamp01(hsl.l);
+        return tinycolor(hsl);
+    };
+    tinycolor.complement = function (color) {
+        var hsl = tinycolor(color).toHsl();
+        hsl.h = (hsl.h + .5) % 1;
+        return tinycolor(hsl);
+    };
+
+
+    // Combination Functions
+    // ---------------------
+    // Thanks to jQuery xColor for some of the ideas behind these
+    // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>
+
+    tinycolor.triad = function (color) {
+        var hsl = tinycolor(color).toHsl();
+        var h = hsl.h * 360;
+        return [
+        tinycolor(color),
+        tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
+        tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
+    ];
+    };
+    tinycolor.tetrad = function (color) {
+        var hsl = tinycolor(color).toHsl();
+        var h = hsl.h * 360;
+        return [
+        tinycolor(color),
+        tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
+        tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
+        tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
+    ];
+    };
+    tinycolor.splitcomplement = function (color) {
+        var hsl = tinycolor(color).toHsl();
+        var h = hsl.h * 360;
+        return [
+        tinycolor(color),
+        tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l }),
+        tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l })
+    ];
+    };
+    tinycolor.analogous = function (color, results, slices) {
+        results = results || 6;
+        slices = slices || 30;
+
+        var hsl = tinycolor(color).toHsl();
+        var part = 360 / slices
+        var ret = [tinycolor(color)];
+
+        hsl.h *= 360;
+
+        for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results; ) {
+            hsl.h = (hsl.h + part) % 360;
+            ret.push(tinycolor(hsl));
+        }
+        return ret;
+    };
+    tinycolor.monochromatic = function (color, results) {
+        results = results || 6;
+        var hsv = tinycolor(color).toHsv();
+        var h = hsv.h, s = hsv.s, v = hsv.v;
+        var ret = [];
+        var modification = 1 / results;
+
+        while (results--) {
+            ret.push(tinycolor({ h: h, s: s, v: v }));
+            v = (v + modification) % 1;
+        }
+
+        return ret;
+    };
+    tinycolor.readable = function (color1, color2) {
+        var a = tinycolor(color1).toRgb(), b = tinycolor(color2).toRgb();
+        return (
+        (b.r - a.r) * (b.r - a.r) +
+        (b.g - a.g) * (b.g - a.g) +
+        (b.b - a.b) * (b.b - a.b)
+    ) > 0x28A4;
+    };
+
+    // Big List of Colors
+    // ---------
+    // <http://www.w3.org/TR/css3-color/#svg-color>
+    var names = tinycolor.names = {
+        aliceblue: "f0f8ff",
+        antiquewhite: "faebd7",
+        aqua: "0ff",
+        aquamarine: "7fffd4",
+        azure: "f0ffff",
+        beige: "f5f5dc",
+        bisque: "ffe4c4",
+        black: "000",
+        blanchedalmond: "ffebcd",
+        blue: "00f",
+        blueviolet: "8a2be2",
+        brown: "a52a2a",
+        burlywood: "deb887",
+        burntsienna: "ea7e5d",
+        cadetblue: "5f9ea0",
+        chartreuse: "7fff00",
+        chocolate: "d2691e",
+        coral: "ff7f50",
+        cornflowerblue: "6495ed",
+        cornsilk: "fff8dc",
+        crimson: "dc143c",
+        cyan: "0ff",
+        darkblue: "00008b",
+        darkcyan: "008b8b",
+        darkgoldenrod: "b8860b",
+        darkgray: "a9a9a9",
+        darkgreen: "006400",
+        darkgrey: "a9a9a9",
+        darkkhaki: "bdb76b",
+        darkmagenta: "8b008b",
+        darkolivegreen: "556b2f",
+        darkorange: "ff8c00",
+        darkorchid: "9932cc",
+        darkred: "8b0000",
+        darksalmon: "e9967a",
+        darkseagreen: "8fbc8f",
+        darkslateblue: "483d8b",
+        darkslategray: "2f4f4f",
+        darkslategrey: "2f4f4f",
+        darkturquoise: "00ced1",
+        darkviolet: "9400d3",
+        deeppink: "ff1493",
+        deepskyblue: "00bfff",
+        dimgray: "696969",
+        dimgrey: "696969",
+        dodgerblue: "1e90ff",
+        firebrick: "b22222",
+        floralwhite: "fffaf0",
+        forestgreen: "228b22",
+        fuchsia: "f0f",
+        gainsboro: "dcdcdc",
+        ghostwhite: "f8f8ff",
+        gold: "ffd700",
+        goldenrod: "daa520",
+        gray: "808080",
+        green: "008000",
+        greenyellow: "adff2f",
+        grey: "808080",
+        honeydew: "f0fff0",
+        hotpink: "ff69b4",
+        indianred: "cd5c5c",
+        indigo: "4b0082",
+        ivory: "fffff0",
+        khaki: "f0e68c",
+        lavender: "e6e6fa",
+        lavenderblush: "fff0f5",
+        lawngreen: "7cfc00",
+        lemonchiffon: "fffacd",
+        lightblue: "add8e6",
+        lightcoral: "f08080",
+        lightcyan: "e0ffff",
+        lightgoldenrodyellow: "fafad2",
+        lightgray: "d3d3d3",
+        lightgreen: "90ee90",
+        lightgrey: "d3d3d3",
+        lightpink: "ffb6c1",
+        lightsalmon: "ffa07a",
+        lightseagreen: "20b2aa",
+        lightskyblue: "87cefa",
+        lightslategray: "789",
+        lightslategrey: "789",
+        lightsteelblue: "b0c4de",
+        lightyellow: "ffffe0",
+        lime: "0f0",
+        limegreen: "32cd32",
+        linen: "faf0e6",
+        magenta: "f0f",
+        maroon: "800000",
+        mediumaquamarine: "66cdaa",
+        mediumblue: "0000cd",
+        mediumorchid: "ba55d3",
+        mediumpurple: "9370db",
+        mediumseagreen: "3cb371",
+        mediumslateblue: "7b68ee",
+        mediumspringgreen: "00fa9a",
+        mediumturquoise: "48d1cc",
+        mediumvioletred: "c71585",
+        midnightblue: "191970",
+        mintcream: "f5fffa",
+        mistyrose: "ffe4e1",
+        moccasin: "ffe4b5",
+        navajowhite: "ffdead",
+        navy: "000080",
+        oldlace: "fdf5e6",
+        olive: "808000",
+        olivedrab: "6b8e23",
+        orange: "ffa500",
+        orangered: "ff4500",
+        orchid: "da70d6",
+        palegoldenrod: "eee8aa",
+        palegreen: "98fb98",
+        paleturquoise: "afeeee",
+        palevioletred: "db7093",
+        papayawhip: "ffefd5",
+        peachpuff: "ffdab9",
+        peru: "cd853f",
+        pink: "ffc0cb",
+        plum: "dda0dd",
+        powderblue: "b0e0e6",
+        purple: "800080",
+        red: "f00",
+        rosybrown: "bc8f8f",
+        royalblue: "4169e1",
+        saddlebrown: "8b4513",
+        salmon: "fa8072",
+        sandybrown: "f4a460",
+        seagreen: "2e8b57",
+        seashell: "fff5ee",
+        sienna: "a0522d",
+        silver: "c0c0c0",
+        skyblue: "87ceeb",
+        slateblue: "6a5acd",
+        slategray: "708090",
+        slategrey: "708090",
+        snow: "fffafa",
+        springgreen: "00ff7f",
+        steelblue: "4682b4",
+        tan: "d2b48c",
+        teal: "008080",
+        thistle: "d8bfd8",
+        tomato: "ff6347",
+        turquoise: "40e0d0",
+        violet: "ee82ee",
+        wheat: "f5deb3",
+        white: "fff",
+        whitesmoke: "f5f5f5",
+        yellow: "ff0",
+        yellowgreen: "9acd32"
+    };
+
+    // Make it easy to access colors via `hexNames[hex]`
+    var hexNames = tinycolor.hexNames = flip(names);
+
+
+    // Utilities
+    // ---------
+
+    // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
+    function flip(o) {
+        var flipped = {};
+        for (var i in o) {
+            if (o.hasOwnProperty(i)) {
+                flipped[o[i]] = i;
+            }
+        }
+        return flipped;
+    }
+
+    // Take input from [0, n] and return it as [0, 1]
+    function bound01(n, max) {
+        if (isOnePointZero(n)) { n = "100%"; }
+
+        var processPercent = isPercentage(n);
+        n = mathMin(max, mathMax(0, parseFloat(n)));
+
+        // Automatically convert percentage into number
+        if (processPercent) {
+            n = n * (max / 100);
+        }
+
+        // Handle floating point rounding errors
+        if ((math.abs(n - max) < 0.000001)) {
+            return 1;
+        }
+        else if (n >= 1) {
+            return (n % max) / parseFloat(max);
+        }
+        return n;
+    }
+
+    // Force a number between 0 and 1
+    function clamp01(val) {
+        return mathMin(1, mathMax(0, val));
+    }
+
+    // Parse an integer into hex
+    function parseHex(val) {
+        return parseInt(val, 16);
+    }
+
+    // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
+    // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
+    function isOnePointZero(n) {
+        return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
+    }
+
+    // Check to see if string passed in is a percentage
+    function isPercentage(n) {
+        return typeof n === "string" && n.indexOf('%') != -1;
+    }
+
+    var matchers = (function () {
+
+        // <http://www.w3.org/TR/css3-values/#integers>
+        var CSS_INTEGER = "[-\\+]?\\d+%?";
+
+        // <http://www.w3.org/TR/css3-values/#number-value>
+        var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";
+
+        // Allow positive/negative integer/number.  Don't capture the either/or, just the entire outcome.
+        var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";
+
+        // Actual matching.  
+        // Parentheses and commas are optional, but not required.  
+        // Whitespace can take the place of commas or opening paren
+        var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
+        var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
+
+        return {
+            rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
+            rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
+            hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
+            hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
+            hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
+            hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
+            hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
+        };
+    })();
+
+    // `stringInputToObject`  
+    // Permissive string parsing.  Take in a number of formats, and output an object
+    // based on detected format.  Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
+    function stringInputToObject(color) {
+
+        color = color.replace(trimLeft, '').replace(trimRight, '').toLowerCase();
+        var named = false;
+        if (names[color]) {
+            color = names[color];
+            named = true;
+        }
+        else if (color == 'transparent') {
+            return { r: 0, g: 0, b: 0, a: 0 };
+        }
+
+        // Try to match string input using regular expressions.  
+        // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]  
+        // Just return an object and let the conversion functions handle that.  
+        // This way the result will be the same whether the tinycolor is initialized with string or object.
+        var match;
+        if ((match = matchers.rgb.exec(color))) {
+            return { r: match[1], g: match[2], b: match[3] };
+        }
+        if ((match = matchers.rgba.exec(color))) {
+            return { r: match[1], g: match[2], b: match[3], a: match[4] };
+        }
+        if ((match = matchers.hsl.exec(color))) {
+            return { h: match[1], s: match[2], l: match[3] };
+        }
+        if ((match = matchers.hsla.exec(color))) {
+            return { h: match[1], s: match[2], l: match[3], a: match[4] };
+        }
+        if ((match = matchers.hsv.exec(color))) {
+            return { h: match[1], s: match[2], v: match[3] };
+        }
+        if ((match = matchers.hex6.exec(color))) {
+            return {
+                r: parseHex(match[1]),
+                g: parseHex(match[2]),
+                b: parseHex(match[3]),
+                format: named ? "name" : "hex"
+            };
+        }
+        if ((match = matchers.hex3.exec(color))) {
+            return {
+                r: parseHex(match[1] + '' + match[1]),
+                g: parseHex(match[2] + '' + match[2]),
+                b: parseHex(match[3] + '' + match[3]),
+                format: named ? "name" : "hex"
+            };
+        }
+
+        return false;
+    }
+
+    // Everything is ready, expose to window
+    window.tinycolor = tinycolor;
+
+})(this);
+
diff --git a/emperor/util.py b/emperor/util.py
new file mode 100644
index 0000000..3b776f5
--- /dev/null
+++ b/emperor/util.py
@@ -0,0 +1,488 @@
+#!/usr/bin/env python
+# File created on 25 Jan 2013
+from __future__ import division
+
+__author__ = "Yoshiki Vazquez Baeza"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza", "Antonio Gonzalez Pena"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+
+from numpy import ndarray, array, ones, zeros, vstack
+from string import strip
+
+from os import makedirs
+from os.path import abspath, dirname, join, exists
+from copy import deepcopy
+
+from qcli.util import qcli_system_call
+
+from emperor.qiime_backports.format import format_mapping_file
+from emperor.qiime_backports.filter import filter_mapping_file
+from emperor.qiime_backports.make_3d_plots import (get_custom_coords,
+    remove_nans, scale_custom_coords)
+from emperor.qiime_backports.parse import (mapping_file_to_dict,
+    parse_metadata_state_descriptions)
+from emperor.qiime_backports.util import (MetadataMap, is_valid_git_refname,
+    is_valid_git_sha1, summarize_pcoas)
+
+from emperor import __version__ as emperor_library_version
+
+class EmperorSupportFilesError(IOError):
+    """Exception for missing support files"""
+    pass
+    
+class EmperorInputFilesError(IOError):
+    """Exception for missing support files"""
+    pass
+
+class EmperorUnsupportedComputation(ValueError):
+    """Exception for computations that lack a meaning"""
+    pass
+
+# Based on qiime/qiime/util.py
+def get_emperor_library_version():
+    """Get Emperor version and the git SHA + current branch (if applicable)"""
+    emperor_dir = get_emperor_project_dir()
+    emperor_version = emperor_library_version
+
+    # more information could be retrieved following this pattern
+    sha_cmd = 'git --git-dir %s/.git rev-parse HEAD' % (emperor_dir)
+    sha_o, sha_e, sha_r = qcli_system_call(sha_cmd)
+    git_sha = sha_o.strip()
+
+    branch_cmd = 'git --git-dir %s/.git rev-parse --abbrev-ref HEAD' %\
+        (emperor_dir)
+    branch_o, branch_e, branch_r = qcli_system_call(branch_cmd)
+    git_branch = branch_o.strip()
+
+    # validate the output from both command calls
+    if is_valid_git_refname(git_branch) and is_valid_git_sha1(git_sha):
+        return '%s, %s@%s' % (emperor_version, git_branch, git_sha[0:7])
+    else:
+        return '%s' % emperor_version
+
+def get_emperor_project_dir():
+    """ Returns the top-level Emperor directory
+
+    based on qiime.util.get_qiime_project_dir from github.com/qiime/qiime
+    """
+    # Get the full path of util.py
+    current_file_path = abspath(__file__)
+    # Get the directory containing util.py
+    current_dir_path = dirname(current_file_path)
+    # Return the directory containing the directory containing util.py
+    return dirname(current_dir_path)
+
+def get_emperor_support_files_dir():
+    """Returns the path for the support files of the project """
+    return join(get_emperor_project_dir(), 'emperor/support_files/')
+
+def copy_support_files(file_path):
+    """Copy the support files to a named destination 
+
+    file_path: path where you want the support files to be copied to
+
+    Will raise EmperorSupportFilesError if a problem is found whilst trying to
+    copy the files.
+    """
+    file_path = join(file_path, 'emperor_required_resources')
+
+    if not exists(file_path):
+        makedirs(file_path)
+
+    # shutil.copytree does not provide an easy way to copy the contents of a
+    # directory into another existing directory, hence the system call.
+    # use double quotes for the paths to escape any invalid chracter(s)/spaces
+    cmd = 'cp -R "%s/"* "%s"' % (get_emperor_support_files_dir(),
+        abspath(file_path))
+    cmd_o, cmd_e, cmd_r = qcli_system_call(cmd)
+
+    if cmd_e:
+        raise EmperorSupportFilesError, "Error found whilst trying to copy " +\
+            "the support files:\n%s\n Could not execute: %s" % (cmd_e, cmd)
+
+    return
+
+def preprocess_mapping_file(data, headers, columns, unique=False, single=False,
+                            clones=0):
+    """Process a mapping file to expand the data or remove unuseful fields
+
+    Inputs:
+    data: mapping file data
+    headers: mapping file headers
+    columns: list of headers to keep, if one of these headers includes two
+    ampersands, this function will create a new column by merging the delimited
+    columns.
+    unique: keep columns where all values are unique
+    single: keep columns where all values are the same
+    clones: number of times to replicate the metadata
+
+    Outputs:
+    data: processed mapping file data
+    headers: processed mapping file headers
+    """
+
+    # The sample ID must always be there, else it's meaningless data
+    if 'SampleID' != columns[0]:
+        columns = ['SampleID'] + columns
+
+    # process concatenated columns if needed
+    merge = []
+    for column in columns:
+        if '&&' in column:
+            merge.append(column)
+    # each element needs several columns to be merged
+    for new_column in merge:
+        indices = [headers.index(header_name) for header_name in
+            new_column.split('&&')]
+
+        # join all the fields of the metadata that are listed in indices
+        for line in data:
+            line.append(''.join([line[index] for index in indices]))
+        headers.append(new_column)
+
+    # remove all unique or singled valued columns
+    if unique or single:
+        columns_to_remove = []
+        metadata = MetadataMap(mapping_file_to_dict(data, headers), [])
+
+        # find columns that have values that are all unique
+        if unique == True:
+            columns_to_remove += [column_name for column_name in headers[1::]
+                if metadata.hasUniqueCategoryValues(column_name)]
+
+        # remove categories where there is only one value
+        if single == True:
+            columns_to_remove += [column_name for column_name in headers[1::]
+                if metadata.hasSingleCategoryValue(column_name)]
+        columns_to_remove = list(set(columns_to_remove))
+
+        # remove the single or unique columns
+        data, headers = keep_columns_from_mapping_file(data, headers,
+            columns_to_remove, negate=True)
+
+    # remove anything not specified in the input
+    data, headers = keep_columns_from_mapping_file(data, headers, columns)
+
+    # sanitize the mapping file data and headers
+    data, headers = sanitize_mapping_file(data, headers)
+
+    # clones mean: replicate the metadata retagging the sample ids with a suffix
+    if clones:
+        out_data = []
+        for index in range(0, clones):
+            out_data.extend([[element[0]+'_%d' % index]+element[1::]
+                for element in data])
+        data = out_data
+
+    return data, headers
+
+
+def keep_columns_from_mapping_file(data, headers, columns, negate=False):
+    """Select the header names to remove/keep from the mapping file
+
+    Inputs:
+    data: mapping file data
+    headers: mapping file headers names
+    columns: header names to keep/remove, see negate
+    negate: False will _keep_ the listed columns; True will _remove_ them
+
+    Outputs:
+    data: filtered mapping file data
+    headers: filtered mapping file headers
+    """
+    data = deepcopy(data)
+    headers = deepcopy(headers)
+
+    if negate:
+        indices_of_interest = range(0, len(headers))
+    else:
+        indices_of_interest = []
+
+    # get the indices that you want to keep; either by removing the
+    # indices listed (negate is True) or by adding them (negate is False)
+    for column in columns:
+        try:
+            if negate:
+                del indices_of_interest[indices_of_interest.index(
+                    headers.index(column))]
+            else:
+                indices_of_interest.append(headers.index(column))
+        except ValueError:
+            continue
+
+    # keep the elements at the positions indices
+    keep_elements = lambda elements, indices :[element for i, element in
+        enumerate(elements) if i in indices]
+
+    headers = keep_elements(headers, indices_of_interest)
+    data = [keep_elements(row, indices_of_interest) for row in data]
+
+    return data, headers
+
+def preprocess_coords_file(coords_header, coords_data, coords_eigenvals,
+                        coords_pct, mapping_header, mapping_data,
+                        custom_axes=None, jackknifing_method=None,
+                        is_comparison=False):
+    """Process a PCoA data and handle customizations in the contents
+
+    Inputs:
+    coords_header: list of sample identifiers in the PCoA file _or_ list of
+    lists with sample identifiers for each coordinate file (if jackknifing or
+    comparing plots)
+    coords_data: matrix of coordinates in the PCoA file _or_ list of numpy
+    arrays with coordinates for each file (if jackknifing or comparing plots)
+    coords_eigenvals: numpy array with eigenvalues for the coordinates file _or_
+    list of numpy arrays with the eigenvalues (if jackknifing or comparing plots
+    )
+    coords_pct: numpy array with a the percent explained by each principal
+    coordinates axis _or_ a list of lists with numpy arrays (if jackknifing or
+    comparing plots)
+    mapping_header: mapping file headers names
+    mapping_data: mapping file data
+    custom_axes: name of the mapping data fields to add to coords_data
+    jackknifing_method: one of 'sdev' or 'IRQ', defaults to None, for more info
+    see qiime.util.summarize_pcoas
+    is_comparison: whether or not the inputs should be considered as the ones
+    for a comparison plot
+
+    Outputs:
+    coords_header: list of sample identifiers in the PCoA file
+    coords_data: matrix of coordinates in the PCoA file with custom_axes if
+    provided
+    coords_eigenvalues: either the eigenvalues of the input coordinates or the
+    average eigenvalues of the multiple coords that were passed in
+    coords_pct: list of percents explained by each axis as given by the master
+    coordinates i. e. the center around where the values revolve
+    coords_low: coordinates representing the lower edges of an ellipse; None if
+    no jackknifing is applied
+    coords_high: coordinates representing the highere edges of an ellipse; None
+    if no jackknifing is applied
+    clones: total number of input files
+
+    This controller function handles any customization that has to be done to
+    the PCoA data prior to the formatting. Note that the first element in each
+    list (coords, headers, eigenvalues & percents) will be considered the master
+    set of coordinates.
+
+    Raises: AssertionError if a comparison plot is requested but a list of data
+    is not passed as input
+    """
+
+    # prevent obscure and obfuscated errors
+    if is_comparison:
+        assert type(coords_data) == list, "Cannot process a comparison with "+\
+            "the data from a single coordinates file"
+
+    mapping_file = [mapping_header] + mapping_data
+    coords_file = [coords_header, coords_data]
+
+    # number PCoA files; zero for any case except for comparison plots
+    clones = 0
+
+    if custom_axes and type(coords_data) == ndarray:
+            # sequence ported from qiime/scripts/make_3d_plots.py @ 9115351
+            get_custom_coords(custom_axes, mapping_file, coords_file)
+            remove_nans(coords_file)
+            scale_custom_coords(custom_axes, coords_file)
+    elif type(coords_data) == list and is_comparison == False:
+        # take the first pcoa file as the master set of coordinates
+        master_pcoa = [coords_header[0], coords_data[0],
+            coords_eigenvals[0], coords_pct[0]]
+
+        # support pcoas must be a list of lists where each list contain
+        # all the elements that compose a coordinates file
+        support_pcoas = [[h, d, e, p] for h, d, e, p in zip(coords_header,
+            coords_data, coords_eigenvals, coords_pct)]
+
+        # do not apply procrustes, at least not for now
+        coords_data, coords_low, coords_high, eigenvalues_average,\
+            identifiers = summarize_pcoas(master_pcoa, support_pcoas,
+                method=jackknifing_method, apply_procrustes=False)
+
+        # custom axes and jackknifing is a tricky thing to do, you only have to
+        # add the custom values to the master file which is represented as the
+        # coords_data return value. Since there is really no variation in that
+        # axis then you have to change the values of coords_high and of
+        # coords_low to something really small so that WebGL work properly
+        if custom_axes:
+            coords_file = [master_pcoa[0], coords_data]
+            get_custom_coords(custom_axes, mapping_file, coords_file)
+            remove_nans(coords_file)
+            scale_custom_coords(custom_axes, coords_file)
+
+            # this opens support for as many custom axes as needed
+            axes = len(custom_axes)
+            coords_low[:, 0:axes] = zeros([coords_low.shape[0], axes])
+            coords_high[:, 0:axes] = ones([coords_high.shape[0], axes])*0.00001
+            coords_data = coords_file[1]
+
+        # return a value containing coords_low and coords_high
+        return identifiers, coords_data, eigenvalues_average, master_pcoa[3],\
+            coords_low, coords_high, clones
+    # comparison plots are processed almost individually
+    elif type(coords_data) == list and is_comparison:
+
+        # indicates the number of files that were totally processed so other
+        # functions/APIs are aware of how many times to replicate the metadata
+        clones = len(coords_data)
+        out_headers, out_coords = [], []
+
+        for index in range(0, clones):
+            headers_i = coords_header[index]
+            coords_i = coords_data[index]
+
+            # tag each header with the the number in which those coords came in
+            out_headers.extend([element+'_%d' % index for element in headers_i])
+
+            if index == 0:
+                # numpy can only stack things if they have the same shape
+                out_coords = coords_i
+
+                # the eigenvalues and percents explained are really the ones
+                # belonging to the the first set of coordinates that was passed
+                coords_eigenvals = coords_eigenvals[index]
+                coords_pct = coords_pct[index]
+            else:
+                out_coords = vstack((out_coords, coords_i))
+
+        coords_file = [out_headers, out_coords]
+
+        if custom_axes:
+            # this condition deals with the fact that in order for the custom
+            # axes to be added into the original coordinates, we have to add the
+            # suffix for the sample identifiers that the coordinates have
+            if clones:
+                out_data = []
+                for index in range(0, clones):
+                    out_data.extend([[element[0]+'_%d' % index]+element[1::]
+                        for element in mapping_data])
+                mapping_file = [mapping_header] + out_data
+
+            # sequence ported from qiime/scripts/make_3d_plots.py @ 9115351
+            get_custom_coords(custom_axes, mapping_file, coords_file)
+            remove_nans(coords_file)
+            scale_custom_coords(custom_axes, coords_file)
+
+    # if no coords summary is applied, return None in the corresponding values
+    # note that the value of clones will be != 0 for a comparison plot
+    return coords_file[0], coords_file[1], coords_eigenvals, coords_pct, None,\
+        None, clones
+
+def _is_numeric(x):
+    """Return true if x is a numeric value, return false else
+
+    Inputs:
+    x: string to test whether something is or not a number
+    """
+    try:
+        float(x)
+    except:
+        return False
+    return True
+
+def fill_mapping_field_from_mapping_file(data, headers, values,
+                                        criteria=_is_numeric):
+    """
+    Inputs:
+    data: mapping file data
+    headers: mapping file headers
+    values: string with the format a format of
+            Category:ValueToFill;Category:ValueToFill ...
+            or
+            Category:ColumnToSearch==ValueWithinTheColumnToSearch=ValueToFill
+    criteria: function that takes a value and returns true or false, default is
+    to check if the inputed value is numeric or not.
+
+    Output:
+    data: Filled in mapping file data
+
+    """
+    out_data = deepcopy(data)
+
+    # parsing the input values
+    values = map(strip, values.split(';'))
+    values_dict = {}
+    for v in values:
+        colname, vals = map(strip, v.split(':', 1))
+        vals = map(strip, vals.split(','))
+        assert len(vals)==1,  "You can only pass 1 replacement value: %s" % vals
+        if colname not in values_dict:
+            values_dict[colname] = []
+        values_dict[colname].extend(vals)
+    
+    for key, v in values_dict.items():
+        for value in v:
+            # variable that is going to contain the name of the column for multiple 
+            # subtitutions 
+            column = None
+            # variable to control if the values with in a column exist
+            used_column_index = False
+            
+            try:
+                header_index = headers.index(key)
+            except ValueError:
+                raise EmperorInputFilesError, ("The header %s does not exist in the "
+                    "mapping file" % key)
+            
+            # for the special case of multiple entries 
+            if '==' in value and '=' in value:
+                arrow_index = value.index('==')
+                equal_index = value.rindex('=')
+                assert ((arrow_index+2)!=equal_index and (arrow_index+1)!=equal_index), \
+                    "Not properly formatted: %s" % value
+                
+                column = value[:arrow_index]
+                column_value = value[arrow_index+2:equal_index]
+                new_value = value[equal_index+1:]
+                
+                try:
+                    column_index = headers.index(column)
+                except ValueError:
+                    raise EmperorInputFilesError, ("The header %s does not exist in the "
+                        "mapping file" % column)
+            
+            # fill in the data
+            for line in out_data:
+                if criteria(line[header_index]) == False:
+                    if not column:
+                        line[header_index] = value
+                        used_column_index = True
+                    else:
+                        if line[column_index] == column_value:     
+                            line[header_index] = new_value
+                            used_column_index = True
+            
+            if not used_column_index:
+                raise EmperorInputFilesError, ("This value '%s' doesn't exist in '%s' or "
+                "it wasn't used in for processing" % (column_value, column))
+            
+    return out_data
+
+def sanitize_mapping_file(data, headers):
+    """Clean the strings in the mapping file for use with javascript
+
+    Inputs:
+    data: list of lists with the mapping file data
+    headers: list of strings with the mapping file headers
+
+    Outputs:
+    s_data: sanitized version of the input mapping file data
+    s_headers: sanitized version of the input mapping file headers
+
+    This function will remove all the ocurrences of characters like ' or ".
+    """
+    all_lines = [headers] + data
+    out_lines = []
+
+    # replace single and double quotes with escaped versions of them
+    for line in all_lines:
+        out_lines.append([element.replace("'","").replace('"','')
+            for element in line])
+
+    return out_lines[1::], out_lines[0]
diff --git a/images/emperor-logos.ai b/images/emperor-logos.ai
new file mode 100644
index 0000000..6684a99
--- /dev/null
+++ b/images/emperor-logos.ai
@@ -0,0 +1,1197 @@
+%PDF-1.5
%����
+1 0 obj
<</Metadata 2 0 R/OCProperties<</D<</OFF[32 0 R 59 0 R 86 0 R]/ON[7 0 R 33 0 R 60 0 R 87 0 R]/Order 88 0 R/RBGroups[]>>/OCGs[7 0 R 33 0 R 32 0 R 60 0 R 59 0 R 87 0 R 86 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<</Length 51656/Subtype/XML/Type/Metadata>>stream
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
+<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27        ">
+   <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+      <rdf:Description rdf:about=""
+            xmlns:dc="http://purl.org/dc/elements/1.1/">
+         <dc:format>application/pdf</dc:format>
+         <dc:title>
+            <rdf:Alt>
+               <rdf:li xml:lang="x-default">Web</rdf:li>
+            </rdf:Alt>
+         </dc:title>
+      </rdf:Description>
+      <rdf:Description rdf:about=""
+            xmlns:xmp="http://ns.adobe.com/xap/1.0/"
+            xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/">
+         <xmp:CreatorTool>Adobe Illustrator CS6 (Macintosh)</xmp:CreatorTool>
+         <xmp:CreateDate>2012-11-02T14:07:45-06:00</xmp:CreateDate>
+         <xmp:ModifyDate>2013-02-04T15:12:08-07:00</xmp:ModifyDate>
+         <xmp:MetadataDate>2013-02-04T15:12:08-07:00</xmp:MetadataDate>
+         <xmp:Thumbnails>
+            <rdf:Alt>
+               <rdf:li rdf:parseType="Resource">
+                  <xmpGImg:width>176</xmpGImg:width>
+                  <xmpGImg:height>256</xmpGImg:height>
+                  <xmpGImg:format>JPEG</xmpGImg:format>
+                  <xmpGImg:image>/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA&#xA;AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK&#xA;DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f&#xA;Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAACwAwER&#xA;AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA&#xA;AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQV [...]
+               </rdf:li>
+            </rdf:Alt>
+         </xmp:Thumbnails>
+      </rdf:Description>
+      <rdf:Description rdf:about=""
+            xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
+            xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
+            xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#">
+         <xmpMM:RenditionClass>proof:pdf</xmpMM:RenditionClass>
+         <xmpMM:OriginalDocumentID>uuid:65E6390686CF11DBA6E2D887CEACB407</xmpMM:OriginalDocumentID>
+         <xmpMM:DocumentID>xmp.did:0180117407206811822AD8B6EC7B82C8</xmpMM:DocumentID>
+         <xmpMM:InstanceID>uuid:d9617043-b01d-264a-b26d-ab0ce760bc56</xmpMM:InstanceID>
+         <xmpMM:DerivedFrom rdf:parseType="Resource">
+            <stRef:instanceID>uuid:0422f9ea-96fe-9946-a099-8d9eefc40c79</stRef:instanceID>
+            <stRef:documentID>xmp.did:66A6819719206811822A897E387FE54C</stRef:documentID>
+            <stRef:originalDocumentID>uuid:65E6390686CF11DBA6E2D887CEACB407</stRef:originalDocumentID>
+            <stRef:renditionClass>proof:pdf</stRef:renditionClass>
+         </xmpMM:DerivedFrom>
+         <xmpMM:History>
+            <rdf:Seq>
+               <rdf:li rdf:parseType="Resource">
+                  <stEvt:action>saved</stEvt:action>
+                  <stEvt:instanceID>xmp.iid:0180117407206811822AD8B6EC7B82C8</stEvt:instanceID>
+                  <stEvt:when>2012-11-02T14:07:45-06:00</stEvt:when>
+                  <stEvt:softwareAgent>Adobe Illustrator CS6 (Macintosh)</stEvt:softwareAgent>
+                  <stEvt:changed>/</stEvt:changed>
+               </rdf:li>
+            </rdf:Seq>
+         </xmpMM:History>
+      </rdf:Description>
+      <rdf:Description rdf:about=""
+            xmlns:illustrator="http://ns.adobe.com/illustrator/1.0/">
+         <illustrator:StartupProfile>Web</illustrator:StartupProfile>
+         <illustrator:Type>Document</illustrator:Type>
+      </rdf:Description>
+      <rdf:Description rdf:about=""
+            xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/"
+            xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#"
+            xmlns:stFnt="http://ns.adobe.com/xap/1.0/sType/Font#"
+            xmlns:xmpG="http://ns.adobe.com/xap/1.0/g/">
+         <xmpTPg:NPages>1</xmpTPg:NPages>
+         <xmpTPg:HasVisibleTransparency>False</xmpTPg:HasVisibleTransparency>
+         <xmpTPg:HasVisibleOverprint>False</xmpTPg:HasVisibleOverprint>
+         <xmpTPg:MaxPageSize rdf:parseType="Resource">
+            <stDim:w>513.000000</stDim:w>
+            <stDim:h>689.000000</stDim:h>
+            <stDim:unit>Pixels</stDim:unit>
+         </xmpTPg:MaxPageSize>
+         <xmpTPg:Fonts>
+            <rdf:Bag>
+               <rdf:li rdf:parseType="Resource">
+                  <stFnt:fontName>OSP-DIN</stFnt:fontName>
+                  <stFnt:fontFamily>OSP-DIN</stFnt:fontFamily>
+                  <stFnt:fontFace>DIN</stFnt:fontFace>
+                  <stFnt:fontType>TrueType</stFnt:fontType>
+                  <stFnt:versionString>Version 001.000 </stFnt:versionString>
+                  <stFnt:composite>False</stFnt:composite>
+                  <stFnt:fontFileName>OSP-DIN.ttf</stFnt:fontFileName>
+               </rdf:li>
+            </rdf:Bag>
+         </xmpTPg:Fonts>
+         <xmpTPg:PlateNames>
+            <rdf:Seq>
+               <rdf:li>Cyan</rdf:li>
+               <rdf:li>Magenta</rdf:li>
+               <rdf:li>Yellow</rdf:li>
+               <rdf:li>Black</rdf:li>
+            </rdf:Seq>
+         </xmpTPg:PlateNames>
+         <xmpTPg:SwatchGroups>
+            <rdf:Seq>
+               <rdf:li rdf:parseType="Resource">
+                  <xmpG:groupName>Default Swatch Group</xmpG:groupName>
+                  <xmpG:groupType>0</xmpG:groupType>
+                  <xmpG:Colorants>
+                     <rdf:Seq>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>White</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>255</xmpG:red>
+                           <xmpG:green>255</xmpG:green>
+                           <xmpG:blue>255</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>Black</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>0</xmpG:red>
+                           <xmpG:green>0</xmpG:green>
+                           <xmpG:blue>0</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>RGB Red</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>255</xmpG:red>
+                           <xmpG:green>0</xmpG:green>
+                           <xmpG:blue>0</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>RGB Yellow</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>255</xmpG:red>
+                           <xmpG:green>255</xmpG:green>
+                           <xmpG:blue>0</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>RGB Green</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>0</xmpG:red>
+                           <xmpG:green>255</xmpG:green>
+                           <xmpG:blue>0</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>RGB Cyan</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>0</xmpG:red>
+                           <xmpG:green>255</xmpG:green>
+                           <xmpG:blue>255</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>RGB Blue</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>0</xmpG:red>
+                           <xmpG:green>0</xmpG:green>
+                           <xmpG:blue>255</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>RGB Magenta</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>255</xmpG:red>
+                           <xmpG:green>0</xmpG:green>
+                           <xmpG:blue>255</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=193 G=39 B=45</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>193</xmpG:red>
+                           <xmpG:green>39</xmpG:green>
+                           <xmpG:blue>45</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=237 G=28 B=36</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>237</xmpG:red>
+                           <xmpG:green>28</xmpG:green>
+                           <xmpG:blue>36</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=241 G=90 B=36</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>241</xmpG:red>
+                           <xmpG:green>90</xmpG:green>
+                           <xmpG:blue>36</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=247 G=147 B=30</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>247</xmpG:red>
+                           <xmpG:green>147</xmpG:green>
+                           <xmpG:blue>30</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=251 G=176 B=59</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>251</xmpG:red>
+                           <xmpG:green>176</xmpG:green>
+                           <xmpG:blue>59</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=252 G=238 B=33</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>252</xmpG:red>
+                           <xmpG:green>238</xmpG:green>
+                           <xmpG:blue>33</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=217 G=224 B=33</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>217</xmpG:red>
+                           <xmpG:green>224</xmpG:green>
+                           <xmpG:blue>33</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=140 G=198 B=63</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>140</xmpG:red>
+                           <xmpG:green>198</xmpG:green>
+                           <xmpG:blue>63</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=57 G=181 B=74</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>57</xmpG:red>
+                           <xmpG:green>181</xmpG:green>
+                           <xmpG:blue>74</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=0 G=146 B=69</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>0</xmpG:red>
+                           <xmpG:green>146</xmpG:green>
+                           <xmpG:blue>69</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=0 G=104 B=55</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>0</xmpG:red>
+                           <xmpG:green>104</xmpG:green>
+                           <xmpG:blue>55</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=34 G=181 B=115</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>34</xmpG:red>
+                           <xmpG:green>181</xmpG:green>
+                           <xmpG:blue>115</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=0 G=169 B=157</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>0</xmpG:red>
+                           <xmpG:green>169</xmpG:green>
+                           <xmpG:blue>157</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=41 G=171 B=226</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>41</xmpG:red>
+                           <xmpG:green>171</xmpG:green>
+                           <xmpG:blue>226</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=0 G=113 B=188</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>0</xmpG:red>
+                           <xmpG:green>113</xmpG:green>
+                           <xmpG:blue>188</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=46 G=49 B=146</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>46</xmpG:red>
+                           <xmpG:green>49</xmpG:green>
+                           <xmpG:blue>146</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=27 G=20 B=100</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>27</xmpG:red>
+                           <xmpG:green>20</xmpG:green>
+                           <xmpG:blue>100</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=102 G=45 B=145</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>102</xmpG:red>
+                           <xmpG:green>45</xmpG:green>
+                           <xmpG:blue>145</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=147 G=39 B=143</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>147</xmpG:red>
+                           <xmpG:green>39</xmpG:green>
+                           <xmpG:blue>143</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=158 G=0 B=93</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>158</xmpG:red>
+                           <xmpG:green>0</xmpG:green>
+                           <xmpG:blue>93</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=212 G=20 B=90</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>212</xmpG:red>
+                           <xmpG:green>20</xmpG:green>
+                           <xmpG:blue>90</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=237 G=30 B=121</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>237</xmpG:red>
+                           <xmpG:green>30</xmpG:green>
+                           <xmpG:blue>121</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=199 G=178 B=153</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>199</xmpG:red>
+                           <xmpG:green>178</xmpG:green>
+                           <xmpG:blue>153</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=153 G=134 B=117</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>153</xmpG:red>
+                           <xmpG:green>134</xmpG:green>
+                           <xmpG:blue>117</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=115 G=99 B=87</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>115</xmpG:red>
+                           <xmpG:green>99</xmpG:green>
+                           <xmpG:blue>87</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=83 G=71 B=65</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>83</xmpG:red>
+                           <xmpG:green>71</xmpG:green>
+                           <xmpG:blue>65</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=198 G=156 B=109</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>198</xmpG:red>
+                           <xmpG:green>156</xmpG:green>
+                           <xmpG:blue>109</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=166 G=124 B=82</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>166</xmpG:red>
+                           <xmpG:green>124</xmpG:green>
+                           <xmpG:blue>82</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=140 G=98 B=57</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>140</xmpG:red>
+                           <xmpG:green>98</xmpG:green>
+                           <xmpG:blue>57</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=117 G=76 B=36</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>117</xmpG:red>
+                           <xmpG:green>76</xmpG:green>
+                           <xmpG:blue>36</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=96 G=56 B=19</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>96</xmpG:red>
+                           <xmpG:green>56</xmpG:green>
+                           <xmpG:blue>19</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=66 G=33 B=11</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>66</xmpG:red>
+                           <xmpG:green>33</xmpG:green>
+                           <xmpG:blue>11</xmpG:blue>
+                        </rdf:li>
+                     </rdf:Seq>
+                  </xmpG:Colorants>
+               </rdf:li>
+               <rdf:li rdf:parseType="Resource">
+                  <xmpG:groupName>Grays</xmpG:groupName>
+                  <xmpG:groupType>1</xmpG:groupType>
+                  <xmpG:Colorants>
+                     <rdf:Seq>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=0 G=0 B=0</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>0</xmpG:red>
+                           <xmpG:green>0</xmpG:green>
+                           <xmpG:blue>0</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=26 G=26 B=26</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>26</xmpG:red>
+                           <xmpG:green>26</xmpG:green>
+                           <xmpG:blue>26</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=51 G=51 B=51</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>51</xmpG:red>
+                           <xmpG:green>51</xmpG:green>
+                           <xmpG:blue>51</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=77 G=77 B=77</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>77</xmpG:red>
+                           <xmpG:green>77</xmpG:green>
+                           <xmpG:blue>77</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=102 G=102 B=102</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>102</xmpG:red>
+                           <xmpG:green>102</xmpG:green>
+                           <xmpG:blue>102</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=128 G=128 B=128</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>128</xmpG:red>
+                           <xmpG:green>128</xmpG:green>
+                           <xmpG:blue>128</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=153 G=153 B=153</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>153</xmpG:red>
+                           <xmpG:green>153</xmpG:green>
+                           <xmpG:blue>153</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=179 G=179 B=179</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>179</xmpG:red>
+                           <xmpG:green>179</xmpG:green>
+                           <xmpG:blue>179</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=204 G=204 B=204</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>204</xmpG:red>
+                           <xmpG:green>204</xmpG:green>
+                           <xmpG:blue>204</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=230 G=230 B=230</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>230</xmpG:red>
+                           <xmpG:green>230</xmpG:green>
+                           <xmpG:blue>230</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=242 G=242 B=242</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>242</xmpG:red>
+                           <xmpG:green>242</xmpG:green>
+                           <xmpG:blue>242</xmpG:blue>
+                        </rdf:li>
+                     </rdf:Seq>
+                  </xmpG:Colorants>
+               </rdf:li>
+               <rdf:li rdf:parseType="Resource">
+                  <xmpG:groupName>Web Color Group</xmpG:groupName>
+                  <xmpG:groupType>1</xmpG:groupType>
+                  <xmpG:Colorants>
+                     <rdf:Seq>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=63 G=169 B=245</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>63</xmpG:red>
+                           <xmpG:green>169</xmpG:green>
+                           <xmpG:blue>245</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=122 G=201 B=67</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>122</xmpG:red>
+                           <xmpG:green>201</xmpG:green>
+                           <xmpG:blue>67</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=255 G=147 B=30</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>255</xmpG:red>
+                           <xmpG:green>147</xmpG:green>
+                           <xmpG:blue>30</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=255 G=29 B=37</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>255</xmpG:red>
+                           <xmpG:green>29</xmpG:green>
+                           <xmpG:blue>37</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=255 G=123 B=172</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>255</xmpG:red>
+                           <xmpG:green>123</xmpG:green>
+                           <xmpG:blue>172</xmpG:blue>
+                        </rdf:li>
+                        <rdf:li rdf:parseType="Resource">
+                           <xmpG:swatchName>R=189 G=204 B=212</xmpG:swatchName>
+                           <xmpG:mode>RGB</xmpG:mode>
+                           <xmpG:type>PROCESS</xmpG:type>
+                           <xmpG:red>189</xmpG:red>
+                           <xmpG:green>204</xmpG:green>
+                           <xmpG:blue>212</xmpG:blue>
+                        </rdf:li>
+                     </rdf:Seq>
+                  </xmpG:Colorants>
+               </rdf:li>
+            </rdf:Seq>
+         </xmpTPg:SwatchGroups>
+      </rdf:Description>
+      <rdf:Description rdf:about=""
+            xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
+         <pdf:Producer>Adobe PDF library 10.01</pdf:Producer>
+      </rdf:Description>
+   </rdf:RDF>
+</x:xmpmeta>
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                                                                                                    
+                           
+<?xpacket end="w"?>
endstream
endobj
3 0 obj
<</Count 1/Kids[9 0 R]/Type/Pages>>
endobj
9 0 obj
<</ArtBox[0.0 7.0 485.849 689.0]/BleedBox[0.0 0.0 513.0 689.0]/Contents 89 0 R/LastModified(D:20130204151208-07'00')/MediaBox[0.0 0.0 513.0 689.0]/Parent 3 0 R/PieceInfo<</Illustrator 90 0 R>>/Resources<</ColorSpace<</CS0 91 0 R>>/ExtGState<</GS0 92 0 R>>/Font<</TT0 84 0 R/TT1 85 0 R>>/ProcSet[/PDF/Text]/Properties<</MC0 87 0 R/MC1 86 0 R>>>>/Thumb 93 0 R/TrimBox[0.0 0.0 513.0 689.0]/Type/Page>>
endobj
89 0 obj
<</Filter/FlateDecode/Length 2295>>stream
+H��WɎ
7��W�(
��}�v�1���l� 
�95�
�d��~/Hf2�J^�:AR�
�E��No���t6�ỳZ>/F�RU�^��������<\����Q�_��?�����,���r�\����a��h�}�=�`�.�c�!��./˫ǧ߾y�������G�?C�(T��R�U�Vm}���e�/��ڬs�������Y0F���t�wR�.��iˍ
E��}�����t�Q4����`wD R|�œζN��v����#2WB,�؄�v�wg��
+1�j\�
�C����p#o:�:��I�k����@��M�?��t�Y,� ӂ���m����O�Qۘ ByM���~�Fg`�=
O��
O1�G���B�%��.�ԦJ��"�-
��0:S$p�烵�*]��(�/C{�?.�n�eu�7� ����չ�BO�����
+�SQA��7�aS�&n�r�rS��>�q��N��p�
+CJ�筃��6]|���F�-r�i��/��������1n�c�^��U��kq�Pw%�
W��H���Z���-
f����>ŝ�xr.W�&1&f�:3;{`v������mggrW�6c��r;Z{�6���
�������m�
�>�En�]����&ˍ�4.{1H�=q�[d_)xO<���&����=T���0gqƐ��#'Ɩ��&</+����`r�c0^Bb
A��) ��м�ԝe{_ u�-���
+7�y%�!�V"���
x6lZ3���
���dC�<3��u����;~�h�i3i�B�q3ͳ�kH���iMސuy���f�M��x�i�v`���.�����K�p$��X���͟]B�ďe�I�q��\�J%�3;
�s�s�~
���rț���F[uMkI��q͊Rl*
�'�5iv킵�R��;]�7�mC>����W�y
w�D;��m��me�b,�5u�(G7<_�^@(,.������]��2AL-�OF�	Wxؖc'
���VI]99I�Ք!�l@��gI*~
풹���}ڽ����z�
+STɪ�W
L���r��o�y;�EIY����~LN�Mm��]�e�Θ
�4�Mp\�@ū��
��/��hF�l"-�#%⼂�c31��CJ-��u����;J�H���S�-�a����I�����K�Ѭ7�bZQ_���h�a4�Ԏ�&\7D����5L�+�6���
+{cA�\��
+ȍ���|�^�Iۂ�D^��0D�9/E�f2�i�20MI�U�ߐ����B�jX��½�`3�T�$�T>|�bg��F���8�r<37
�>ǭ�F.ʒ�W,�	a&�
txj0����v=�]����x=B^��������}��@���™R<
m��
8ƛ�%3�D���J@•q��H�7}_o�
+�1��[�G�7����n�s�JS[d��^���2�`#�R�A�M
ڋ�� �<��zA�a���f+
(B�Hpޮ�z�:��Ge 
�q_W�6�{:��ش�!B�v#�%�n2xF����@ߓ�%��|'$J ��N�� �̋�FiN<��a!�2s�i�
;�e�wc�

]?��<�6�Mb�ۛlơ?�
�ͻ����|�K�w堡�2q�<�͈��JD�l��fn���E���eN���6���s��
+)
���"B�R�Lۂ��2�0!�XP�
+��D�CtN���.�
Gf���|�HU�<-��]�_.�N�c�cS��NJ��A�(Uݳ-�N&K&4�����
��L�Y�9�z���9�,́��I���;`�
�dd�7`p55{#1Cfal�C	 ��A��o� �6
U�z�
��
���k�/��x%d7 ��H�F"��^��)e[�7���z�N�~GN�e n�Z~���@M��[�w+�Q�g���B�E�
C�
+�P��^�,x�EG\up�~��
d<��
+�`�
D���S��Bb��g�0௿`�M
+FFP2
+
6݋��W�h��wSf���ɸ��;���T��T�1�������tV���Y���V=|	�8�6�����W��8�!�+*l
n�(���R-m� a�^�'�Ee��
9���3B"��^�.�_㓙�"S�+^8]9z
92.��a����䂦W���9�`�;��G^״d�c1�1(��
+�$

H�X�9�@��p��l�Y!��50[�ؐгU��d=Z�"�k 
�p����$�$!���"[��*HB�u�P����r�\H�ˇ��
ţ�e͹G�
9�����������Ƿ�m~��50�z������K��O��
 ْ?�
endstream
endobj
93 0 obj
<</BitsPerComponent 8/ColorSpace 94 0 R/Filter[/ASCII85Decode/FlateDecode]/Height 86/Length 570/Width 64>>stream
+8;Z]a96,\.&-h(mP[;^Rm_Op#?RHNKd8(LI)JcA$.16;C>!\Yih%"8k.`tbni_:i'
+V&-2jSYV&5%C1.8V"4IlQW3F;[4..MDq__AiuV-jc*a'K\I at 8Nq(J:0H8RNn:\t'%
+*o/))"eJ$kmEPS%ENQu2s1K9AcMfOmC0fu7IR!hlI\93AXAuoi[<gT*`3O?[4P4tc
+!FWdDP"tmXVMpJBO*VmK-P#Nb_V3]jHpZ.Dik`>nrB'"&[s9mDVe/)+n!1'5bAp2Q
+fiVCTjG(F=s(qZ-&KmWN5BrdET_;4Eg`uNgk0\8pfk,X3%!V__+Wg=thAn4D&pYI?
+]&<T<Q<3RU+ciH&-9VbZo[&!86h+Tg_tnVn<);'i?RL-(50Iurk9S7>b"otH+7,U%
+?fjMR8,Ft5r2u[]*6%oTL at q@gY']FUr;D9in^N#P*qqMQ>NM6Mc[#5Ic?WEESj)?%
+r>qU(pA]n+o!tYADqBK[3s3&8JPU&VgVLSf$$XnDkOR69H?U;)P'tSfhH?*l^KkQ`
+DhX[Q,&_[ZS)<&$E5,#7\+:^<9@#g>.f\r;Iu/*)~>
endstream
endobj
94 0 obj
[/Indexed/DeviceRGB 255 95 0 R]
endobj
95 0 obj
<</Filter[/ASCII85Decode/FlateDecode]/Length 428>>stream
+8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0
+b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup`
+E1r!/,*0[*9.aFIR2&b-C#s<Xl5FH@[<=!#6V)uDBXnIr.F>oRZ7Dl%MLY\.?d>Mn
+6%Q2oYfNRF$$+ON<+]RUJmC0I<jlL.oXisZ;SYU[/7#<&37rclQKqeJe#,UF7Rgb1
+VNWFKf>nDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j<etJICj7e7nPMb=O6S7UOH<
+PO7r\I.Hu&e0d&E<.')fERr/l+*W,)q^D*ai5<uuLX.7g/>$XKrcYp0n+Xl_nU*O(
+l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~>
endstream
endobj
87 0 obj
<</Intent 96 0 R/Name(Layer 1)/Type/OCG/Usage 97 0 R>>
endobj
86 0 obj
<</Intent 98 0 R/Name(Layer 2)/Type/OCG/Usage 99 0 R>>
endobj
98 0 obj
[/View/Design]
endobj
99 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>>
endobj
96 0 obj
[/View/Design]
endobj
97 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>>
endobj
84 0 obj
<</BaseFont/BCFXQD+OSP-DIN/Encoding/WinAnsiEncoding/FirstChar 69/FontDescriptor 100 0 R/LastChar 82/Subtype/TrueType/Type/Font/Widths[366 0 0 0 0 0 0 0 546 0 384 375 0 385]>>
endobj
85 0 obj
<</BaseFont/BCFXQD+Futura-Medium/Encoding/WinAnsiEncoding/FirstChar 69/FontDescriptor 101 0 R/LastChar 82/Subtype/TrueType/Type/Font/Widths[565 0 0 0 0 0 0 0 961 0 894 540 0 602]>>
endobj
101 0 obj
<</Ascent 1057/CapHeight 761/Descent -289/Flags 32/FontBBox[-524 -289 1322 1057]/FontFamily(Futura)/FontFile2 102 0 R/FontName/BCFXQD+Futura-Medium/FontStretch/Normal/FontWeight 500/ItalicAngle 0/StemV 112/Type/FontDescriptor/XHeight 476>>
endobj
102 0 obj
<</Filter/FlateDecode/Length 5964/Length1 10435>>stream
+H��V	tU���Rݝ:diĪn��61`'�b�l	Y�@$��qcXz EY$	�ڀ0!a�DADG
0��(�ٓ��UwO�qΙ3��_���W������
+ 
� "stVBR���˖sܳs�sJ�r�uh 4$��2�X�

�/��.%ӊw/֯�y�dM+z�@gH�t� q~a~Nލ�[@�
�ߧ�
Cu
a<���N��es[�i
��S
㣢ٹ9�3���m����s�ܦ�"u���Y9��΂��� @��dviYč�\��&���dN~ɮ�.3�G��-H�r�� ��q�8[�����(���"
� 	�8��m�u�ڽQ�׹�0W?Z�T@�_���sr�fG����H�b_H"�
yHa\md��>
/���˴JxH8 |*|&|'J�^
��}���6��D*핎�Y�U���U٩��X�`�ͭ�m�Q����8�0s�9?���F�����PTl�q�(����!�b��%Bi�tPJ�ߠ�3�C�(y>b�(�B���{�{�{Z���#�<e�R�
������u��n�\��k����]g\'��6�*j�
��js���X��vjm��0}Yп�l��S&'w���5ڇ���j��v
ߡ.��\����@9��;.�".�wq���؆
؎����C| 'vav�#��
�C����A5��	�p�=4�4��N���=�7	$�BZD��b�(�
��4�F� [...]
��<z�V� N�T@}(���;��[�B�i�`ʤ�4�fл����|�Sԏ��L*"+��n�Hk�
��(�7h(=Iݩm��6Q1͢��
�R��8�M%��ʩ�*q�(�zQ_\�/D4?�W�3�P�+�F
����h"#��,ÿq~[s�s��D��F��0�k�#
���

�Ģ3��QtE
��
ݑ�D�@z�z�����0�a
�=;C��HE�b�1
�������E��I��S����11	�1���T����G�(�����"cf��`JQ�g�
~����
l�&�b9+��U����5�>��C��v��4��c�U�ުYqXm��°��
x���U�*���a���]��'X��8���
�r�?��8��X���x�c!W�$���X��a䜾��yW�B�����O�o�O��DR8� g�+�
+��	�p>m7c
���ǿ��k{�Jy�i��2~dM\��*V�+������{
g)�G�p��@��
�x&Y�����* >�
+�L�.��*�.������-N�ܭ
+�(�ӭN��φ�*��x%�)Ƥ��Y�Cq��9�4�0'�)���� �aOP�ȲM��8��L�����v{�#iq$_
��#�D�������8��L��s���L��M���:eڜ��&�ng/��)�_��.�Yǜ�<���dq
aw83!Vu
r8L^��bQ��T�cR�(%��(Ţ�4�E����n�؆��,[*3Q����uT�`q�E.oz5W�w_
�0V�� �X0��j�&����q�=`�f �5�t�
���s�=z��᝹J�ܗe���qA���w�]
+c��kx�geIq�O�'X
�G��~�`�)5�0A�)J>���v��=H
W�]
+s���Jaf�Nq\P7��L��x��,�,���,&�t���3H�Aja0h�f�D�h q�
+m��԰Ɠ�V�/6fJ�{
�-MQb]�f[�(W�1�M���ezm!L�h��
�|3}s����B5����!5�
���o���>��6:ܨ�U�s�c��I�}z��
k1�䢆z��
��V����s��s��;��RYY�����
+1�Ү^��������ʗ��ċ���oީܺ���k��6c#O��&�o$''k�`z>4Kp�
+D���b�f��p
$���КC�����-�jR���Q���sO�w/�B�����ܼ�<�=E�={G
+����dʘ䛻��4Xnf���g%��/z.���
eK�%��e��X����f���џ��
Rɢ�U4�r����	�z.x�v�Bᔴd��u8exf	�ܳ���%��t��[�V���?a*:h�Oi=�֯�Wkl�>�;;�'� I��R��^��
D	�@k��8�i�2޽ޝ2;���Ŭ�*5�H�6�Q�R	%��*yT���"�R5�@�RE�JhJ@�"R����;wf� ҿU�xt|�93�<�s��;��)��v�Mj<��ƳWO_�_�#$c���#8�.��p���ɺ
7��j���kl at b[Cmb2]I���y
+j�7	�_d1Rڱd/�^w��c�
�m�ցwj�k��C�㫨���lH9�I�?��sT�}B}�@�7H��#j�~}Z��=��k���x�
ȷ!]�
��}�
���1~��%G���B)��%v��>};d,��q�>�������D;�R_r?=m��q:�zG4�Ŝ}d�����G�eQ*�
v?������"v����(�ц�������M���ԸI/Ҧ�&Z�� �R�6 at 6K��½F'��^?��91�㋩-����z�4�h��z�	%�����v�
F
��.׀�� S���	�3�
�{�%�<��W�F}m�};s�:�����%�{�!ݘ����O�T}�k?��Ԗ�oK
��,�Lۘ�[I���G}å�9�qv�(�#�9���!�B�w�W��(
�p�7}�x�:�y'��
�f1�d�[�
+=��)��~/����4�<`
,�~<�\�{�V����
�A���
z�ׅ�������WOA.A��y5Q}	���/�/��eȳ�i�y�I�
��}��X��G�Έe)�
O�|�[�С��E���/����Ne�`^k#󜸢��3�+��(�7�[ĵ�Ƶ�~�X�M��=D���U[O�e�3�f�"���p.���q�W�>�pl"s��`����ڡ�'�I����
�n�DZ
���-�T�`
��C
���g������ �[���csԻ���
ۃߦZ�%�E��
Z�x��ZO����T�;�zW:
��~�w
y�Y`�Xj����W{��q<jm�y��z
���k]_�qd*?׾�
�1�s�������0����i�h��� ���/���L=[}>��������j5���c��X=��W��\�
�^O�
�
�[��PN7�����Ӣ�5Z(Pu��l��+��6�������
�A:�ݥp�������}�	Q{�6��
���
Y�3ik��h�ϑL���]�c��"
�/Ӆ�
��"���vG�
�'D8IG�)
E㴋�����3£iR���
b��dD�5��{"
�Q���+Øa�tcq��h,�0�����3��5�
�z����c�'
��
�p����c<����Sx��;
�\?Sx���G�;>����Q����8��J�'Ʃ0���)��{�2�T�|!M��-
b�,wo�l[��V�
+L;-��������\+:V�]ѱn���,X��v�@`���t|�k�ߵ�L''�fEi���d�ss�̉
+(��@)z<+�#�.��}�
5��{�:�/����6�N�-8.pEL˥o��Bze9({f��Y�b�Ex�P<�n�5˦��9��ܘ����:��y�k
aY�H"o���D�9Y4��
�R�,=�Y)I_�]��9f9z8e_
+�^���@�>��
��2�}
�T,��W�|�r���mK?j�m�!
�ljmn�5RӲ�,�`��1�cɴ
d� �J��{f���n3+EN1��\�@�Y<N��2葨bSk�E=ijmZ 
+���UT����r���<�h/;V� J�.��a���x�d+ at Z��*�L�Վ�X��]����~�d��W��R����*�9�9�D���� ���0�Z���ȺE��ý-�U+�NԖlq���*Gs�5Q�.�b���8*�0o����
{�A�8kx4�"�C<�k��7;
��f�
U'�T�Mo�v��L��k�E�yk�Tl�Awن��"�.�MY�
p���1�Y�C�T[��� o]�c����,�"k���5�pջ
+�+s *|�Qd�,�ܬY���2�=�[{�-I�o��	~��t�2�ؼ�m�(�(<�w�a�zҶ��ǀ\��a�Ud�{K �h
ӫ(/
�QL��:Ū�����s���#-��tЦQ�Q3��
m?)6Sā��{�D�b��Y2���~����n配���\l��w��V�7�r��È��b?
��6)�jw
�V�B�
�3���'����4:���K at m�0�+7��Y_�mV>��"H��k
kk���R;IʑEe*�*2ɦ.�.�5��H�R�Π���5�̋��g.
����k�e�G�
}���>_��/�[���
I����
�a���Wm��t�?�k����V�~Epr�~K�ѯ�mz�~C��Kt���QzS�k���
-�SK'�$�p
��٣�n���Cci
��;�.�@i�M���t/}��#A�q��BSiM�4�f�l�����4�2�D�4���|���
E��/ew����`C�K�� !Y�
!�lB	�N6��$��0���(6�HQ@+����p�
���
۱{����μ�Ÿ��}{�k{��ƣR^q1.���
�p9&�
+S��� ����M��
��6
�h�
�D3�hA+fa6�`.�0�-���J6e�b)�� 6�K,�-X�{�
��J�hKp~��X�MX���~�f�Ư����ųx�QǰV6�y��g�
|\ޚ��#�W�D�BJ���~
`�
���5X,���p=n��X�[��q;6�܉�p7��9���
;� v�<�ǰ�c?�O�I���^�+x�
�|o�-��w�.����PX�G���3|���\���5��������?�'����?�!����8���p
�#y����<�o
�<�'�$��S8���4��h��3x&���<��<���|^�
+^ȋ8�c9��Y�	�ȋy	/�e���9�U��j�0̩�e
#��46p:��$g��Q����8�s8�m�����
x%���v��A����{�`7{�`/�4�Dž��b��f?���
�ռ��y-�����7r	�r��&���\�U\�5���r-�q=o�������;x'��ݼ��r3��>����۸�;� wrw�!����C�eJ�Kwu��߭�S�,3ᔏO�fP]KU7gPm4�2;�]�BK�e
F��4�����ҨT�^M��4�`D�	�M1
�B�RF�������n�#��}����������O���E��m)�iiR��6���m��ocJ̤C3�'}�P&P�,E��U�����t��px�a���΄����2S�-F����iM:��l�f��P4�Y�Vע:
Cm���A]+�^R���\�e'ㅝ�^��s*��^�c�ܕO
_�x�X_Ts~����|mJK)�V�C��2�B�z�m
fZ�_SC�e�pEp(k�kV���-遾(�y��WM�2��WkT
��V��,F��ބ�Һ<���Z�:-��5�.�"�
���R�4�>���^z]%�P"�L�*���g�T���>$U�K��j1'�tW�����<�+
g�&�ga ��Qs���(�"�
���K]�q�i�W�!И�L�&��T2М��2���l��`+��x�f@ܹ�����w�b٫�
���-�DEt�ܓP�]�(��!��l������x�g  �ew⪙	:Zh��a�f2n
+�Jh�N�T^i����DC�
V�'���autW�j�|�y��9�x���r�q7�g(Q
W��8���֍X�>/@4�r8 �䩓�u�
"�Fw�!7�DnEM*��D����D���jg�
��s��`
^h���xW:Y�ҮYEi�S�|��B�J
2V^~P��$a:��#D�
�-,i����ޤ�aK#�O)���?*Nu��
�H~95�8/�~�k���F����/� �${�
endstream
endobj
100 0 obj
<</Ascent 852/CapHeight 700/Descent -203/Flags 32/FontBBox[-52 -203 617 852]/FontFamily(OSP-DIN)/FontFile2 103 0 R/FontName/BCFXQD+OSP-DIN/FontStretch/Normal/FontWeight 500/ItalicAngle 0/StemV 100/Type/FontDescriptor/XHeight 500>>
endobj
103 0 obj
<</Filter/FlateDecode/Length 1576/Length1 2757>>stream
+H��V{lSU��so��ֱ!/��,{б�c�!�`s""��޶lkg�Q�@DD��""F4
+�i�5�Q��&"y�1���
+$ ���޶���?��w��w���s�` R�

+�U�y��W�dA��G�k��� ,����
�
+���WI/{�՝u�ς�I���
�K��	RyI0RtK3����
�E�W�#��b���;��e��Y#�����b5�4�����}<g
�'i4�{�p��@�]Ɨ�
4�l��Z)Bf
KG��䑒d挛��<\Ӫ��.��ªͪ�b��{�
��)6Q�/��O5������l�T�%�+�S��ֽG�%�gzF��{��ۯ�
2kV��A9��
��6,7/����>|�Ȼ�.
URzF�;���b|儉�&�;復Ӫ�Ϩ�������zh6܀�E���<
+,]ֲ��'V�|�W=�X��

Ro���a��3p"���Us��/��7��A`a�).��2�X@����,
�P��Rl�N��^��S|�C8���
?�
.�.�wV-��c�06������؏/q
���'���!��A���IqB������.^��V��"6��F�xNl%���?j�YfT
�tnR �Śa���{4�ٵ(k5�F55�y��>��t��#Y�-���NHbZ�QF��fg��Z;�]Q%hvg���}�\��/�aI7g匰
����z�
a!�%kmmZ�S[RR[jj}��j����E���.�i'�k'm%%6f=ϲl��z
��6S�
��5��,�=-v=��l'k��Dj
<�ܚ�k5m��jjM��U�j�\+�5��X�ћb��X]
+{fK�7�s���΀<R��M%
-\5�:�3)j~���Io�hn�ҋHI2���[�I`�I�'�7!6)~���|D��=I m�|��]^�������`�"��A��Z��'��t��"��m�e�
��/�$h��J�i��8&��u�V���?�v��L�A*�,
��_&����@J#�'�
+/�B|��cry��R
]O7Q�E8�t�P�g�+����ϔJ�s���O�̷#�+��.H'�ʿ���=b`�Cx��Ϣ��#݈�[3�Ms�Mx3&˻brʩ"a�P�v#��B�ep^,��4߅l�7��c��:���J����/�b�F
PY�
r�	`[�F=6�M�ؗX�u�s�\z�ׂ��検�|�Pn�J�f���
�xS�3T�Μ�iV��_1����������m��߶��
+e#w���H�����O��:$
I��
uqZ&�9N��^���rW���8���i��,a#�<a#�N�6
-�
a�Kq6+
��/
+��)S�\>��w)�����V�ܞ�_���^�2�W�
�R�W�
����U���4/�����J�Rl
+���!"�
+
+
��+gN��M��ʭ�4�V
���ځ0�.z򜔼��$	R�!D= ?��t
+���G��$��8�a����/��c���'i�~T������-��g<�C�"L���O]�۪��y��F}�0=�
+r����}un���Q�B�e�W%fb:]��U���$:
3�56����,��N����M5F�C� 0�Z#
endstream
endobj
92 0 obj
<</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask/None/Type/ExtGState/ca 1.0/op false>>
endobj
91 0 obj
[/ICCBased 104 0 R]
endobj
104 0 obj
<</Filter/FlateDecode/Length 2574/N 3>>stream
+H���yTSw�oɞ����c
[���5la�
QIBH�ADED���2�mtFOE�.�c��}���0��8�׎�8G�Ng�����9�w���߽��� �'����0
 �֠�J��b�	 
+  2y�.-;!��K�Z�	���^�i�"L��0���-��
 @8(��r�;q��7�L��y��&�Q��q�4�j���|�9��
+�V��)g�B�0�i�W��8#�8wթ��8_�٥ʨQ����Q�j@�&�A)/��g�>'K�� �t�;\��
ӥ$պF�ZUn���
�(4T�%)뫔�0C&�����Z��i���8��bx��E���B�;�����P���ӓ̹�A�
om?�W=
+�x������- �����[��� 0�
���}��y)7ta�����>j���T�7���@���tܛ�`q�2��ʀ��&���6�Z�L�Ą?
�_
��yxg)˔z���çL�U���*�u�Sk�Se�O4?׸�c����.� �
 �� R�
߁��-��2�5������	��S�>ӣV����d�`r��n~��Y�&�+`��;�A4�� 
���A9� =�-�
t�
�
l�`;��~p����	�Gp
|	��[`L��`<� "A
�
YA�+��Cb(��R�,� *�T�2B-�
+�ꇆ�
�n���Q�t�}MA�0�a
l������S�
x	��k�&�^���>�0|>_�'��,�G
!"F$H:R��!z��F�Qd?r
9�\A&�G�
��rQ
��h������E��]�a�4z�Bg�����E#H	�*B=��0H�I��p�p�0MxJ$�D1��D, V���ĭ����KĻ�Y�dE�"E��I2���E�B�G��t�4MzN��
���r!YK� ���?%_&�#���(��0J:EAi��Q�(�()ӔWT6U@���P+���!�~��m���D
�e�Դ�!��h�Ӧh/�
�']B/����ҏӿ�?a0n�hF!��X���8����܌k�c&5S�����
6�l��Ia�2c�K�M�A�!�E�#��ƒ�d�V��(�k��e���l
����}�}�C�q�9
+N'��)�].�u�J�r�
+��
w�G�	xR^���[�oƜc
h�g�`>b���$���*~� �:����E���b��~���,m,�-��ݖ,�Y��¬�*�6X�[ݱF�=�3�뭷Y��~dó	���t�
��i
�z�f�6�~`{�v���.�Ng����#{�}�}������
��j���
���c1X6���f
m
�
��;
'
_9	�r�:�8�q�:��˜�O:ϸ8������u��Jq���nv=���M���m���R 4	�
+n�3ܣ�k�Gݯz=�
�
[=��=�<�=G</z�^�^j��^��	ޡ�Z�Q�B�0FX'�+������t���<�u�-���{���_�_�ߘ�-G�,�
}���/���Hh
8�m�W�2p[���AiA��N�#8$X�?�A�KHI�{!7�<q��W�y(!46�-���a�a���a�W��	��@�@�`l���YĎ��H,�$����(�(Y�h�7��ъ���b<b*b��<�����~�L&Y&9
��%�u�M�s�s��NpJP%�M�I
JlN<�DHJIڐtCj'�KwKg�C��%�N��d�
�|�ꙪO=��%�mL���u�v�x:H��oL��!Ȩ��C&13#s$�/Y����������=�Osbs�rn��sO�1��v�=ˏ��ϟ\�h٢���#��¼����oZ<]T�Ut}�`IÒsK��V-���Y,+>TB(�/�S�,]6*�-���W:#��7�*���e��^YDY�}U�j��AyT�`�#�D=���"�b{ų���+�ʯ [...]
چ
���k�5%4��m�7�lqlio�Z�lG+�Z�z�͹��mzy��]�����?u�u�w|�"űN���wW&���e֥ﺱ*|����j��5k��yݭ���ǯg��^y�kEk�����l�D_p߶������7Dm����o꿻1m��l�{��Mś�
n�L�l�<9��O �[����$�����h�՛B���
�����d�Ҟ@���
�����i�ءG���&����v��V�ǥ8��������n��R�ĩ7���
����u��\�ЭD���-������ �u��`�ֲK�³8���%�������y��h��Y�ѹJ�º;���.���!������
+�����z���p���g���_���X���Q���K���F���Aǿ�=ȼ�:ɹ�8ʷ�6˶�5̵�5͵�6ζ�7ϸ�9к�<Ѿ�?���D���I���N���U���\���d���l���v��ۀ�܊�ݖ�
ޢ�)߯�6��D���S���c���s����
����2��F���[���p������(��@���X���r������4���P���m��������8���W���w����)���K���m��
 ����
endstream
endobj
90 0 obj
<</LastModified(D:20130204151208-07'00')/Private 105 0 R>>
endobj
105 0 obj
<</AIMetaData 106 0 R/AIPrivateData1 107 0 R/AIPrivateData2 108 0 R/AIPrivateData3 109 0 R/ContainerVersion 11/CreatorVersion 16/NumBlock 3/RoundtripStreamType 1/RoundtripVersion 16>>
endobj
106 0 obj
<</Length 959>>stream
+%!PS-Adobe-3.0 
%%Creator: Adobe Illustrator(R) 16.0
%%AI8_CreatorVersion: 16.0.0
%%For: (meg pirrung) ()
%%Title: (logo.ai)
%%CreationDate: 2/4/13 3:12 PM
%%Canvassize: 16383
%%BoundingBox: 460 -682 933 14
%%HiResBoundingBox: 460.8408 -682 932.8486 13.4839
%%DocumentProcessColors: Cyan Magenta Yellow Black
%AI5_FileFormat 12.0
%AI12_BuildNumber: 682
%AI3_ColorUsage: Color
%AI7_ImageSettings: 0
%%RGBProcessColor: 0 0 0 ([Registration])
%AI3_Cropmarks: 447 -689 960 0
%AI3_TemplateBox: 480.5 -280.5 480.5 -280.5
%AI3_TileBox: 433.1567 -678.6738 973.8438 10.3257
%AI3_DocumentPreview: None
%AI5_ArtSize: 14400 14400
%AI5_RulerUnits: 6
%AI9_ColorModel: 1
%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0
%AI5_TargetResolution: 800
%AI5_NumLayers: 2
%AI9_OpenToView: 248 26 1 1252 758 18 1 0 78 133 0 0 0 1 1 0 1 1 0 1
%AI5_OpenViewLayers: 67
%%PageOrigin:80 -580
%AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9
%AI9_Flatten: 1
%AI12_CMSettings: 00.MS
%%EndComments

endstream
endobj
107 0 obj
<</Length 12784>>stream
+%%BoundingBox: 460 -682 933 14
%%HiResBoundingBox: 460.8408 -682 932.8486 13.4839
%AI7_Thumbnail: 88 128 8
%%BeginData: 12638 Hex Bytes
%0000330000660000990000CC0033000033330033660033990033CC0033FF
%0066000066330066660066990066CC0066FF009900009933009966009999
%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66
%00FF9900FFCC3300003300333300663300993300CC3300FF333300333333
%3333663333993333CC3333FF3366003366333366663366993366CC3366FF
%3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99
%33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033
%6600666600996600CC6600FF6633006633336633666633996633CC6633FF
%6666006666336666666666996666CC6666FF669900669933669966669999
%6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33
%66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF
%9933009933339933669933999933CC9933FF996600996633996666996699
%9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33
%99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF
%CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399
%CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933
%CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF
%CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC
%FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699
%FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33
%FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100
%000011111111220000002200000022222222440000004400000044444444
%550000005500000055555555770000007700000077777777880000008800
%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB
%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF
%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF
%524C45FDFCFFFD65FFFD08A87DA8FFFFA8A8A8FD08FFA87DA8FFFFA8FFA8
%A8A8FFA8FD05FFFD08A87DA8FFFFA8FFA8A8A8FFA8FD08FFA8A8FD07FFA8
%FFA8A8A8FFA8FD05FF7DFD08F827FFFFF8F8F87DFD06FF7DF8F8F8FFFF7D
%FD07F852FFFFFF7DFD08F827FF52FD07F852FD05FF5227F8F8F87DFD04FF
%52FD07F852FD04FF52FD08F852FFFFF8F8F852FD06FF27F8F8F8A8FF52FD
%08F87DFFFF52FD08F852FF7DFD08F87DFFFFFF52FD06F87DFFFFFF7DFD08
%F87DFFFFFF7DF8F8F8272727F82752FFA8FD04F8FD05FFA8FD04F8FFFF7D
%F8F8F827FD04F827FFFF7DF8F8F8272727F82752FF52F8F8F82727F8F8F8
%52FFFF7DFD04F827F8F8F8A8FFFF52F8F8F82727F8F8F852FFFFFF52F8F8
%52FD08FFFD04F87DFD04FF7DF8F8F827FFFF52F8F87DFFFF52F8F852FFFF
%52F8F852FD07FF7DF8F852FFFF7DF8F827FFFF52F8F827FFFF27F8F852FF
%FF7DF8F852FFFF7DF8F827FFFFFF7DF8F852FD07FFA8FD04F852FD04FF27
%FD04F8FFFF7DF8F852FFFFA8F8F827FFFF7DF8F852FD07FF52F8F87DFFFF
%A8F8F852FFFF27F8F87DFFFF52F8F852FFFF52F8F87DFFFFA8F8F852FFFF
%FF52F8F852FD08FFFD05F8FFFFFFA827F8F8F827FFFF52F8F87DFFFFA8F8
%F852FFFF52F8F852FD07FF7DF8F852FFFFA8F8F827FFFF52F8F87DFFFF7D
%F8F852FFFF7DF8F852FFFFA8F8F827FFFFFF7DF8F852FD07FFA8FD05F87D
%FFFF7DFD05F8FFFF7DF8F852FFFFA8F8F827FFFF7DF8F852FD07FF52F8F8
%7DFFFF7DF8F827FFFF27F8F87DFFFF52F8F852FFFF52F8F87DFFFF7DF8F8
%27FFFFFF52F8F852FD08FFFD05F852FFFF27FD04F827FFFF52F8F87DFFFF
%A8F8F852FFFF52F8F852FD07FF7DF8F87DFFFFA8F8F827FFFF52F8F87DFF
%FF7DF8F852FFFF7DF8F87DFFFFA8F8F827FFFFFF7DF8F852FD07FFA8FD06
%F8FFA8FD06F8FFFF7DF8F852FFFF7DF8F827FFFF7DF8F852FD07FF52F8F8
%7DFFFF52F8F827FFFF27F8F87DFFFF52F8F852FFFF52F8F87DFFFF52F8F8
%27FFFFFF52F8F8FD0527A8FFFFFFFD06F8A8A8FD05F827FFFF52F8F82727
%27F8F8F852FFFF52F8F8FD0527A8FFFF7DF8F8F82727F8F8F827FFFF52F8
%F87DFFFF7DF8F852FFFF7DF8F8F82727F8F8F827FFFFFF7DFD07F8A8FFFF
%A8FD06F85227F8F827F8F8F8FFFF7DFD08F852FFFF7DFD07F8A8FFFF52FD
%08F87DFFFF27F8F87DFFFF52F8F852FFFF52FD08F87DFFFFFF52FD07F8A8
%FFFFFFF8F8F87DF8F8F827F8F85227F827FFFF52FD07F852FFFFFF52FD07
%F8A8FFFF7DFD07F852FFFFFF52F8F87DFFFF7DF8F852FFFF7DFD07F852FD
%04FF7DF8F827A87DA87DFFFFFFA8F8F8F87D27FD05F8A8F8F8F8FFFF7DF8
%F827A87DA8A8FD04FF7DF8F827A87DA87DFFFFFF52F8F852A827F8F87DFF
%FFFF27F8F87DFFFF52F8F852FFFF52F8F852A827F8F87DFD04FF52F8F852
%FD08FFF8F8F8FF52FD04F852A8F8F827FFFF52F8F87DFD08FF52F8F852FD
%07FF7DF8F87DFF52F8F87DFFFFFF52F8F87DFFFF7DF8F852FFFF7DF8F87D
%FF52F8F87DFD04FF7DF8F852FD07FFA8F8F8F8A87DFD04F87DFFF8F8F8FF
%FF7DF8F852FD08FF7DF8F852FD07FF52F8F87DFF7DF8F827FFFFFF27F8F8
%7DFFFF52F8F852FFFF52F8F87DFF7DF8F827FD04FF52F8F852FD08FFF8F8
%F8FFFF27F8F8F8FFA8F8F827FFFF52F8F87DFD08FF52F8F852FD07FF7DF8
%F852FF7DF8F827FFFFFF52F8F87DFFFF7DF8F852FFFF7DF8F852FF7DF8F8
%27FD04FF7DF8F852FD07FFA8F8F8F8A8FF5227F852FFA8F8F8F8FFFF7DF8
%F852FD08FF7DF8F852FD07FF52F8F87DFFA8F8F8F8FFFFFF27F8F87DFFFF
%52F8F852FFFF52F8F87DFFA8F8F8F8FD04FF52F8F852FD08FFF8F8F8FD07
%FFA8F8F827FFFF52F8F87DFD08FF52F8F852FD07FF7DF8F852FFFF27F8F8
%A8FFFF52F8F852FFFF52F8F852FFFF7DF8F852FFFF27F8F8A8FFFFFF7DF8
%F8F8525252275252FFA8F8F8F8A8FD06FFA8F8F8F8FFFF7DF8F852FD08FF
%7DF8F8F8525252275252FF52F8F87DFFFF27F8F8A8FFFF7DF8F8F82752F8
%F8F8A8FFFF52F8F87DFFFF27F8F8A8FFFFFF52FD08F827FFFFF8F8F8FD07
%FFA827F827FFFF52F8F87DFD08FF52FD08F827FF7DF8F852FFFF52F8F87D
%FFFFFF27FD06F852FFFFFF7DF8F852FFFF52F8F87DFFFFFF52FD08F827FF
%A8F8F8F8A8FD06FFA8F8F8F8FFFF52F8F852FD08FF52FD08F827FF52F8F8
%52FFFF52F8F852FD04FF27FD04F852A8FFFFFF52F8F852FFFF52F8F852FF
%FFFF7D7D527D527D527D52A8FFFF527D52FD07FFA87D527DFFFF7D7D52A8
%FD08FF7D7D527D527D527D52A8FFA8527DA8FFFFA8527D7DFD05FF7D7D52
%A8FD05FFA8527DA8FFFFA8527D7DFDFCFFFDFCFFFD32FFA8FD40FF52FD07
%27F852FFA8F82727A8FD06FF5227F852FFFF7DFD072752FFFFFF52FD0727
%F87DFFA8F8FD06277DFD05FFA82727F852A8FD04FF7DF8FD0627A8FD04FF
%52FD09F8FF7DFD04F87D7D7D52A827FD04F8FFFF7DFD08F852FFFF27FD08
%F827FF7DFD08F87DFFFFFF7DFD06F87DFFFFFF27FD08F8A8FFFFFF52FD08
%F827FF7DFD05F8525252FD05F827FFFF7DFD08F827FFFF27FD08F852FF7D
%FD09F8FFFFFFFD08F8A8FFFF52FD08F827FFFFFF52F8F827A87DA87DA87D
%FF7DFD06F85227FD06F8FFFF7DF8F8F8A87D27F8F8F8FFFF27F8F852A87D
%A87DA8A8FF7DF8F8277DA827F8F8F8A8FF52F8F8F8527DF8F8F852FFFF52
%F8F852A87DF8F8F827FFFFFF52F8F852FD07FF7DFD04F84BF8F8F84A26F8
%F8F827FFFF7DF8F827FFFFA8F8F827FFFF27F8F87DFD07FF7DF8F827FFFF
%A8F8F8F8FFFF52F8F827FFFF52F8F827FFFF52F8F852FFFF7DF8F827FFFF
%FF52F8F827FD07FF7DFD04F89F26F8F8754BFD04F8FFFF7DF8F8F8FFFFA8
%F8F8F8FFFF52F8F852FD07FF7DF8F827FFFF7DF8F8F8A8FF27F8F87DFFFF
%52F8F827FFFF27F8F852FFFF7DF8F852FFFFFF52F8F852FD07FFA8F8F8F8
%FF7B26F8F850CF52F8F827FFFF7DF8F827FFFFA827F827FFFF27F8F87DFD
%07FFA8F8F827FFFFA8F8F8F8FFFF52F8F852FFFF7DF8F827FFFF52F8F852
%FFFF7DF8F827FFFFFF52F8F852FD07FF7DF8F827FF52F8F8F826FF7DF8F8
%27FFFF7DF8F8F8FFFFA8F8F8F8FFFF52F8F852FD07FF7DF8F827FFFF7DF8
%F8F8FFFF27F8F87DFFFF52F8F852FFFF52F8F852FFFF7DF8F852FFFFFF52
%F8F852FD07FF7DF8F827FFA8F8F8F852FF7DF8F827FFFF7DF8F827FFFF7D
%F8F827FFFF27F8F87DFD07FF7DF8F827FFFFA8F8F8F8FFFF52F8F852FFFF
%7DF8F827FFFF52F8F852FFFF7DF8F827FFFFFF52F8F8F852275227A8FFFF
%7DF8F827FFFF7DF827FFFF7DF8F8F8FFFF7DF8F8F8525227F8F8F8FFFF27
%F8F82752275252FFFFFF7DF8F8275252FD04F8A8FF27F8F87DFFFF52F8F8
%27FFFF52F8F8275252F8F8F827FFFFFF52FD07F87DFFFF7DF8F852FFFFFF
%27A8FFFFA827F827FFFF7DFD08F827FFFF27FD07F8A8FFFF7DFD08F827FF
%FF52F8F852FFFF7DF8F827FFFF52F827FD06F852FFFFFF52FD07F8A8FFFF
%7DF8F87DFFFFFFA8A8FFFFA827F8F8FFFF7DFD08F87DFFFF27FD07F8A8FF
%FF7DFD08F8A8FFFF27F8F87DFFFF52F8F827FFFF52FD08F8A8FFFFFF52F8
%F827FD0452A8FFFF7DF8277DFD07FFA852F827FFFF7DF8F8275252527DA8
%FFFFFF27F8F827FD0452A8FFFF7DF8F8F852F8F8F87DFFFFFF52F8F852FF
%FF7DF8F827FFFF52F8F82752F8F8F8A8FD04FF52F8F852FD07FF7DF8277D
%A8FD06FFA852F8F8FFFF7DF8F8F8FD08FF52F8F852FD07FF7DF8F852FF52
%F8F827FFFFFF27F8F87DFFFF52F8F852FFFF27F8F852FF27F8F852FD04FF
%52F8F852FD07FFA8F8527DFD07FF7D7DF827FFFF7DF8F827FD08FF27F8F8
%7DFD07FFA8F8F827FF7DF8F827FFFFFF52F8F852FFFF7DF8F827FFFF52F8
%F852FF52F8F852FD04FF52F8F852FD07FF7DF8527DA8FD06FF7D7D27F8FF
%FF7DF8F8F8FD08FF52F8F852FD07FF7DF8F827FFA8F8F8F8FFFFFF27F8F8
%7DFFFF52F8F852FFFF52F8F852FF7DF8F827FD04FF52F8F852FD07FF7DF8
%A852FD07FF7D7D2727FFFF7DF8F827FD08FF27F8F87DFD07FF7DF8F827FF
%A8F8F8F8A8FFFF52F8F87DFFFF7DF8F827FFFF52F8F852FF7DF8F827FD04
%FF52F8F852FD07FF7DF87D27A8FD06FF7D527DF8FFFF7DF8F8F8FD08FF27
%F8F852FD07FF7DF8F852FFFFF8F8F87DFFFF27F8F852FFFF27F8F827FFFF
%52F8F852FFA8F8F8F8A8FFFFFF52F8F8527DA87D7D7DA8FF7D277DF8FD07
%FF7D277D27FFFF7DF8F827FD08FF27F8F8527DA87D7D7DA8FF7DF8F827FF
%FF52F8F87DFFFF7DF8F8F87D5227F8F852FFFF52F8F852FFFF27F8F87DFF
%FFFF52FD09F8FF7D7D2727FD07FF7DF87D27FFFF7DF8F827FD08FF27FD08
%F827FF7DF8F852FFFF52F8F852FFFFA8FD08F8A8FFFF52F8F852FFFF27F8
%F87DFFFFFF52FD08F827FFA852F8F8FD07FF7DF8277DA8FF7DF8F827FD08
%FF27FD08F852FF7DF8F827FFFF7DF8F827FFFFFF7DFD06F87DFFFFFF52F8
%F852FFFF52F8F852FFFFFF7DF827F827F827F82727FF7D27F852A8FD06FF
%7DF82752FFFFA8F82727FD08FF52F827F827F827F82752FF7D27F852FFFF
%7D27F827FD04FFA852F82727A8FD04FF5227F852FFFF7DF8F852FDFCFFFD
%FCFFFD08FFFD0427F8272727F82727277DFD4BFF27FD0BF87DFD09FFA8FF
%FFFFA8FD1BFFA8FFFFFFA8FD1DFFFD0CF87DFD05FFA8FD0927A8FD15FF52
%FD0927FD1CFF27FD0BF87DFD05FF7DFD0AF8A8FD13FF52FD09F827FD1CFF
%27FD0BF87DFD05FFA8FD0BF8A8FD11FF52FD0AF827FD1CFF27FD0BF87DFD
%05FF7DFD0CF8A8FFFF7D7D527D527D527D7DA8A8FFFF52FD0BF827A8FD1B
%FFFD0CF87DFD05FFA8FD0DF87DFD0B527DA852FD0CF827FD1CFF27FD0BF8
%A8FD05FF7DFD0EF8FD0C5227FD0DF827FD1CFFFD0CF87DFD05FFA8FD0FF8
%FD0A5227FD0EF827FD1CFF27FD0BF87DFD05FF7DFD10F8FD085227FD0FF8
%27FD1CFFFD0CF87DFD05FFA8FD11F8FD065227FD10F827FD1CFF27FD0BF8
%7DFD05FF7DFD12F8FD045227FD11F827FD1CFFA8A87DA87DA87DA87DA87D
%A8A8FD05FFA8FD13F8525227FD12F827FD2EFF7DFD0CF8207420FD05F827
%FD05F87526FD0CF827A8FD1BFF7D52527D527D527D527D52527DFD05FFA8
%FD0DF8C875FD0AF84BC875FD0CF827FD1CFF7DFD0A5227A8FD05FF7DFD0D
%F89FC8FD0AF875C826FD0CF827FD1CFFFD0C527DFD05FFA8FD0DF89FC775
%FD08F820C8C126FD0CF827FD1CFF7DFD0B52A8FD05FF7DFD0BF827274AC8
%74FD08F84BC19FF827FD0BF827FD1CFFFD0C527DFD05FFA8FD0AF852FF52
%4BC87BFD08F84AC8757DFF52FD0AF827FD1CFF7DFD0B52A8FD05FF7DFD09
%F852FFFF534AC874FD08F84BC19F52FFFF52FD09F827FD1CFF7DFD0B527D
%FD05FFA8FD09F8FFFFFF5351C875FD08F820C8757DFFFFFF52FD08F827FD
%1CFF7DFD0A5227A8FD05FF7DFD08F827A8FFFFA826C826FD09F89F9F7DFF
%FFFF7DFD08F827A8FD1BFFFD0C527DFD05FFA8FD08F827FD04FF2726FD0A
%F84B27FD04FFA8FD08F827FD1CFF7DFD0B52A8FD05FF7DFD08F827FD04FF
%52FD0CF852FD04FF7DFD08F827FD1CFFFD0C527DFD05FFA8FD08F827FD04
%FFA8FD0CF87DFD04FFA8FD08F827FD1CFF7D27FD0A52A8FD05FF7DFD08F8
%27FD05FF52FD0AF827FD05FF7DFD08F827FD1CFFFD0DA8FD05FFA8FD08F8
%27FD05FFA827FD09F8FD06FFA8FD08F827FD2EFF7DFD08F827FD06FF7DFD
%08F8A8FD06FF7DFD08F827FD1CFFFD0DA8FD05FFA8FD08F827FD07FF7DFD
%06F8A8FD07FFA8FD08F827FD1DFFFD0BA8FD06FF7DFD08F827A8FD07FF52
%FD04F87DFD08FF7DFD08F827A8FD1BFFFD0DA8FD05FFA8FD07F82752FD09
%FFF8F8F827A8FD08FFA827FD07F827FD1DFFFD0BA8FD06FF7DFD07F82752
%A8FD08FF52F8F827FD09FFA827FD07F827FD1CFFFD0DA8FD05FFA8FD07F8
%5252FD09FFA8F8F8A8FD09FFA852FD07F827FD1DFFFD0BA8FD06FF7DFD07
%F85252A8FD09FF2727FD0AFF7D7DFD07F827FD1CFFFD0DA8FD05FFA8FD07
%F8A852FD0AFF7D7DFD0AFFA87D27FD06F827FD1DFFFD0BA8FD06FF7DFD07
%F87D52A8FD15FF7D7D52FD06F827FD1CFFFD0DA8FD05FFA8FD07F8FF27FD
%16FFA87D7DFD06F827FD1DFFFD0BA8FD06FF7DFD06F852A827A8FD15FF7D
%7D7DFD06F827A8FD1BFFFD04A87DA8A8A87DFD04A8FD05FFA8FD06F852FF
%27FD16FF7D52FFFD06F827FD1DFFA8FFA8FFA8FFA8FFA8FFA8FD06FF7DFD
%06F87DA827A8FD15FF7D52FF27FD05F827FD1CFFA8FFFFFFA8FFFFFFA8FD
%09FFA8FD06F8A8A827FD16FFA852FF27FD05F827FD1CFFA8A8FFA8FFA8FF
%A8FFA8FFA8FD06FF7DFD06F8FF7D27FD16FF7D27FF52FD05F827FD1CFFA8
%FD0BFFA8FD05FFA8FD05F827FF7DF8FD16FFA827FF7DFD05F827FD2EFF7D
%FD05F852FF5227FD16FF7DF8FFA8FD05F827FD1CFFA8FD0BFFA8FD05FFA8
%FD05F87DFF52F8FD16FFA8F8FFFF27FD04F827FD2EFF7DFD05F8A8FF2727
%A8FD15FF7DF87DFF27FD04F827A8FD1BFFA8FD0BFFA8FD05FFA8FD05F8FF
%FF27F8FD16FFA8F8A8FF7DFD04F827FD2EFF7DFD04F827FFFFF827FD16FF
%7DF852FF7DFD04F827FD1CFFA8FD0BFFA8FD05FFA8FD04F852FF7DF827FD
%16FFA8F852FFFFFD04F827FD2EFF7DFD04F87DFF7DF827FD16FF7DF8F8FF
%FF27F8F8F827FD1CFFA8FD0BFFA8FD05FFA8F8F8F827A8FF52F827FD16FF
%A8F8F8A8FF52F8F8F827FD2EFF7DF8F8F827FFFF27F827FD16FF7DF8F8A8
%FF7DF8F8F827FD1CFFA8FFA8FFFFFFA8FFFFFFA8FFA8FD05FFA8F8F8F852
%FFFFF8F827FD16FFA8F8F852FFA8F8F8F827FD1DFFA8FFA8FFA8FFA8FFA8
%FFA8FD06FF7DF8F8F87DFF7DF8F827A8FD15FF7DF8F827FFFFF8F8F827A8
%FD1BFFCFFFFFFFCFFFFFFFCFFD09FFA8F8F8F8FFFF52F8F827FD16FFA8F8
%F8F8FFFF52F8F827FD1CFFC9C1C8C1C8C2C8C1C8C2C8C1CFFD05FF7DF8F8
%27FFFFF8F8F827FD16FF7DF8F8F87DFF7DF8F827FD1CFFC2C7C1C19FC8C1
%C19FC8C1C1C9FD05FFA8F8F852FFA8F8F8F827FD16FFA8F8F8F852FFFFF8
%F827FD1CFFC8C1C8C1C8C1C8C1C8C1C8C1CFFD05FF7DF8F8A8FF52F8F8F8
%27FD16FF7DFD04F8FFFF27F827FD1CFFC2C19FC8C1C19FC8C1C19FC1C9FD
%05FFA8F827FFFFFD04F827FD16FFA8FD04F87DFF52F827FD1CFFC8C1C8C1
%C8C1C8C1C8C1C8C1CFFD05FF7DF852FF52FD04F827FD16FF7DFD04F827FF
%A8F827FD1CFFC2C19FC8C1C19FC8C1C19FC7C9FD05FFA8F8A8FF27FD04F8
%27FD16FFA8FD05F87DFF27F8FD1CFFC8C1C8C1C8C1C8C1C8C1C8C1CFFD05
%FF7D27FF52FD05F827A8FD15FF7DFD06F8FF52F8A8FD1BFFA0C1C1C19FC8
%C1C19FC8C1C1A7FD05FFA87DA8FD06F827FD16FFA8FD06F827A827FD1CFF
%C8C1C8C1C8C1C8C1C8C1C8C1CFFD05FF7D27FD07F827FD16FF7DFD07F827
%52A8FD1BFFC2C7C1C19FC8C1C19FC8C1C1C9FD05FFA8FD08F827FD16FFA8
%FD08F827FD1CFFC8C1C8C1C8C1C8C1C8C1C8C1CFFD05FFA8A87DA87DA87D
%A87DA8FD16FFA8A87DA87DA87DA87DA8FD1CFFC2C19FC8C1C19FC8C1C19F
%C1C9FD04FFFF
%%EndData

endstream
endobj
108 0 obj
<</Length 65536>>stream
+%AI12_CompressedDatax��{s$Ǒ'�	�;��!3qo	ddd<R�vf(��$�Dig���h$�j4z���p>����
��*��%�
�
�Wddf<<��s�_�_}��ً�?�|�O���_�?�\���Ն��/_�{���_�����'�T:�"c���Û��W��W��W������ۇ�w���l��τ��۷/o䛗��ݟ\�~Vn&W���7��|�����M��~���^���͛���A�>{�m�߽zq������j3�q�y��f�~�f��������~��<��T��S��O���dw�����ۯ
�o޼9�y���W���^m~{��|s���7/_��u�}yu�����Wo��廷�
�>��͋�ww�������>�}���nn^ܼ��
ξ�\޾��^��z�q���
7}�}w��������F�W�d�
��o�9��7��/����۷�
rC�����_Q�,�����|w�1���_�Y����
�,��sB�-�Ez{ԯ�ps���
��p
O����VW�F�y�BDk)����fI^�C�p㉟B�+ڈ����毿����Սv���ۯu>��8�����߽�y��[�oiѾ������R�^��]����Z�W�ݼ�It���[N�\� }���n09&����o^�����9o&�W2daڤ ��p�_2=��\�_�E#h��
�~�
ߗ��ݾ�U�9
�����mXӴ����$w���OU���ۛW�2���M���_�]/^�8��C׿�R�y�J��,X����o��w��
�r�o���޼��^��,9����/7������̓|����A�>=��)����������7��{+s����Rm8�c��J�\��ӫ���߾���ӫVG��b�ץ�^9���7ݥ7��[m�V��vunk�Wl~8�׺�Z���{_��룼Ӫ��Vu8}W뾸����77���ߧo��<��[���Ƽ;�ӻ�/oޞ��z@���T���z��O/��
�d�� [...]
� 
����Ȯ�6
�
��ū�{��|��v�6�=]}�������6,��4���û�?�����Oa[}������i��w�W�1��K�T����]�����շ����-ot>+w�KF�
V�p���W/_�~��<�
e����/�]
��߂�J�y9�R�����p*,+v�tz�Z.�w{�%��n^��4�ٖ�,���y�ݍl�#*������?�����7��{��A���ʯd6`�~�z�����w�կ�߽��շ��/U\��<�_��͋͗���AdO�B�q��P�uD}�ٓ�ɦ�p��/�J~,����ͷ"ĵ˕z��/7/�_w�V�,��?_=���zy���aCzm�7���~u%=��n�g4*��k�>)�w�'*t_=�FWo�YZX�ڶ~\?��~����!�<l���|����������ں�IE��{|�
^}�J;��NVa�N"����Ej?~�����+�(t�^��������Xd|�o��|�(_�1_������=����=������_��^���?=�T} [...]

|���W�o�o��dͯk�[��?��	���f������yDN��旿�1����`��ꓨEi�}!J�hư̋_|�}��0����2u�[���+%�� ���j��*g]�J9�����x9����
�����2�J�2�W�!�������ʙ��r^���O�A>\��s�}黎W~|W殄)�
�kJ]��"E�?��V����Fӯ�s�F�������
��j?�Y
+~cT�j|�n������
�����(_�-���xt��a5�^��1��Ȓ9�io�Ϭ��V��.Xv{�Xn�9�m�Pt�ʐ-6X2@�����ʎ���%JYYx�~,
Gi�����-�%�f��G���W��=a䔑Gx_�g���>��_�V��_�h����
�4�y����/�K��)�0�RX�Y؆�p.y '�t�!Ƙ���6��]�����RL9-i���.]�<qy�s9攗|��y�/�̗iX�2/�Β�e�.���r)�Ɲ���,�ų|����m�����.8�&y�y�i���|�ݞo/��2�������y8�8_��η����K�l��0��.���vy�춻�����b�q��E�H�b��{]�_솋��K̈K��p)oz)�~y&Eny)W^���X�2���EWv]9��vUκ��2��+�+qUBW����
.�Zz֘��?�E��[�s-m�r֕eUrWRWbW�pj�W�we������������+[-\��q\��z��h��h=,2C7�!Xw���������9 [...]
;�B�@%�
8
�
�a��v1Ď�1�ˎ�+KXdR�PN&ӱ�4�
��?�9�K�B����D�a+��F� ��]��̲|d��u��fw���*1x�AS�(4\Plp&8�*:��ݎ^��6^j���܎�Zt��
����oη�a8�Tr�W#K��U�G%<|T�;� ~!M;��u��lR2�
+6UjͰ��^|�'����AZ	
���|���\ؓJW� 
+��yN�*����G��;Zs��E��{��㶊��4E&�T�dj���ҌL[���JG]�`e��
+Z��Y��J�;�]��^�J�+M�Կ�P��Z���t��ϰ�t:��^H��wG��j�禌mU;;Z�GK.z��)�Z�3KS)C_���jlh�H����X)�3+>����Q?����/�ڣ*�1{gE%�b�(F�l�p1b�JLm.˻�E���_�EB-��_�[]�E�?\��� ��P
�6��:��ʔ��Y�����cY���3j�F����]���S��g^Yٚu�Xh�-��y8X����pQ>Y��᱕����[�Cg���5��:��/�O��1��~��G��"��С/(m,��E�7���\vGZI
S���D
hJ��@P�Fj@�����zP�z�7=�йhA[�A���y�*�8.��p�>�&����I�!�C;Ӈ�ԇ2�!hD�\n0��.�"U��e�FP�f*G�K��95$�HВ��UEDQ�d����
�K;Q��0Ae���)́�T'ON5R}�4�z�e�
�d�D�-��Ǐ��Ը8dZ
�&��d��@��C��RW'cK���p/"Q
:!h�T�JrY��G
V2	�
o;�֞� %�籉G�ǧ\Ǧ-j��T���,י׶����Ǵ*~U�f��W⪤ae�K+#_���.g{e�_�j�*ewP.K�cC�S_�#e:Z��b���
)��Rm��ʰ�hn~�,?RΆ�9�x�>���+��Se��2
!
�g��ѯ>��p�І�p3V�s2���ي�3�y3Y)��Ŋ�F3����ڲ)�Q8kQ��$_��j�����Z��7ײX9�e{�G��p�����f�?΍:>4<„
�>�q���li�y��<�cVL���$O9�I~��
O�g��=�0�\�YC��c
�d�+��u�t���������"�����NX�Y~���b��
+'P�h
Y�d��Y����7+��:g=EQ8h�@��xi᧽��V}�
�%�������0��X@껅��%=����6
�[�F���
E��$	��,�	9Q,_DD?��\�Ņyy�37�
?Q����x�(�/�㗮_)��

��n`8�!�'���]�V��9y��%��ce���6[�kc��Q��J
-��1��+�l��爻�����5���u=���Z�̆���Y�
�:"�
�N�9.�<�e��
Oq�e���?t
���_���Vq�I���<�̲\6��I˄�_���9U�o[<	R1���ND��6�_m�����ɗ��T�_B��,�0�ד
q٠�62��1��f��f�4D�5�"��N�kQ���2�qaL�0�9�~��-2
+[i����g��0T����ˆ�-Q��?	GE[2naY��q��Ӵ��8������rH�G�~kGf�=���?l��+I+�]�8��q����߾�l(H�S�,���d��8�)�H �,.v/��ȯ��]�)Whp�]��+l����޽�\�
��
�Ms<�
��n���}���?� }����Y;������v����`��d�˙m(���l��lX(�
(	�^�
�5SD �kK��V!	"��3��% �¾�{��}�}`���GH��j&�^< T]��I�pŅǰ��~
G����`FZ	9A�DEO�
+��(GaH
+�
+/
Lq
ԭ�4<�C�<
��f� �
:Ѓ|�\��B0W�f)0����XCBz�EH�U1��r*�SH�A�"�L��bh0��.�
�j����X7�v8V��9��jLf���c���l�̳�2
+h�ou�O�m2LӮ��
�T���`J
+O)]���
P�BE�|��p_��@�"R�H�02��g�3�4,����T8�ʡ�O���
�y4݃ڇ�A
+�H
��d*!E
٭T�=e�t
}A�R)ɰ��`N,$=�T}Q��*�5�sj$HSH|��$SF�TFvTFp���C��T-d����!��H0E��H�C��E��(2���;�"=��_Կ�T����拼h^��W����{P��
b��W�}��W�gc��
*�^���������^�_�������agPm���Gq��nHmo.�hX�h�h�t4P=� C��nK�-5O@��2�!pFV��{L�#��<�]0oe�bp�-�;(
��>չx9�
��������P"Zh��(v�qd�_���ü��
9�}�����
|=�wͥ�z��p��Y!S�G���@�
�Z��AUN�
^������n��ƌ@~�m��M�=�.�DfenсR@���p����W������l�Čr�mp�}��	���ǎ�ѣk�hE�*���tC���	
��A�>��+�Ż���f�6[�Pq҇H�T��k�t���������n��~|Vf����
:�t��
�D�4�������sVg�½�sw����I�
��\]�eB_Xw�w�~��R���fy��@EY/9�n��i���ɯ�cN�����r�8���&�O�����a4�
��xP�a`��
G�+Ҟ�6��s��ãq�غ��3
]<�Ι��O������ޝ,"d�@��;�
�(2t��	�
+����#��L.��Cj7ߧj�V�
���/"g3���j�Q
W����r{�n��O��۔O���Z���xC��Wͳ���%B�K]3T~tCjˣ����L���ٷt>	�ø��k�,'!����[z/sʻ���XT���j�I~E 륪>�2U�^��mk�E�
���ס�ЩJ�-������p
;��7tQ���4[@�":�����uR
:IXU�����]�StO��
 �v�}bC��8�RE�(�۲?o*

+�&�]�2
��j��r��Me�b�T	

+u3���"�ҡ��}-����jɵ�ղme��]i���˥�q�U�]a�
i�%���O�+ˑr6���r�H�=Q��h8r���y�ӧ(�1�-
+7�8�m[�n
O��(�-�Kq0ut��ev���5�X
̮���e}�>3ؚ8�wp�����u3�mg6�/h���4;����l9w���-<q
5��]�6~�J:j���b{�4�����v���.�Vu/��3�/�D!�Y] �-��B���#��� ^:��9��a�0
a��{��Hٸ!l�X%��i���X
{Nข�,@��C�C�TL�j#kL���.��C<��p�r��˭���V�#��Q��3 r�q$�c(�'
5u���h��[�s[lny�&��H�	���ѷ-��H�M�n��U@�❂�ց�M�=
�m��Z���u��@��M
���kep���l��T7�0T����E9�jڍ�S/;E�*5*x(7>k?K-���w�m8�2�2�^ٴ˩�1]�kvg+�&��
:
�|U��l�
����
ݹi�y�T1hh���
w�a񟢼�����
�
�b$c�ב���>rۜsC5��ƺ
���P�2ө��z늿o�����bΫ�jًh��Vgֺ���ⷜ��9����T�]s^�ݗ-�!Q�
��;�sZ�<\�_�
+%�����5b�����K��v�����C"4(Bš���V�
1�(�K�?�{*���Zxf�8чND�$LI�~��SBRݑظǢ�J��:"vL�Z:��V�c�%L���0���#@��t�!�)��(y�8��.˜E�#�F�� �׬�氇������:��W���n^l�3��}6
#6�ˑ�?��]��xܺ��_����`���u�|�>`����0��V
p���[��1(I��K�}{����f�xn6�'��C{��?��
���{{�߃u�[=n��=�<�8����[⠟$A�'JOrv9��$?iF��X��q�9�	Χ�Vg��f�5<
��(C�M	wԼ�ԦW����x��
+Y�+.:8
����O���u
�Xh鼐�f��%�&M�v6T�D��h+�u��
+�� �[�d0z��
Ze�p�5Tŀ*+�|0��8�[N.�ˡ�O�2��2�!�@�)=E
j�E�1�i0[�b�wt�K
Y���\��2T���̏��.�
!>������W����
��(�ex^�痟<R�J	E
+�����5QH�\��E���s����٠���|��Ϯ�i�����
+�k���3T�\��Y��T���A��b5�
+�l&��C�‚8F��q���\��<�ۃ��
�YJ�6;G�~4�H�i��_�>��*�W��B^8t���r���Hi6�<��U,<�2�X��-�m��?����i�1�����Zę��j��hJCI�
+�a*� ��
өfh�nvYq�a��U]=rۻy�r��C
+��H��Q.R�HBVAe&g+N��}^���d&��U��#��1����蚯��!� ��a�&���
){
gx&�1vT��ԗP��qT~n�o���D�<��׸a`I+;��p�jYeN��lh�Q3H���{;�鉀��=�\'�;]V�h���we_R:�A��4 �-�X���kE:����.R`<����`3���TY��>렼h06tn!K���Ry�¯�[��٨<
Ԡ���+B�v�VH����GĻ#�0��z��'�4�O�.�s��j��ex~��4
��� ��V�l��萍ug��"v�Lյ��*�>�,5�Vf][0J�${�Ypd�KA�;��Rhy(y�詪�s*�TC�?'��~��~�a��2S�.y����T�u@�.�Vs#х4����֮��8jN��2�
F�y���Y�r���D����=�cs�)���c�S�~ ��<��?�
ϫ��%�H�_�?M�?s��9��
�w}��7�3�����A�q��?~���}����*OӜ�B���$&T	2�@I)��ۍ��:���G�柴Us�#����NF�a�}�R_�)_Dw�����O�k������>I�a��	��/����5��)
����t�O��������k6��=h�n��h�u�;(��W��}2~��
x�4
+�MF���*N���|gS���1r���'/��.��fZ��ٯٹl.�P��g5�Z�°%�K=.�N����
-�L��͔d3[�gd>f�4���{ƙ����<�a^���DZ?��I�.
��ؚ�J�,\�Z�.Ά�4<�
�3�o��a��?ɫ|��Hx�N�
�0<��*n�`�C�]�n2
�&��=�����'Z?��A����+}��G�}�SK����I�
���T��O\�_#
�\%w0�o���'G��csݯ��̦���}�%�.j�ז�5tǣi����,�
����Z*�XS��Y���[��4�x����%��,tVm-���\�
��8a8��5&|��^X|�7<a�#���,�0�kv���.}������.�������
�E�Գ1J I.$�
+N{|�=N�7��7W�z|��܋�qD��gBG�R�U̱ϙ���}.��u��.��JY3��
h}6�5�������W��BX����z
;hH��\,?�`�G��<��
hk�_o+E���9�dz�-.h�+�����ϖs,_�;�/�r�
5��h%ı�5ꎵ��
	u,�q��4�k;n�D�X��Ū�}�=��;MO��wk�m؋��l�����В8	k��!C
O�['}�c��(O1�=qz�B���&.��1

i\dR�\n��?[�qIط��y�(�3
�ɠD
���q���XN���������ԥr�y�+.��
cgk�F���J3а�=|�����V&�W��#�����Y���������i;U�b�;3�28��{�A����!�x
v��u�!
+
��L��	��%:葝 
���\
��b}Ή���� �sA�L�@:��x�n�i ��E��?���>֣V��9k��H��}Гubb��?Q�w�B?��
��D��
��<V��<_Y$�.��'nV�y��<D瘁L�O��RcR�G������8�,�BwO�I�Us����K����'3��5�}:"�(�Y�
+_,��/
�1f}y&�?��QA�5�QB�|LƟWvs�MB��lI�/���0G�g���<`ϚY��
��)�������]տ�y9�|�ǫ����`��\��P*Y���4����ևP�8��T�QT}�����{^�
��H�/��.��x��O���Ҧ礫�W$�mMd�.1)�m���

�%����g��V
'��%.
�;�d2�tj���\�)�FK[5�`GxI��-�Y�Xj���]���l��o�:^��2y>Z⇖��/�D
��hY�J*��j_��,�
��[:zI�
�/Y�'�;�������'�
�t�=��Yx�����g\`�`���E��i�8w��[�z
Ao��
Ļ��َ<��]/���X�	vU����̲ȗ�g�q~��.��[F��������`�׌����>��Y�V����?�X~��sn�@�dzpW����f<x�P��
��F���{�%H*�$�7r�<f龴|z;�w���{[c?�F�f��.�簗ws>(��2
/�#_�-Ç_��T�C�	�^��p���
o��Dn�Vi�Z���d�[��c���k�_�ȯ�@���e^
�E�
`W�W·��_��7����R�O�������
8̞�gQ�V4
=� 
d�\g�t
�,��`kh���7��3�.���q{ܞI���=-��U�����R
{KN
�wOY����T�1

W�Gt
+�8��@Y�˨��š�S5����.{�f���E�
�5{n`�-��d� g�z- v��sK��
���&>>����Ht�h�*��UO���P�>J|&�A��T1��u
�a�o
I�s�?k��������3�)|kE���|=�g�i.C�j�=U3��T+��|d�
��M|Z5x�B+��#���t�ՙ��
�"��������
n��\M���P�إԳ
��T�x{?�S��Ä�-�!X��h��뜙v&Z������)�o��c|Dwx���1
� ���Z���c	
�� +[�*Ҏ���5��YF
����r��b
�v��d���~g�ʔ=�!;��wN����/��^�����T�S4��n1<���
+��1��iDC����^�jjV�	�t�hn�����ĵ�ڷ��
f��Olo �h�s

�wp>���
Wg�_��PgI8�a¶proō4��Փ�	�ݤ�ą�9�>����8-�Q�n�<�Gx�
��q���f����9��Ӊ���0ݒ�~�^���f�O��9>���r��cδ�B�
Ž��W�N;���}
���aE��.�m9(��@T�o
���l"���<�D��d�}!�[��ނ�|���{�P�����L�1� ���
��Ak�8,�!i�#
�z��4<z�C���:��M���:��q�)�S��(
hb\�w�fj}����-Yt;˜��dW���(��8,�����v
~Ɓ����Iq�q9�҄z]�]�xnz��>�I��4Ϝm�H�]&o;.hg笖#�ڡA�
�Ëځ�8i���7']���Z�K88Ka5�
����V:*2��T�ڣQ_O�|��
)k�K<6u�担"��
�Sk
������xx���qf��s����'V��X�������dS�v!s�:(c\���f�\�?Sto�|t[�:�퟿�}{�_7ۗW��e����8��l���
	gK��n�O5���	�sU�
��@]]Xz٩ã������o>�c���]�{��� �RS@�D��f�=��Oy�ʁe��OXTM}d��ܝ���������(�wa@~/���g�9��OlVç,�ϋ�8<l�C#.֩�a
�)$vG�<k*|��5�:�f82V�:����?��L�.Y�SS��V��U�y��?)v��tS��(�#�r��Q|:��d�]��<S��Nk�6
anG􋣙��y�-�h�Ȉ��1E-�h��"�,l��@���̓��uڑ�#�G~���>�Tw���}�u�W�|%qۮ�n�,��m5y��Y�����,i��p$��5�*㑆H"bx�hHU�Te9]IG2��<~�
+?O����ci��#��A~�����29	��ݿ�������W�}�y�k�_
�{�o�~���۷7�D	?{�Û7W�~��e9Y���9�c�i�Od�?\<����g��_�*�����s�������-Ŀ��o~����5n^�%��_�
��͝n��MO-����c��կ������������WW?l~E�����������\��}{��������[������7���~��������W�/����o�%�Ä�����1˄Ȗ��P���yɣ_6�rņV
�;}�q�'^${�y�I�MXN��TY�'�T؄��Z坳Tϓ߄p�dmm�qY'�x�&L'!�"��̲�6�r"B?Ύ�'N�f�'��&�l9�+l�|��xһM�$L0���$,�L�t�a|�ei>DjE��ݢ�e���O�L �������V/�s9md�������&��
�DE
��
	��g{�8�@)���g�l&�'kq3�/�ͦ���d.�Tc}�)�JӔiHN���y�
�
�^O��7p [...]
+�Gv�:�s@<7.�k��R<��8:W>���x/6`
3_�S^���V�,�!�{�Y�k'�6^ޝ��2�����˳��8˸��<�M��(7����d�9�M�����D��3.�
I�(}p�N�Y�R�T�J"�B��Ə�B��{�e�e�d�s˔~�#&�uG�s��p2�T���Ͳ\
dNy�m��i�Y��d�E��y��<zO�%�>?mV���
z�!�Le��\'w=Q&�r�,��i�7��c���G
K��VGV�(u]#F(�*�[�2w��Z֌�5�X�W!ԙ�j��{�v�^���w���e�˼�[g�W^f���
��
+�Vd+AMf����j�z��N!���B�NDc�A��YF�����L�Vy���޻���/�����
�7����z{���@����~{%{��������˯�߽^�$�旟m�埏�)21B
�)����9�$=��DXQ�B��$=�[w�9��%�ƈ3��mD�&��T&���2�e�d���Z	qE�c9a+�2Y�2Y��:ؤ��W�b5xY!�q��&�Ҳ3;�"�/NKGᕳ�୞L\�7f}�JY�n���p��7�`x��[�����a"y�n�r�"����
�"����Ȩ�G}�/��
+��ˮTۨ�S�h�
fƝJ3�.�z��-��8�Ry����k�&�j�y?�H�Q�:�Ɛ\�L�#szf����,;�N�I�N�h��
�*�r�
N�
+��(߆ 
S
+��({��x���
�;GY��*����olv�,[k��OV�"��f�]@$	�
;���"�A�����>�=�]�g�N��C�e��H�)d�G��l&�6�1�7E�A�g7�k�~�˄8�mdσ�7�YD�D�
�h�?�v,�r�0���VD�/vby�0{N#̚Ix�C������)O�����Q�)��^U�2¢�mj3	];�v�B���Z��ȵ��[�5�9{�Ѭ�Az�-�����H+1�(��ʓ��,sE�-c�����+Ay�H�2�ZҋQ$��N!���BL'P�7�
��
����L�Vy���޻��S݊���k��T	�w1e�+��#`Ӎ26"o:a�"���\��R���a�.X:d���
+�
��"��LXO2��(
�e���T	���cl�dՐo{_��Dc鸻Ex�QV��`In>�e"
�MA�na�zC�9��kغHQ��NP��Q&��s��RE"���΂qK�;d��+�W��d�VO�1�y�G����ư�`W*�h_�,��ǚ
:A���t�x��8�9,{d�|#c{�g�62G�'dT�>�R�i�1���G�L>�ٱ^ª
�'ƥm�
}�����x�R�8 i�u�d{F�
�p
�-�0Gy@�^=��lR�]��Q�?C��i����I&�4*�Cy����u;a����(e����]�I^?���J�;ꅕ*
�tM�e�t�4�^Y�֫O_����
��j�`F�aR)
c��H��
,�����V��Z��G�be��ڒ��0"�F�+��$���'�,�W][���g��g�m������J�7��R3�[��Xa^{yΪ�;�
<fa!f��3�V�:���J��]�tS��)1��3�
ջA�wy�Z�<uig��
��>��
qro_{��U�KP �ż�f��D�Z�� ��޷T�*
��y	"�8��ʼ��lż^ɻ��B�Do^"��+�%y����y	�iZ[��4�g�%pW��]�.���ұ�.�o��X�K�0���j]�5�u	,s�s{j7�(��X�Kn�����K �,
���$P�KXa��ee^�
^�t5/�"Z&X�̺�E%��ʸ$K/��9�-��y;Ӓ|�3��ʹ~�q@�Y��	��R
K·e��ʮ$b63���B	9U���
���M��oI�ڔ�u"�II�}�ʤ��\RŤ�d� ���%ܖ��7)9(��Y��}�9�բ�򤳸�(a��X[,Jr_ٔ呋I�`a��]��q�ʤT���鐃��f.rS�ޤ��j�pW�8-an+�Bo�iD3�f���ޫ7)�j��{�v�_���"�+�R#�)|�/�ڊD�D�Բ�(��"ܧ�5�M[3���x�,A�
���u��H�Ry���ޛ���>\��>er!3���TY�4�U7�l� �
+��t
+�,�P)扚�_Fx��ER�e��y�[�Ԑm�8���NFR���bp-
+;���\%�
zj�`YO"��l~�BXm���A�j#
R63�8
U
+#0�N�#��e�6��K��w�Lԇ��g
H��7�	��v�F�(2��V\z�ɜ�1�Fў��Ǿޤ����B)�,fk�&s�ն�;q��=
E�,�V뵷*m
��O"�T#?���y�K��B�]�i�'�|j�4Ȧ!��3w���"���O�^�az�l%`[�>�=j��<�u�V�4�ʚ1�Tܘ0��W
܍�V�jؓ����հu9L��YyA߇�Z�@&{B�Aq05皬�9�`�
�l�}]��
���*�ܪw�yu�|l�<�<U'\[�ܹz�
E�y�e�r
5�z� z�n4un�
bR@��>�oT
m���Vu���\q�
/�>��l�z�L���݆
�������͂w,�i�/��q&*��mH��6�E�]��J�ou���ZgOq�����ؼn�SRI�~7��DP3Ou�M4B�!��mgtt���F��+�LB3��
��,��9\�����=��ݚ�ec��怃!�/S�b��r�u���ֵU)vϕ#� :�-(�4��{V���ի�_���w�$]����8`8���kr��&�\d��rȵZ��t
U��p��ɭ)�%n�nh��O�ժO^����
+Q�;5W:��Mg����h��YG�~K�B�����~�ES
l�:�D��ϰ��й(�R�_����գ����F1

%,`�Ƴ\ �-W�VcWʆ�{ʕ���FR�G���y�S����m�
��f�v`��vqd��1�LY(�����c��	��P�ZS�26FӨ�
+�}׶fa���{ʵ�$�U+O_�:x��e�O����s`��
WǷxza�����^'�lk_/-ż��q�4t�U�ݜ�Թ̩��B���[)+wo�W��W��)>�Iը��;x�����h�C��|'�������0����;fq�V���۪�nk�Q�+�/7V*�-�Ǵ��[���U�O_��˟�
~%|!���J-Zb�-ӂF�^K�Q�Z"*��	-�
{-���X�qE�:�)��D�~c�J�D6�d.Jb��눅VUD��#�{U����/q�#Š�f<[�W{�與}�oG�j�X鈍Z�:�e0���?�i�#���穫���Z[���k
���V���={
�=[�Wߪ�����
�c
+���R`x���nMM���sqF� ��F�,�R��pv����5�ڪ�ga
F]̊Pے�H�X��Y(Řn��Z�>mk�=��uJ��l�)��ڰ$B�&
&OKv���#U�xJ4���\�L��`P���M9��EK�VX�I_���r��|�f���
��ra*@2�.��XmM��,�"p�e=u��^��8ӻJC/�U:��4T|���}_�nx�2)pDC�_��
!D�~hڝ�p�i8:�@;R��� \�an�抂��Va���/��'������>�6����,͚��M�'�óE
8�i"uz
�/�o���ce
�Y1
+U�ܟ{��?�+�4��Q�� ���^�`=�Su�E�W<NWO6E�C�V��=��J�mmM�Uv�����=[�W������?�8�S�+^u*ó3x\Y�&��8��*G�YaK�G�2�X���H�(mU����}@TsC�>���x*�A� �{�Dm��R���ށ�R�
K/�=*8����J�W+E�M� 5�"c�w�3r�L���C̽��
�c�>l,+�-�h�Y(�7�U��!`/t�����N{���B|��\lP ��AK
��
_���=�Y�Ar��]�'[�`�~
+4ҠY�T6�D3�m��;b(֕P���饴Y��᥉���(�:���

H
�ָ:�'���s}��`JȆ����nM��%�j�sֱ�as��]_(�
��xg��ԗڵU)�3�5j�QWnӵ�0��F�+��z��k[��Y
n2m���|�Vu��.j�X�n1�=hH�]��H�؝����)�ڪ�g�Ҩ�,\�-�Ka�l�,��<[�W�������d`��-W�?��VTl͉����K0��
3�PV�H��6#ٜ�rK��R!��gX��"�vG����<W��^��s���ao�D}�����QgH�
+�aR

��a��O���&
��
��.>Vi���=��J�L�km� �C��Q��i��j������{�
�p�N�g���g�
+��pp�3O�E5����F�6��.��
`�֦*�ܲ�F5�EkKX'Д�FP�=X��^��s�?�>_1��)��Z���UE�-�nlA1�QT`�0I�\���
A_�
+�

v���B�'’]
@� iB6*�nt�:4
�5}(>w�
2ǹ��)� vR���~كw��X��n���F����P�ZA���xU]
R͝�Ҋ��������]=�pwmJ�gQ����kSr'�@�-
�H��h�^}����[
W�?ޛ^���=UQ\�9%�gi�JQs�A��z��nMU��Q/�T��#a���dv�v�ԙ�ۣ�z��k[�oY�L�p�AmNa؋J*��"d%��9�!�c	�p���f���z~4�fm�R�

SR*Ll at y��0�楿g��X�OVk�g�-
��Ob���b�!B�{�;e1�

�

%xz�
�
��@�;=y`�9gI�����[��m��m6�BY\�����p�ShTu�P�`�U��0x8�U�(�v�>%�F�00�++U��q�O�`����M_a����[�o��!
��
��dTwBPpA�Ot� "PP�@����7���
+
+
��bh(x�M�((x��	��(x�
~�
�֫�Qq�@
��R`��Պ��F��9& 
�x�

��p��ف
J
��Lq���
+���+��tX�B(����V� �]C�b7�
+�(v�) ��
}_)zey�V�>|mk�%��_ �yͮ:�z�V�;f?�%
%t!�
+�
�-ڄ��{E�4�zW� �][���g��g�m�����.����b�nTaa���'#���^)�5����{k�Q��C���nBCJkk�7k�4����l�^}����{>.�~
+\�
+,V��+V��+V��+�X��W�X�W�X����b
_�b_�b �ŠU�|ŊU�|Ŋ��
+*VA�*Va�*V�k+�X��W�XE�W�X��CŪ{��B��W�X��wH�
+��H���/X�
+��b2_�b2_�b3�#�*h� �
+j���
+n~�+��
++���+��H���+F���+D���+�X��W�X�W|XE���������
+�on��_��
+����*����*�~�@�
+�H��;\��>P����a
�0]�^q_�‡U(|�W��F�Z
�0]������\���u���޳g��$��a
�`]�^�_����**��2�|�N�th��j��֔��
;xX}�Z�>wmg�����͓s����T*,	I��@���a֗������p)?���J�{VÁReE��wmٖ�ݳ�g+���޳���Q��R6Q��gV�ݚ�4����$k�*�r���b�1e��stiMU����
vF��ڵ�����F�+ˣ�j��kS�o�����KxǤ:�Ȍ&�6*l[L^�9vh �)���T�BQͫ$��Rb�V��=���2*�%�����{�޳<[�W�������!��$
l<X(wk*z��gK�:f�� -���<L��z���`�ڪ�g��*5GB�J[�����Y�*�U�t�c���'Y�n��>4�ࢻ5�(�۰��s:;K<�Ke�Ե���'0@��U)v�b��TM�U�����Q	e�ۃ�Z�i;���:�#�=n���0-t�nM��k|�Ƣ˰&x�S /U�F�`�j��_kS�b�,�_��^�(甦`f5��h�kSq��j������I&%,$��菬��X*�Yw)�x���JDV����>�;����~�NTNk��VE9W�,�N��L�p�v��Ej�J%��<��s�b͕:�� ���+Qzk����<Ƃ	�աJ5���.���Z�#d� ��J���e����T���Y��nF(�W}�Z�<uig��~���m"
�
z���T�w�Be&8�����J��P
+����
�~	}[�b�,|̨I!�][�Ċv��kt�V���m��'��r
+j���
��݊
+c
<�P��iT��멘W��'\h����[�5T(�za�N|�Mk�v
��;�^Y
�ի�T��ɟB�㌖0��H#Nj���h��J�*�?A�����zԦ*��X�?��5B�cm
�K]�ni��>Y�V
������~B�3Z<�,��A�T���[҆�5x
���Fѷb�ܥ�7�|�5Uv�r�X&�HhiH(�A��

��>X�W������fk��k�+��V�\�D32�gV{.��7�
+s��W뉔��B][�b�,v�J�1�Zkk5�ݳP��a��Z={����K��{���
��T>C@#�Y|+��������E��O�2���z(sF�E
ɜh��
Ȝ0~
c��/B��P�Hߘ�`( �

D5wf�mE
O�0�P:���pB��� f��ǂlb>^B}�/���e��ȑ.�e�GN�a�# �}v9�57�rZd��l�e��4v�e�p�4dž\v�
ҡjND"eⲬ��@�L�E�2d��7���h�-�Fg��
�܈f�F�E�b##��'��,'��҈I�-Ca-�4B
nD��v
���֡��3�Z�����w{ܿ�Qh�i�R� �!�Wpr+Xrb����d��ȂS1��#3���8˳G at 2D�2��F=Y���w%z�����:�� ��26��F
�
j

pƎeH�	��
�
+t�O�9��I1���3��5�O�-%�x�Fzh���9h�VH1 ��ٹ�1�R��8ۓ41����L��-l��_z$1���5�XXHp.51��16qN6~��9��uU Ĝq=��~�
�3���Ѡ��1�PA��c�17��p�εf��6�a|[;�7���퉌؞ٚ9x��Ⱐ��@�F,�^Z�;�/��i#,�frK�]4��T�
+ \��[ک�_��
+
lTh퉭���*/��ہt�zb��bm�m��G��ר`X�\�*a����4S=B�
�[�1�o�U��S�Է(m�Vy_�br㲆7��wя����
v�_n���S_,���VYک��ۈ��m���ݭC �g*��S�v�߭�4�<��>;�o%Į,��3���zazJ�\w�_af�:"ܛ<E��S=�
�[�1Do�Y���OT���(���1g���{�`���J�\B����u���;�45bFz��
VBݫĂ��`�[�lU���KK�/�AZ�O�,�'��/^�=r��W7����7����{O�蝧���:v.	�#��7��}��c��������|���us���gW��ޡKno�|�������
��_�w�Ы��;��틷���cm��՟�2�7����6����������Vo,/􌜋W/��g����_n>�����32�Jƒ5=e燣��(B���ͱ[��+Fiu;�{S���~Ӯ?F[]��85��d�F
;<�8e
+�EvӨgO�L�y�L%h�^��Kp!^"{7RY��� ����Y	�4�
ڔiI
��
Uj���V�uPi�5�OT�
 �3݋
Y��0@��m9�|Ax.�*�� �
�Ŏ@ $Kơ��
OD
+�� |D����x �7mB ��$�����y��\;�H��c����;i>�S@�T����K��TZ���aB{Z��+�ߠ|� �S���B?/J	
��,G�H�[�2d�I�tu�"��^��,�(���h#���:��OAÄi�B���9���#�I�B'�!� �S����F/�jPTu����
�?F;}���љu�)
F8�4
[�� ������ �]��#�����^ɳQf���R��i
u8Tl6�33#��=��T�
��bщ'm���T����/<�3An9����\�y�"-��h�0�
 ����9kF�<cHG��V�
���Vc"|s��O�	R�.*�$.�lK��X
P9�.��fiD�!(����P&ѫ`�		#�
��G����Φ�.��K��r�,Z��D���#�,p�+x��AD�ϱ�A�G����5���<�S��O�+4X�L�
H�mcZ�{#T<~���>`�e��
>�[�6U�j�N|ac�l
)�a�*!JX\��9,XŧL%��$�H[�Đ��b,2�#젦H��]
g"D$ٓ�	�s4C�!�׬��ShpL g��7�>�efvt	g���yB����W�ZV�l ��/P��7
i@��Q4bü� �>����Kw���oA�v��swY�

��k�|٪ivH�l
2Q`�KA
+��E�=}3�wK�X�
�	]ΊiTD���
p���g��
 �p�h{��g�ɇ��]��^R� Q
A
�-�/�-�6���e�ط
+�]<�“&f
_�
�X at H	�>�V��0��ē����!� 
��q�8�\���5�ȓ
':z2��Bάl=a-�H�j��O�%g�-.=U�� ����T�`�6K��*ꄙ,U��42�0�gl���me����b�y�tlWN6k�	���k�C��Y���8�ܥu�_`0
+:D����Dx,��c�o����#-�OAP�k!�
+$�6�\�Y��Y�3��#/���D�����Gu~0Sp�uoF�D��!@��L�
c�/�
�0
+���`���5I�J:\��( ��`�.�G��7iʝ}Z v�F����2&惞�,�!��+��I ,
I�(,���X�\B%���$��0��b��RHQ�����+�ڄy�Vk!�>�l�ܬ	at���ζ,�Dk-��	�f8,�
�'�m�bV0��2	!���
cf�S���7�y�H+)
+�(i-�M�-*�L3c[��:�� 
+A�.��DёΫ����\��*$����>�3���"�jT��s�̬���i���-Lf�B*Th������UcIM� 7�P�\7f��y�38�fS ����^Y�ʘ�4g�uyf�7�2���8�&�-�f�#��	�	��4B���,�(��R�c�g�
��rj�0V�;�B>^����1Hӟ��=*0�u��$v�>
T��:Bz�I&6d��"�]6j�
o`J,\��M�f�����z]l"�J 1���5t��i���n�9�]�l8[�f  V�xu�
qWӨ�l�YoG�!�Y-á�����.��[|�c���8�	��Eߴlu������ ��B|)�'r0�����B��#�T���� ��
l�cN���]�"����S�#�b๦���{ġ�1�˘o�mdA�����4�
�MA�%�����;9ЀJ��
�.�IO�/��^��ŀS�Fm:�=S�U��L>��+[�2��"r*`y��K&J�z��
���r$�����Ϲ�"� [...]
+�f+��g�uL oSq�*�cK���z
E%L�B��b3
��Q<�򌠉�Z�
1��3f;'��R��04��k�M`0�l�MC���
+9 ��);FC��(O�H%r���6�&��Y�OF�U��:�D��D.D�K���X^b�Dg�HN&�����-x?.��ĉ��B�ҏ/нq,0?k�Ł����b��m9���4�Ylr<����)�̩T��h�&-�f�ӆ�qaĹAc�f�,�
��MQ����e��٨1D�#g
���3zA
F�o_}�^�������#���y���'��̔�%x�y˘�
AsI�ޤ�;�X�G!"l��5a�
�1)������9��-˪�mL�)A��T�*�Q��������6\�}L� 9�Jf0U��d�<�n�"�G�!��_�:5�i��"mTׁ�pQ</��BQmh�l�5u���
[# �+���J�I4h*A�a��:��D��RF�&M�u�i����"�@'�Q���e�.��0�gVe�a�<�`r�yBF`ͧ��Cf�v”��������%0{�B
�a2� (c9�p�2��+��^���IOrBX�-ށGz�Ueg	0Z
�_�
�1ѭ`9���>[�.V.z� ����&����-fMe��%�L�ns�c8

C����
��
��L0,pRL�ʚ,9��sQ�9��{�;*�4*G5ϙ�g��So�7Ѿ�W:��T\����%;�<Q @VT��j�OofQ�LF�������ȪJc-���'���h
+������F��B��p�<3�zl!/jNu�CS*�",
+;'�j��JJ}���E�Lb��Q|���G0�d{8�h�L�S�,�
ޜcQ�i� |N��׺����Hk<���
�
+�!��~�k3����E��@3�d�3�4ژ�
+^��u�&�ً�\��4Y�G
+�AWB���2���$e�!>�;�B� �f�d��
b ����#�lţѱX�a�d�
;�AlPH�n̺#�J�v�
�o)E�z����(n�)�SR�Rv/�I�����Ϥ�;��Iep#��t��=��!��^���G�p`P��Uz�V�je�8ws��vA��Z����3l|)ky��3�!SF+� tf�&g
6�u��)���JHŕ���2��ɱ���
)���e
�<����ƅ�
j )�=�d����(�	��F���QԾH at FD�'�<C"q�՘�	K���㬩\�1%%���r�C��<���Y��s��
X3
�f`�R�10�G��Ө+�i@�G0
�W9D�|P%
�@��BDj��p�1�
��
����u��G7D&<#���0k(>M
H�P���=$4uLx/H���L%
�����;
2ˡ�e�S������mKX��WqZ@��ȓq���n��O��|��F�)����B��w`����ԃUO+2&߬�t���{>��[N��z̡�I��U�Oy 
DU��s�A�d�#1��f�C���Pr"q+Dz�C��
�N\-Sb��EMm4�{�3��x��bOV�7�����<q_�IA�/t:�b&1#13�Α`��E�E����>K���r�@�LW=2Nm!%F;�Way3g�eH�0��W��;0����8-�2D��aň��&����m�o	*�����%ףt&暀WRxcf5��{����8B	�z���9���-1y&x�wdt4(���^h6�gX�s�/�4�6Se-Y��*���xIj0z��4���5��
 Cja��h�Q������wz
+
͚AR�
+o��=��f=;K�~��Q�����Q/�sP
�`z���+�@�C��
+���sQx-�T�[ $"� ��p�1���g;�`xS��w�sv�#|��*B{At���0�����3�T��*L= :�4��OP�
f��耓ڞ��B�+M?�"p�{���2��?1���B��E����G
�G���/AD��Q��pa*�Q�p�[9�>M�5"ԟ�4���*ѭW�0db��`&-��>�6j�����>��c'ޕ:�s�����B�ZWz��0�-� �`�!(��YO
��=�ح�
y
p=LL�����R{]i�ѓNH
+�Ϥ�z��z8A��-���	M�6c�
�b��3v�E���|�ⲱ���
�^�
�yV/<�t�!W����)B6L�g��a�
+ J��<Sya�N��)Q ��=�r�J����#2#+�f
&
';�T�-�v���u�qCr��āಱt~Rab��`�`U�Û���E�/�6gL�@�F
�"�HX�`=�L�+�)�%���
H��ƫuzZ�E��������$^�Q5m>���D�7�|�D����$�S�2���}t��
���
���Y�&O���{�#R��g�Gu.��rK+\Ȃ$1��Y� �M }"�@�(��0s�N�r�w37&\|aѬ]���m�����Nt��p�aZ�	�M����'���j-Iu0�
+YhRe�]&��H��S���3��_-�.�c�[�Ћ׬ �N;��f~d2��j���=����`n�[����x 7
ɕ gKjGv(m1� u��9���
!達
+ �}�Y�
-�
}*�
��]4
�־��P�0>�I$$h���G��ƚ�9pm�
3��*�{~�L'�7�Ԧ�`DQ���
c^��
Tp���B}������S�:
4��3^4c-��	fRAm� �Zj2}�P$��<B�H��=�	��5�EN�K��
�
}����3Ȝ's:��F��Ds><�Z[��eM����G�'�q�q���gJ!g����f�Α�4$.�ю'�^�h%��
kEp^��r�X�<"t ���e>fӞ�C�5a��
��W�j?��3sV���h,J�ގܚ{C)��I� j0�Ҩ3 Z�����j�AJ`�7�4|� ���r�
��	�r����Y���2���t}/�9!'�ɦ�b#2�L _��[XaO�~��0����dyxd�
�\���*31��t]Ax��c,b�b��ˁ�7��"i�-�P��\��
F� ���8k-d3_�e�`��� 
+�]/ބr�[Q
6�F��D����=�N�59kv��'v��g
�=���
�̨��g��0�h\���5�1�2��A�wL3�S��$
Z	6���&UQ�Mu#zQg&�2D�j�!��
+Pf
� ��|�t��fl�L�Hd���3Lv�!�ސͷ����0�e�a�<q�+"�ϴ����*��1�X�L�ɸTg�
;	<a��R�̑Y+@i>aXD��;�,{X�ch��f*.ҨYh5���vƬ�qf�e�� �UU!(���^H�g
+��C&�ǖ��
;�
��5�2,2P��3�ϑ��2�~��o7���P��+G��Ѧ
=x�`�",r��h�@
��2"@̖���\���>��E&
��u�#t
�"�f꿹"���ݲ:e��PPt�̈{� ��x�Ӛ�5Dg�d|^�YQ/��p�S�U/��2Ar7+����2�S��[!�����*<v��d(��t�
�ZEcqz�0[@/P�0�+Ƞ��XǬ���F��
�`�Gە &�
yT�Ts8�	��"�֡W��$AH
+Wc=�i4}�r_��PB���
+K�4aQۄ
+��SE�.�q�aFW�
�
�W�IP�*�=h8E	w��t�	��w�R�4�F����l�1�r�X�[�C�����;Sу6
;�]9#楣��ԭ�
�4mU�L0��ҜԐ�t������*Bx�x
-�"�L�z��0�n?�s����B,8>�"����T R¬
Dk
+/P�aa�<E�§`4����)<���0
��b���#�#8�29
+mpC��ƽm_�����Վ�]��sf̓Q�ls��ۇ�W�m~�ݞ]_������+�]�[�I$�z�UV�;�f�a���Pɲ�QQ}�.��ޡ��x	P�1��b�|J��nq5�!*�Vę�vޓQ0P	3���(*N��ĭޢӝ�?Ti*�(
g���x�3�l�j]�8Y�^
����F?u��d恱��bLeB�3
+q�{��k�J<
)!��؂$�n�BD��4�)+xOO��H̺��r0�"��p�g�4�/!5���x�tRU^=:�H
l|v6��P�U�
�U
8G�ޛ�*"���N��aC��A�s<i0�y5z�bs���vUa
�\�\���9̤f=�-}I�"�� ���Ʊ�Hi
��0l�F�ݨ�roʾ ���-)�
��,:cϋ�-�
���l%�[���y�<���*&�a��^�8
�L� �Q�0q���P�q4��F��(���9�%-1h�Ta��N�
��aIc+����ӫF��`��d��	�@鐦���l
� |r
^�穩zQ0�D4'l�KC���
ć�J�U�c:
��B ��
 �9f�
Á�=�莜����9��e�� ��:��j���e����h��O��*d��*���{ �ऄ0j;�F(�"P<�L���O4S %��4�d�`B;H�>��N7���>���K6g�F@�x�
"�h�l�*CR��j.��4+�Xd &T��LW-�
+b���
+h�fó�I�iA/
�ט��
,������)
i���
+ћ�?[hN� �q"[q�5�0)
+e�V9>\�;�=�JY�9��~��`L���/�


 <�ia�#+��
� #��;��Mjv���
�u t�	U,�y���8n5���
糣-V�E�����b1f�[a�/��eU�
w,�<D,��V�sh��
+��0�&�HW
S���j4 �=���H�
Q��.�&��k\�D�(�)�I@��ɽa�
+s]/��G�c��CG��
+��gM�Q�
�G�#��	��'f�AO��
#5$eW�bn�l��.uG#�=�o��IT)V%����Q݃�OC�!��<8�����r�z����j����E��� t4r0�٢�
{gr
�=�.D��
����A4�;�����V�av�"N'����n.��@7��sR9<�Wϣf�_x�(ڣ�r<R�-��QZX4�)9�*0�G�b B�M5�
V6�0m��
hI���I���Rk4Ȃ# o�)�
����-^����%�l���?�lO�=��:�5���P/ȸ�cʘ o�0��B�ܬ����i,֘Ƌ�xj7�-���έ_}9�"�,K�i
���n�����?���%��S.J���4���.�*	�^t~L%ӢS�`��1p��F�g|3��3�Q�9��dƄ'�� _��<���>b͘��#=�dy%�&һ^sG��1lL
GWԤ�n
�÷�I�����i*D
���fK]6��Wa
��Ч5E�F���9�����E�x��
'�,S tN:�v*�C�1���Rq��?j��
����s�a�f:��6�X~���.
�5^��
�烊��
�v/
\�yA"7;P���s�!p��A�|<�s
hq/� [...]
+C�
SVEߐ0�a5���hzʼnLK��%�C���R��8��䧘�c�<�ۣ�
jO�9ק�rZBՂ-
X at 5w�v
����2 �<4�|�@%�Q�
+}&���G
��y<b���/��Nv���Ĵ��
����ZJOn�v���~|nc��p9��$�ěa�^n|��4^����AF�{:W�VT`@��&�j
������5���l��8Ҡ���Z-�W����-i�$�0�����E�v<f������x��%��a��ز�0�˗�s���}{S�X�M &S=��2V�E��l�)��߁�]Yp~�
վ
	�c
���(
ys�1<�
�h*
�I=�\����J
� !�
�d

3���!o,V樰��
�ټ6��T/#XD�α���.2~�"$5@�U0bc
+�k�,J
���2 ���V���J؛hd���g]�!u6x�Y�v3��

 0�	�[@(�0)h�-F#vO��;�Bxm;!���3�&)�y��[��C�+o9x�c>r%zC��:�lCK�ώ�s�z�@D�B	�#���T�A��C*�
+�˒&��v�ro�0Ǭ�
�z�
+ÄG�sq5�-0��4���K\kT�5�DŒB�
m8V��~V�>sR;u���x���9��L���F����S4��1<�@�e�,ܪ�ŠPƴ)
��'�O�V�'ѱ#���,	ΰ��?h���7Y�yt�؅;�*�@���`N��-d�H�����(?��@�XFG�l^�f{�  �XubO%�1:
<�cI1/hX
�zX�GmC���'6�D��
+b6�NX��3����nK��wu�Ē����}�!,c��dY�����FM���߻��
�\���8�T�9���632�'�ҡ��3f�%�
�&�h껋�*S�R��ov�
��W��z���c�\rt���]x���+��9v� Y=�6Q
+4�?���c�2€�{U94Q����Y�2.�{`��na`n���
��4?2�����O�`�	�Р0�؅E���� FL�Sr3KW�6��ܳs��h
����
~<)Bi;���3
+��K�
�ȣ�
��� �	���#�ǀ���#
+�dҸ ^'�����r{1Ō��2�
���G���<��a����Frӱ
����� �|�?P�ME��V�QxE7��ކ�ȶ�q��Q~f�5MҘ���΢bGa,�6���Q˵��--������Ӓ H9��džv�1l��0B��/T��r�>"��4_G@�:Ct�ɬ"$k���v,Q�
4n�
�����(À6Z�r�R�!r���\�Y��`U���o2
W�v��X%�!���
+�Xa��y�[�^;	�
���D�J1V
��
F%���\�������� �@
�V�NQ�n�-È
,
�
��ƺĬ���IfĹ@�R}a��+�
+��l������4#�`�g��
+U
B!��i�� �T��
Z&�"K˨��鰬99eq�
W�*H��a���z�@���t�8
Z��T�
��!���|��!��
���hP3��B�����AS�0��.��ػ��X+h&z���I��QI|���k���T,����"�</�
�����Y/-��Z�p�},�-8�,F��	V^��-*p��ME.�0l�
+֒<(��~�q�1�����Մ��p�g�DZ��Xh�vnCE�)��
�H��m�_���הT �+\V_�rF�'geʨ�xnq}���?�q<��̈́�O���cI2)�fѹ����"�d��pړT�~���
e��7
)�O�=x^NE_��nuW�|��~�����������è��v��qv��s���}Sw�17t7�%U��]|���F�b w�t�w;������4����bE��ݍ��j����fִ�
���8�`���*B�F�r��6i�
cm�nf��
�
���~L("Ί� ��ʭ��
�4]`#vQ(��Jv�Q/��m$_��[�[�·/�n-R�o�.H�\D[�:��B�2ɖG�u�>�6O�7W�J�_��
�٦�"b����"j��8L]��� u��/�M�m.�@�V���<]z{�t[1g!h�8N����,"{�t�G��t�,L�����P
+G����F�C�@t�
	��Ѕ�_,X��9�4�A��
,����T����r+P��Ģ��Eύ�%�sQ$6;��0wG���*�ir.:�h58wT��nl�p�"���u��2��H�� �f�.7�d�J̔0r��h�eՠ室�Ű\�OX�ܬ�ٕ�
T.�`�:�����w�r!�B<%8�,�ŧ�\�p����aN{ulN.g=o�ɵ���8�Ԑ�#7'�
+�$)�֓0P�8oxU��48����2��/_���.;A��g�6T�@ጌ�֍ɕq�M���j��Z����w�rkcr�P�u�����]�;%솤O�"T���\����~)�^  wM�.>.l�����j�����h�f�"��X�иx�
��W��(h=`܅���6�/O
*.�J��.&.��n���l[r�����m�@ͺ���Y�

n����6�����^�J�p�7�"�!A�ppp{49r.�B7��QO0p����E���M�e-m��
ɯ�f��4ލ��.�	�-8g��~+�]y�o��"����R	�-���&���|�d��֪b��{[<8�Q�ZzPo�C�����\�����܃y��5�p�D���gy#oQ�Д���E4�m	�m�.x�n	�x���6�+��d�lO�,Vj)�-~���� �-k]p�n�F�h��t���"O���E�E�yuCnQ������Y�F��W�D��f��O
+�n�-���m[v��p[���m[�_�-�Ou�l�����u*	`��BI����4W�X[�A�
�ms
APm���P[�1�n�-F&/���?7qp7��B����H�� 8x��2�w�la�e�-��]�-��=n�-
.�AͲ�a�`���Uߓr�
dE��6�� ْ~Y�cK%��&ƶ:[�g18�[ /�muN9� Q�C� ���D���<�y=_���*/zm^��6+���Lh�
\[�:�����U���Beh~Qk�2�*zbE1(�:/�~:`�녬E
+"�-\���"=_�����Gm`-�8=!O��ƯoZ-J
�8�Z���8��G�����1�7���>
�nJ-&&�b��Ǣv��Ic;���Y�
Q���(�"�.a�^�ZdΞ��(�9t�l�=�"��D?���۔|дK
P_hZd�Q�dZT��u����ͥ
[`i���&΂��5��EE%)�t��F��/"m�H�z
u�h���8��r�i�(���0ڇK`�2�fq늂F
yaA�}�+o-�N�D�F�GL+a-8ڇ�b�埽\4ZxU�;Pm]��͢� !�h?/
�����ʱ�I�����A���L�/D��!�D���s
��3"�A�Ū�
�E+
�c~\`hd)�E�E�Q���B&�
Z�;��/-f���Rh`�P� TTʋ?���~�gY٬�ef���w��p��Ɔ�"�,��5�M��
=
?����&�]�u�g!2D��Ά-����̙6u�7�荣���J0g!��Y�;�C\�Y�&{�H�R\�
�Yd�s{�f�g'Sְٲ
75k
[W.�*�j]H�f1�qa�l+J�ߜY8�ׅ
�;2�L���o�춙1����z������" l*ׅ�UC��7b6USm�,��a
�l*��n�,�n�b
|/
x܀YZ�E��A����&V����B��R�k�.�����2sy�eq<�7_V��U_VZ��0K�xq���,ѩ#�7`�V!G
��0������n¬��e
�,�љM��?%#y7�*OW�a��'�A��!$�&�"A�T�	��OO��f�����̋1R�Y�MQU
�Y��������l�,�G�� ��
�ބY
+�e
�,q��}�%��&~���D�����C0
��D7a�pV�/�0�X��A�e�OƋ0�̘>$f�Y�O�^[M��`�i�ŗ����ˢQ�Z�]x�բ��|Yn5]�YN{n�,B6|���e`JEf��:�Dz�e��hٕ���dY$R}se'T�0cegnlSea�z���9���)
���F�*{Q�DY�h�|�@Y�ϐ��@Y���o�,/9?� e���P
L�%o��r-�q��U1�o�,���f��BY�Q�8���I��m~��$��uOi�d'w�����Q�H�
�c�,ozի"NnF�%ˀ�PcC�di@�-P�4�@�Q�����Q�J�̱Q�ʿ��B��)
�,�mX_�
Qn���fɲ�4�����o�,Y���],Y?7K��qD��%
�7K� В%K�*����m�.�,%���&ˎ ��&�჊��&+d��&˭0{M6e�,n�,�L/Mv�%E�M�������H_"h�4 �4Y�
��b�&%�0Y�W
+�,[{c��y�0�L6�����27P�Vq_ at Y��0P��a����0��~e�	�� ʪX"� ʲ(��
(���Y
�E�N�A����֛(K9�1�$ʦj�������􅔥�@�JU��!"���Fmҍ���Z�5l��T
H��VH�DY�u-k���$~:3A����I_��0&CQOQO�'ϸ���/0R��bψ@�BO��
)�k��HY��J��f|!�R��� �b?�Q7Bd��(
	���&����G@���1��e�
!�e.�f
��/@�
�M���@YYN�{~;�K)�7NV���i�[)�6MV~�N�F�b��š)`�,
��n�,��sl�,��,<Y�z�d�cH#N�߃ ��03E��jȯ�D%p��K��9��f�/�,�3gi��ZS(َ�/�,l���S�S"�,Թ�E�e'��m
�lwm�7��n�lwK� ��((�Z�bz������&�zm}�c��

�Mf�NϹA�+�E:9v4E��6E�� ���7v�����;u����GЪ
��`��GC[ c��Q1�����̴�$x��ѫ{�b�[�-��1��H�M�ܼ
K�h��P�@�" �nP,L�����

N�����K�;ܽ$�
R��B�v{����6Y".nD���ب Ģ�
�����Y��
KSɛ�]�$4
�f@
D�26�L��E�
����+��b���"1K�����k›
+�v��0�k�H�n��M���̓e��C��
{�
��]x͂�&v
+v�`��{��
N�<�#T
+�" ��.��6:.�Nb���R `I�"���_I�@
�RI���
��:�W��Ӡـ�B���%�_��E��E~]
�
~�j�

\
_��m2�\�.�߰r�댛��
�	�+�a�\�J�AS���|�Q�bE���`�hD�J�T~_	D�7ʟ�܊ZR#_���SG�@�¡,��:����{�2	��A{��<�H�����n ��4p�r�U���N�7�ծBٸW8���/_�W�Y<q�^�O�����\��~�����>7�FD����Z���h���AO��J�.��zn�+.��� ��` _�b���ևrXH|��\w@���w<O�� �*���������
�����d�dE]� �2~�l��|ݎ���|�,P]<]Yd�/��md�=9
�W"
��4�J�9D,�|M��漑��V��
+A��|���|�#�"�F��<K� �����dU�.7ոy��$*M�>)F���2vT��o�^��jh,���>��{���2�Ծ�X�ƽ�8�p����
{#����`e���a"m��^�q�fV�ل5b�^Y:���!Bc
z
Sܶ�iݘW؊*�j�
����pC^�K[f�B)Zf^GW$����j{�]�P����U~�]ѯcj�]�rwK�]�cm7��!�����
�+E����l
�̔��8ް����/�kw�e����EV�:k�p�Xo�DW��e]Yh��湂'�ƹ���:'&��%��2s���Y�X;�y=
�:7 �!�
�����ț��^9�l�+ �P��W�?��6ĵ5m
W([�6������+1M�U�&��1�mW����D \[�
�ڇD7���,y7�"��������Moh���6wOZ����!Y�b�†O'Э��K�[q
ń���B�P���H(����H<�1-���Z�K��a9���Ӎ�od���&���}z��
+M/o��Q1�����6��d�.Z�d̛պ
�T+��_	R+�p���!� ���
��ӊ=�=�i]���u9�{CZ�ЛeWl�XU4�<^�V��|*��놴2�
_�����5Bi��n8 �)�\��s$3Za �&�T�#(q3Z���
�4
p��6�թ��b���b�3Z��Ĭ���
�V�8F�r˖�QW
+��Њ�k/p=�)=�غ�,��2C[1\��BP�Y�(7��E��f]�W
��e�U�-|?�
�f��4�6aa���\`V�������
�����k�6^�a<�F����B�����%y�뎽Jg�6Ǭ����Ʋ��N�7��;��XV^4&cY��7���\V�˪��Y7���+V\XV�� n�l푲3���|� ����SNˊ�;��Le�!`7��g�;T�}��J�>ћ�J#?jSYi�b*+3��TVz{8���ؠ��@��Me墒c���T6,����
�B���E&�Z`Y�^C�TV�K}QYɓ � ��Jp������sL���z4AW5��7�U}�W��JEAe���Š ��
+ܜ�vh�x�Me%�.)�7�xK�A7������\�<LV�`�����i1=����@���H{�����0�9�����
(+�3��PV�ǰ�
(+
���J��7���
���*b��n�����傲�f7-���k�"��j�
��2m�zCYy�U� ����p1Ya�o�ɚH��u3Yi@��f��H�X0Y���LV
8�������Sa&+�,�U�d݆��z�f���
Hm5Q_��Ɋ�)���U�&������ �&B� E0�u��7���O�e����HN��i,�Oww��3}�HbaU@V&����s�^u�J�� �h��b�x�X��j�!�=.�z
 +C8��	�dIj�>
U������W��*��<V�ݑ�

+
�tn
+�
���7#%���Gǩ�[�J�����*ENAd�^�%q/"+
s7g�@LV��%���-C. +uYt}�X?+�k +�:v~zYa%xYi�#�HVZ(h���<
[����9V��ƭ.$+
Lf?�d%�jP}�dR��dݶ�d�dD]f0Yy�f�e�U����0p����܄���J+�P at Y��ƴ
+י5⽠��=f-u͜]Cņ��Q�t�ee����ee
$�jƭ��u_PV.Zұ���m����h�ZX�b
�w>�u�?w�
+3qp��*O��
+o�+���"���Ŋ �k�b�RWo�T	�
���
S�T�l�� �bՌ=7��~�2�N�V�=_
V��H�V���DV�+[�/ +$ר�7�/���XY�?n�*l�HV�W�{�f�BU�b��
+u6
+,���B�&��o����U�k?B�W�=��a�D�\�
��!p��]����kwi�t
7肮ByC��Բ4�r�t�ľx�� 2�
ƭ���S6mT���7l��6kZ�F$�K"`@ы�
+#$�Z-$���Y-����Ƭ�%%))�Ȅ����f�H���l�@�2��&�b��u�XEt�<��U=���-��骰Qgd�*�Y�L*R��?p�U�%NAVmn?`�S��Bu�@����D�=5V��H/�*�Lp��:��	��`��
��x-�<�a�O�T�
7MJ�
S�l��Y��a�N�B���0II��
�Ó"�zsTx�n10��V���l]W���P���B5J����]�T���O�E�Ƨ�&�=��y�T@>�
3;������T�$r����BNmV)��T؊ �TՒ*�󦦶"��MM��4^��9y��'���
���� u��10w Jʛ�
+۳�ƥ�8%�!-�0��^��@�+���:*f\�EJ��x$
"(���Ʉ	�����¤��u_*�(Y�G at RQSD���He
@n�*��ڄT�)�x�QsW�e�Q�7�!AG��O]/8jF
�i��
+��:L6����o2*��
+r|F�˚8�\���s�����G,�
E]�O��ZYqy����
"j�fQ��LQ�*��¡�
��4T�&��*fE^�C%��`�"��%&d���BB�z�
*����_�z��7��b6�)��n*t����?�z��:��"������W�mL?�L�U�~Z��4{f�)�z�]�S�+���S, H�
�)J���z
+ |��S$�
�T:�t�Z�b�J�����"��V�R�<,u�C������m�)
B����#�h
���Ӏ�B �U0O��S䮦(��x��8d�<E�3�"�;%q����|�N�I�;��c�P��� �";��{�NI���✶&�H��H�� S ?��ڔәv�6,�yT�4
V�7�6�� �.c�
8R���dc�
�)�`��6H�!�t�M�T[�@��3��c*]q�M^��>%
�i�}SM�P���5p��ky3M��,�o�6�B�A4�_@Ӱϔ
�U7�t�p�L!1%��0S ��
�)gy�X�ýhe:�u	p)^��
d
+��zMӼc
+�[P;�b��ZQ����]Ŕq��b�NCfa:�j'�nl{�K�l5��q��ĹN9�z�K����9�
&Х�ߋ\:���~�EI,q	p)��
+on)minl)X�-��)��nh�:
�X�E�H�,e�G~K�B(��x礛7�4Z��p�3 �A+]�Z	X)ˮ�|�JM�ݤR���S��V�ݘR���μO*KH�>
(��C4�t:�<���o>)d���
+O:�n�Ǝ�j�M'�
+
#2-�o�MJ��o4�pS�M&%��&
ϩ?
i�xN��j�;{��cP]�
%��n&i�p�e��pI�M@�n@�b��������m�HQV7:��O/i]Ql)��=:.IY
-�v�ٝTԱg��LըN|� ��z�� ER��M 
A����BF
�Q���jCI at 4-T(*ݱ��f��6{�f��x�G+�2ō�A5�&�V��n�(�鬝l�\O`G�:'?�@��RG�F�(�/Qd���3�J2[��Ӓjד�2-m�W+	��iI0�Ys��;!�O
ju����kI��Hry܆��#Һs���^�?���9� ��A��rj�IJ~�
P��E_��=$l�	�ʄ+J��Ә��
䋫li�L&X:�
c���NcT���.���'���m2
[q
j���͖�蠓�ir�Y���\B	
�ߙi%4
��Ξ���ںH��[�5).�8���o�)�&�X��m���b=�@��Q�
e#�ޫ�l
��y2%u���ō���dD?�(^b���$��5T�ѕ��Q�ߡ�
(�X(�MO�u���.� tɿ���Y�q��vc����Z��
Y����
^�6Hh���#W�G
�ԝWמ���"i���0���쐍���FՕk�KP��R�9@*B��: ���
�K�p
���`��7V-��#����[���hIq����2��E)<>��a� K
+l �[VS���]�ex+
+� ��SR�qIE���z�
��y�N7�R;*�/>�1=Y�J�� ��2�
��*�TgVT즃�J�a%�)��_�b�/?�q)KTz/x�*m
����D
�V�,��'d�8��G"V–���R�9:�����
+�(�|]��0G�%�b�n�/r��Q��f7ǡ0������5IӘgag?����
��ө
���*�`t�ub��}��Q�MC��X�
,��,i�<�
�ɋ��AK膿Sܝ�Z�DȮ��$�sU�>��~
=���ݭ p��{���i.a�t��?��-N�C,�Y��,�����O�D������{�^p
k`�qU,�k%+�~�M��
�dZ�cRs�b�l|�X�1��A�`ֲ7�,�LJVj�r�dJֱ�nh�+�a�Ws�֯V*
���[���|�n�/-%�h�.r]C+��ݍ�x$�9��/��6�dY���/í�ؓ+D����JD1�ѥVg��
 H������z�3��Ds��[E�:;D���P
��g���l/+T

�qa���:}�S��mu=L$�Y3���?`x�|nh�%�e/
o�������&� \�|�,K�m�"!��ۓ
ߨ��f B_�=j��?�K�e	��c�%e9�����@_�h�@%�[��Ҋ
���(��/��YK�D
kw�ľ����>��R 
kc���3
��"���l��k�ԋI����z�"?Ol�K����v���-�t��fH�@�:67�E����dr���nR�e�GyT�|1�3�ꁪ�-D�iϘ�Ik�R�}6]UQ���f1V��g��g;���'���0�SO3��L���E&���-:6c�\�J������*��$hĽ�E
���

�^�]��c|C��a�âd-E�����O
[�GV
��hP�\~
+��Z�.���9�=:iR���ք��%�uf��I^u��'9����\��2կ�UZ�^
�v,�z$P� 
kj���S�;��W��ݪj.�bq6R�x�n�KRϧ;�H`>n��Hp�)�K�B����Z�d�[H�}ŗ����P ��n?GjT&Ȑ��MK�&��IM~���2��=Yd�H��Hj�Gн�!ݔ��X���{�x
+ Lqh�3q��!k�d
+�*���E٧�����*΂���G,�?�rW-����O	Q$:TW�Dݧ�,�9#��@ *#xn�0&�H
J�v��=��#���i�4^�J<
�pK:�����(�q	(k/�;Ȭ
q
$^
�y_)�*j�S��OB�[�(��2e�{�EX2|�f�
��3�܂�׾�
TjTy)����H#���m��4뢴c��H)���V#x��yۂ�1�
��QW�b��sچ^R
�-z��g;�yuE�	y��BY�w
ck�������~]A*��'�`<N�}t�8;���l��cR>�W���,l�=�x���u=�HNY����U=$�^��iz�T�R���=ځ�Eg���*�[��"~U
LJ5z�!N��d�����.
X"�!
D�
b��H��MV!�������FcV,M8\WJ2�Q���8���Y#���B�$�4��v;V�q\���%
��v��T�Ǿ�I
���mSR��7���}�s�/P����-fG�G
YKvE���� [...]
�Q']Q
a
�I��B扽ػ`9b���opqN�\�W��r�
$���^4���o@>@.sG7a�c`<Bg(�
+9%����"DC���J-�kG2�x���3U+p[BO��C��v/���T��8��r&�%�5�<Z^�	B��%�dq�q�B�FV!x�i���6��`;X�J����
�)�c<
���4d�߅0*,�	�E6�D��
���i۱;���_�9��2p�
]چ�����v��
P���@
,��'QԼ���1�*����
����E��/�,��R|2�O�;�Q��;���>��%Ê 7,�#��
+J00#��c��#��\�����
�'Ӳ��LM*N�&��Ig��/C�CҸ�l�!��!
�/�8��pF��˰����05��
�
�Hy܌��Q�sz�{?�ٛ�y܆aq(X+��I��g$��(N�4V,����{�G<,�CܹGv��Q�F
!Ֆ'Bw+�*���w ��H#D�lK�,��!"��r	�2�@�dd��lwl
w:H~����PD���x�2+�m;�**l<ޥ�1�2q�� �S���b��CĆ�OVj���
-�C1SF���)�\�h�j³��3��1��BY
Ko
Ѥvh�
/�>,F/�c���9a��
B^��c�G��1鄐��+�a|X��|@UV�u�b���cmNnUľ�E;�Dƻ�̐��
u\�b��1l�1�-�����i�/6�RN����T֪q�@$��Nx�đF�?	d�����c<n���
�x�%ws�-�B%����Tt�
 d-�S]Đ��-ڎ�G�
#�޸C���-^�[J��c9z���	�(!�g��&pT&�{8zˉ�8�_܉�b��B�
K����AXT�5˩�U�Ȕ:&S��.��'Ia��
�3�\�R������#�""#R�y���j7t�a���	�G�
��RXڣmz(�e��e�t�C�.
�8�^+��u~�J���Jxo���BB�]�q��F��T�)�3�B'��;�
�4�����:K�xÂ)X�3��c^
۬��%�
a�`�ř�
���X��һ��T*kO�+ae�\I^
�*)fY��J�9��P�'��9I!NB�#	���64���A�h��2����}1`h�Ӯ[���dC)iF�y��i��"����N�_l? g��D��J�YJfC����T���`R�ˆ.
Gj����L�~q��s�x10�B��R.�wgf�.���5-0���_U��Y��p3[���a�����2��,��~�*��
�u/�?�2�
�r�n)͞q�7����>�ܙ��Ԑ���st�	��H����0p:�c���'���Yh9��@ 
J������"0ܽ��JX���Տ�յ)rj�d�
�'%�Yߍ�
u�p0v*���!�U�9�5�
�� 
2L1��F�V�k�eo'1A2�,-B��퇕S�p�I�k;沘�돞��l&�@|�v cNSP
XJ�7a�#U����V3��>�m�&��Xw�Y��Y_��/�������f�}
j�6n
#�8���ȉ�_��`��9
@m󤈕��ȭ`a��)��3���!��=�����w6�a�~�D}Vj�R�
f���͊�� <Ah�i�@*�a��X�O��X��3Q�6�~
��U<8t�c���p�v0�ݟ�p�%��u�s%]A�`��d��sUW	
�Di�ea��,�Pb*]�&���8����&�4RY�������F���D
{���0��Q��	J�b�[a(׎�pÊ�)�M�I5,l��C'�������`99�A��np�� 9��ÜRX��q�̤@v�|Ї �A�Vf���
���2�(�Ұ�JK��*�-%*���އJ��Fn;�|ӈ
��t� a�e2��6-I��!�'‘�&�P� ���p],)[8
(e�&F�=h�_�I�E���#͂��S{�H��\^��)����\�s
��1
-
�
�#٫v���\�Ȱ�A��.�>�xd�y����ۧ�&
�H���*Nj����]��XR�Î�|�����(Ջm��D�����q���qb��#C�̻��C#�#E,5Fϡ>�
7�%���kT�'��q��
�f
nMNꪀ�Uܶ�F^8+ի�?
+�h�R�2�a/{O�Z�Q4ptE<�R�l�(5��O>�GT����
B���|o7�_�g��2I�2ۑA���ݢ2�'S�\�89�߱D��
+�ӧ+���C-����Kv�d:veJ�q���B�g-�3]�؉��0]��n�Ts�1�j禽b�U�(j��?@q�p�q�%��a��	uJ����$��5(eo\��?5ek_d8��˨A��|
+�͠
���o��SS-�8@�����XOGMK{O�AG�(�e�
҇�ĹaX\>�e^�:I�l(f$�y�m����Pdd�+�Ef�+� �Q'����\/�ǰc�P�)!������;��O�QѠdE�D�8z����=Ξ�܆#��D��o�69��d�l�I谌X52,:t"
�2��H#-���a<��
���Sc!1<a.�q3$Z-ڥ��
gJ��}X�N3h%��⦸���+��c"ë��Z���TϢ���#���h�Y}ׄ�_(0�n�BYp�J�7?�T��4�z�&U����Y�4��&+ơ�%L
�Y�ʇC��j������H
�$p��X*,�'G�x�E
VΓl���T���3I\Xv�p
;��
f�T�
y�Jj�%AO<I�ޘ��OrU�E?�����<٬Q�?	k�LQYP���
�)}����D�0h88dP������IɬEZ�P2�l�7�1�Ꙓ?� �c� �F
a��0���f�X_q
������&
B,b�F�V?�O ۨ[�a� 5N��'j r�w��B
kk��=۽'{a��[�*vb	
W�^�0sK��𴮞��J�E��pw
�ᘂ�@l���/�>J��q>=&=�ő���[�v2�����p��5�
[

˰�=�
�傢ӊ~[N"dǍ�	�^�����p8�!]b�٤��P�+]h�Ó|�
F�̙��)���H��܎,���Lh����T�c�������o��i�Ȗ$��*��l���Fh��. �
�� O4��I�;�Q|��� 8�z��N
 3BN�p�T����ggZTY:��{��|��CA�nzX94���jlL �ȺP�[
+�����u����
>M� yR���"GK.%��V:�̕O��'Crl(�,��5� )��Ɯ�Pbr�S|�F���!������6��u��g�7�<��W���?ž��b
�����3{\.����;�=�z��:�.��
83iÅJ���a��d-rZPޖ���1�, ��ng�� l�

+jY�c^��^q%��B�Y����~�l���65�<�A�wD06�a�{��)�d]-lT�\�jn����ŤK���WuZxB��Z-�
JDf��d]Ӥ�Os��h(�O�KkQլ<:
͏W�p�q��wd�9Z�$�`<�x��p�~IY��˧�,�̈�c@㈌�$�3T�=2v�\WLkw��� r?;
+��@����F��pI� M�߳gH.�5�T�ri��bU:	�$��M�
+�)V�]��i��NC��LԔ(m��B	C�yt at b�*��k"
��*�� �v����
V��3�3
?\fu
�5�02<�9
�A4Fy$:�(��/zy��W��r��B���"53�a(�}
�(D�X�I���K��$)�e�(��6CV�'�
�G���]]���L�5ft�/�ġ�z5U7C&��e�����_/h�����C-��
+�ƞ�h����),�j����:��YX��	?��UX����\p��;!�*>��J=�β�`J�q���T+u�+ 7�X|��~ň`
+�P�)b��ufU	�0�D�#f�ZD����QnƛQ��5 at UJ�cse�e��A~���ȓm�

+5i�m�\�(�o��\����%������J��&9���*9��ۺ��
��	)4X��ޯ,Sk�E:���,���u��w�ת2��NUݠЄ2�Y�H��%�
�G����(wq��9�Da�e�&�3S�
���^���B
��=�Canf,�����v
�'�l"��4IJ���Ì(?/|�!I؋-Xg��CՈ��%Y�jc�3rH�6�n���U�e-� 8����*#
)�вR�
<�f"��E�70T҃A&�Z1�?����n�9�㭠g�oEw�ᑠ$'�A��2	�.!@��Vq2X�^Xeh�(R2� �@
{G����I�9�T�?ǫ,���6�4�
.�5���v�Y.��v?R)F�!ƞmMV�P7T�O�O�zY��[�)ם�RSN8N�3��1��w��CS���	�<�p�"D�H�Es��)�Γ����&�3G��S됬�'��X�-v�^���S�E�ē���x�a��g]V*e�t-"
K�
+�j�m���&
i�.~�����+/+�z�Y@�.5Mh���kT�g��צBۤXzf7�e
��Pg�ڂԢԦ۝H� �4'"��U�JQ���A��d
W��} #C���o��>�,�z���0Q顤6³��w�r!�RIc��F�G�m���Q
�z<E���O������ ��UqBP���b�
+R���Y{6'�ХEoM�6�
+GK����p���yN+Q�%?��ػ��&M�
ɱ@��j7k� �]/UIf�<�
+N�
+����ğdF%�j�fA���L@eIw&�>9c�LBD{�&�:aUH;=�r����S�'��DFZ����B|JM��a%��@�����E�S��_ݫ۔�q�1�n�i�!yW�%���$NV;�{���W���ӔyX(怅g��"ű�0�X�D�^��܂g2E^a��D��GZ�3�(J# �iz�NJd��U�����
�!9�����
B�
KDu���I�hEH�a>�GXY�*�+(w!R5��S}���=�F�"j�\EW
����:
'bwU%��L���'?K�>�#� z#U��)2`���<W<��Ѭ����I1E+�FW��Vv ���T&T#=�A��?��i �I?85�Yk8��1�x����m���ll��)J��k�5죞����GEU�+ at 2Ào����P�K9i��a\zH�Fm:�Ϛ/@
+�!A�y+6� .\�5������ov�����T�*��0���x,�����n�h�/gG�
F�W��[J|��"
V2U�'巰�
+
+��Y��
+��Ht'ia�EQ�'M1~��+ at uB�����EMX	�aF
��z&n�9Ae�*����G ��u���Kf���j:~�fy����J��V����.��
����YV�YB4
�Fz�U=��q��ƏpOĢT
9,/J�,�}SW�P�T��bG�+�JN	��,�UF�@��z�9
ƀX/,d�
��z<�z�x�)ڕ�T~
e�i�-�c��(����#;
q!v�`DV"3�Aɾ���מ}�]x|N��6
j����
�9R�%JMr�,
EI#�'�E�$s�(�Db�����k�n��=b�&\	�~�3�a�\Uچ'�g�"�֘�M�hW��� pa25U���J��'$aZ[쫔ㆎ)�aK0%��(`�����$�ܧ88VC�$�hS�3�{�e�Q9DITȹ��;1�zi��1�~¼ӳ1��t�����@�
,[����=��V:<d��]vH��5�P�&��|� O�I�9C�P)W�94a<��|,
x����*�5-²�@���5�' [...]
4��t�����ގC8z�Jx�B�^��Z�G�F%v�3t�@���.�ڎ��Y�,E�D�Ξ=��
��\%f�r�Z��E��T��l��~S������HﶕWˉ
2o*� ��_7�.���v7��eGݛ%�zh u`���b0����Iĵ�)���=wP����
+D��Qj�e	(K��F|[IdV$RD�`"�,(_c�Vd���
6�X�o;��Kc�
+��\����9!����d:c��*Žb�Κ" �:��cܬ��d��Va���خgd
K�Ym�z��$�~2Y<��/pO�k�4��\���
��G�a,�
R��f�.�b����I��?
+�B.���� �D���sX�h$0�Y����+��U ��X��:N�	���י!�϶.K2r2�Yy4�Q��IS��Y
V�i!��v�i
kƍM+���1yg9�Bf�~1~l�.3)��rmn���Q�P~���}l��1
S��a�a��(�qj��2��ҋ5
�B��N�(�6Y�~*���*x�i0�19Mf���
�6��
+����"��Y����c�w�ʱ&	Y��w>��D
{ۥ_mnu�2LJ�����/j.�d�6���2����U���U=<��Y�����$T����_�ID�F����ܬ�}*F���|xR��)qk����HT�81�4��3��O�"E`'d>\6��q��|�rp���(T
���>��	��܆�/�YK�B
x\��h�g�p�&�� _���~��_9�#G;*
^mD��P�FĴ�1D�lF<n�vYμCT��U��b
+�����m�g��� ��<���X���΍��K	�M�&3�3ԡ/�17;��%U�0.���ď�b���]�@k����A�n�Ͻ��їf3�I�w���j=ڋ�}�fW7�ܼl�		��q�@�"66��Z�b۰�j�����dcVu2��� e��o/P6d��9;�
 ���f�@�7
+��p5v@�Q,�5ٔ2��Fd7mB6z�e
�����n��c����2� y
8v�|���& ����F�I-�L���o�7&�����Eg��1��g(�.(6����8��N�
U�0��)��훈͉�
�M 6��
'�.�_8�z�<lB��m6�\z6،�2%���YYt��G
��`�I6���a�kS��{�� �X-�#b0���{%-_ l
+j��+
��]Я��	��׳;+�k ":��F_�%�֋|��zo�5��JV
�l(!��g��*�8�H�
+_m�5,�n���D:����.�KL��i��t�l��ep���~~B3��=LA���U�V
�5�qh��^�!co7�Z֩zW%l�d��0��q��ͽ�uHAo�5ۄt�5z?�Jo��2�}c���PP�Q�Ѽ���k��+{������5C�v�ad
+4x׈k���m�5ΦI��+���Ya���0Y4t�k8�p����ej'@׳�F60�(�5�sC���4m�u[��6�]��=t5+3
���?
|�&E�n�
�
�u������K�uE:�r� [G���ZS�OiS����N�P�
Y�`Z��W�FZ#���"Ѐ�/��cr���Yר�
+�u}B��=�!��86̺��a�5j��e
�h��
e}�e�5�C_U�,�a��YcQ7-6��D
c
g�
�c��) "�:s"��']�WO0B��rc�g�bX�>�k�@X��̫&�3<#
7��D�Gqt�CZ���5�oz5V9D����|j0���/��Մ�܀����VG��)�tM��+��V#�X�ͬ�o^ �qJ�8�0H�x��,/:��wԴ2�
���/Xu�<}��q��,U}
���5
Ru�>��nQHrs�[t�
+Nu�R��T7wû)�,����p����1�(�z!�qF�kA�Y�U��nU��O
�>D�aмa<5!Jln:5�$��N
�$C���HUM�)Z��FS�fk���A3Sw���y�e�Cu�bc�KL�7�I��I]#;H�t�H]B[@j4 U�y��Hj���Qg��$�&��Z�mu^��$�„����&
+3{��a��{/
5��A7�:��ʀKf�ַ��;���+�,l����h���!Psy���G��ӏv/��kS}�qBjç1����=���w��ϯ��i�U鍝�
���;]ٔU:zU=V�no��d�};��GX���^N�����I� Ns�rp��s��dz5�6ͭJ�X��
i
�aM;N�Q�]%�4�
7hz�3�
�|4C�i
ϱ9����c���p���鼫>0
��͗F1@#�Xxi,��^(��c�����8Bnc^Z���^4�(���!��
��1�V{:|i�
+�4�{�Xp>:����68bӥs�o�4V�LU[z�Wm߂-��h�oo�4<V��,��'��
pHQ>�9?�dixEu��2|&C
]�Ź��r
+��diK�x�ҸU�r�G��/�t6�`S�ѐ�iE�`�aK���)
�Hw������ʫf��
�&�[�)��*�6O�An�4�U��(瑣�N�K��
ǺY�p
�{c����ke ��#{���18�PD��80ҼE��)�Ĝ�q ��EI�!�-��0�AB�g����[��K
q
��
]��
Mu}�Î���	t4�&��	"4hB�C��h�1�+&��142`��pS����趬�f�wNu����ɻ����3�aFS'Dz�`FS!H����Ƈ�X�FFc3�Ca�1c=1��tq��r絁щ�m���x���v��
b`4���?�>�G��h� �K
`��H�
1���1��J�M�s�71�V1�M����
b4�+̻��h�UZ�M�
+�Q���&ɀ�Î�����@FS������P�Zx#��k
���D�/

���;)�퍌���A�@F�B�fF��dR����_i#��>R�0���	3�|#��.c�5��l������4�5��ђ��C��K�M���u�/d4.IP�@FC���w �)GR��BF��n|G��f�JA#��h�ԆZ
�3�p������6{j�xxѫE����h
\4�B��M�^3��a�
�1��h\E_/X4��"�NV4:����h�vU��hDD�ќ�Y��ۘhj_��̛�>�j�hH4
bt��ݪ1������M�fP�� D��7�7!�W�%5��z�#j�Q�����D/W�n>4[k;�A�!�~�޼�аW4�
��($,44{�?B^:Q���cP���Z3�;"n4�8p��H�팼��c	,j���

�
�͆��^�
MC@�7�V�&�?Pg���3���g��6��
*�T5���,���9�ШP�+�мA�����I���ۆC��6��fm�c:̆C��dc����q��CӢΊ
�JT��L�fO��J�2���.#�xh�SIN7
�ۑOxhLGj�}��ըա��G�xhXjP�7
V9
�C���d�iQ�
�X
�BM�Vgˀ��A����
���� D'B��'����-+?]�y,կ�
��9܄h�H�h!�Z���p���	�*�j��M�������e��#�)�Oވh*��YM�>��M+E���T>�P� 3��bDK��i5
d�DOD�V����
+ٿ9Ѹe"Zь/�hF4T[kD4���� G7�#��31#�ёyь���w���\���`D�
Tr;V��_�"����$8���Q(�$�%�Rވh~����h�fP!:�h�{�Y>E�M��P!����T߄�A����_�F�'����F�i��C'�/
����H��͇���`�q|I�
�d�7 Və͇�AiA�ea��D�ٚ*��HL2xhd�޷���C�L)�Ы[��g
q��t�Q"#kc�����wHv��a|�n���P;CCJ=�ڮ=�iRgm*t�R�تWg�n&tw�덄
�	� =��6��M������M�p⠷�A�a�����<�
+�
=���{�"��&Ao�
�>Fq�G��40РJ�vS�7; �=3%
hԼ�B at c��83x4C� ���u���#Zl��
4���)/�3ѿ�1��Y��~
IHŋ�
Ӳ� j4���2
a����t����
�6�օ���n?d3� e�F �G|����jA|��i� >��Y؁%���Ȅ<v
Դ�A�›�ܓ�f=# ˴~��u�2ܹ@Ճ��n��2�yx���<����ܵ�ڌ�
����S���
+��w�w��>��ܹR7���Q6ٹGf4��#RHAq����u0Qe��:c�K7�yF�ě�<���{�ed{�!�k&5Йx�Y���H��
	ɍs����3�
��
�3����y��#�吜�Tf�� ����q>Fc��t��¬ܚ�ľ⼂�
g�%���9Q��fv��
�0u���$͝W���p����&��
�`
A0��
?��p�
��!�h��R]�����
q�?�G���7�xw�̂���]D��
�fV�OC��Y0E����g�B	��J4�%^NV3���lW�P7<=e8����� g�I��δR� gE
K>�f��,�B83\E}P ��\�
F8����&8��U`糚
����	�f���>�2k9�/ێYv;#���e�a�j �,
ULJ����:-�x�m
+�3\;	�n���`����3�"<�I1���
��j�3��2�
gX$���=Pq
�D��+��K
�Z/7�o��f8S���W3��|U�|3�Q�F�W �Q�%\�
���� �P�_�
?�h
�o�{����of���u�����-�5��Z�V������b�t���Y�of�2�ѫ��y���5��}��(�^���9����f����/t3��mr3����p�ʿ��<����6�(^j��6ca��hfPd�l�f��8����т a`�.�6��0i�mc84���
+����
||��A�b�,Pͽ���A��z
�CoN3;
=�9�p��Aif���d�N��� ��j��
a`]$��>b$lD3��W}�
@!Q�o) ͠��������
xf@
:s����
��(�ƃ�/��@3ðB�i�0�>,�03[}�r���S]tc�Q��5��̭y��2c]�Û�
# �d�t����U3�M"%^HfL���U�^�����1s)F���-{�<f(K8K�8f\-h�m��ޜLPW%��cn�_Z��[�^�b��3w
�
+
L�Č�h\��6����0�y���f�� �0C�4�z1�W,҂��Z��3�yu��/3��*�5�yu�x�m
��:a5s2|y9���0�om�2
\z9���
��7xy��`��
+�f`�W�/�2-zo���s ��\�PS�iM�2�D�w�
�9&7u�ł��.���B����._�3t�����
�^�e�|�:�e�);
�@V�
>=�@
+;��4/紀.�AFP��A���M���k	���˨�g}L��<$#��������
�J��[�nLPݴe���Z^L���{Y�3D�,y���}$78H�W��"-si@]D��� c
+?H�
s��t��xڠe�����4��˞�P^���AE?#J<�KO.�2�9Toe�2/{��ʜ�m�9��Qd��,�$�s��UUU�\�eX��78˰PʻA�t���n�2���͘ �Aen�,s>���c$90�i�$	�2KA8�^�e��0���f+a�9���,�����(�J�7f�)ܘe.�5g��XW���I`�`�Y梘z��,c%���,+�[��̸	I
�Y�I�"�Y&�Z�y"�9�ҁY&mz3>�
��L5	�c�,�P
Uf �;
+�|a�����7frun
+�2��
�1�D+���\��`3-�`n�2iZ���۵f�}P��n��sS�_&�1(��1�he���nʲ�Fs
�2��MY��R���,%e���,�a"�,sO�Y]�e����(uO^�e�
I��/ʲ�k
�2O?�VIYF K��
�
�Q�e��tb�,�€�
Y�U�?C����!�<vr[�
Y��Fl�`\�e4�!��pC�/�!��EY��p��dKEyw{��U0���B,oc���S�ބ�����L=�0�e��S5�e&�u��KҺ�
�G���%���?�xFJu?
s
��lX�6\�ހe	�g:�e9�!,3R5� ���|r�+H�
��e~}�ѿ��T;
X���DBmU��
����X�E� �4Hhp�y����2S��b� Urވe��ܝ�T[�̺�����N��n�2u
9�'p���	ˌ���߈eX�߈eZ����L
�7c�ǣ*'ˌ���z�e����X�C��K��1sw6d���6�7C
=��e�p��Y�=co���
�^��,�"�TP��
%P7e�V>��,��7��,SF�^
W������2�;
V6e��:�7e�QkV�e��T =�[��(�\9�˸B�P�m���hz㈗c��w^�]8�v?�`԰f(�����+�	��G,�rw��\��������V�F5���ӑ�
Hv��!+�x�n��d$��KL�MU�./yA�Q4F哙�|CZ�2T#�T�D� �n�2�[
�T�]�z�a�f,��B'`��#��f)gW�l�2
l�(ej�
�04ҩ��J
1�J�	�X�^ e.

��#m�r�F�7E�)���L}p���
|�z�K
'���My�������4��S�'�"���̰^1��F'�F_��K�����)/n2Ja�o.�W��q����/hr
�c0��[� ���`��&&#�C�;�ɐ�t;�uk�^���	Ѵd���x��q`��nVr�dm��[��
Rr�,�"%��]$HJ����>s��
��=�r�����w���dtU�
+��	,�rȶ�
S�6{�ʍGF�EDӑ���
pd��T�r��q�ݵ��R��Pg�
+��o0�*���E�f�i
f>��/(r�� �D���D�b���l��c�|���C..�}�k�!��
B/S�Cn�^��L�
+W,��o��,H�-Ԫ7�B��9�d�p�b
2~�m)n
ru1Ʀ �KSgH'"��^Zzm2��
T~gA at .�v� �ݼ��cTh��i�1iffo�������������}����0�+`�Q�
������Ǩ)S���rJ��$�qFB��櫹��T�<F^�y+���bq���ܥ�߼c��8Gc̛v���Y]x`��d�`
7���ۨc�V#�B�,r
�1��l\�
7T�=��b�
���9Ft�1yS����ۆ
Ww�ތ��7��3a.
ބ8N���pl��o<���Ӗdx� 3K6	�u�.�ŀ��%E5���
��G���6��됍G�*0�8G��׸���`�-L�Tc&T�x��LzDU"���n�1ފd���$l��@�1�g/D�p�Ӛfӌ�������e�u���2F�[W�%=%,j�Jn�1
+ΌR�0ƖtŸ1H�Ę�*���c)HF9cDP?�~(Ɣs>a	����
k`���b|p���?һ��;b!m�q�n8�`�m�q�!��� S'�UwA5O �I�6�T
�
5	���!�jЋ[v&�b��]�t+��A.F�R�
\ܚ�=��C-J�Db���^`�g��lD��q��+)��b�Ȫ�@����b�\��v�W��bc)x��L�ݰ�9�9Xś�b�)�^ޤ��أ	#�����xnLq7�xx��a�x���!��*�c�L��
+Q|���a܀b•W=|�X�O
1��@���uЉa���
�4z}�x�:)@ģ9�y����
+�1^����������fg:��4�
K��uzC�g���$�nH�h
~��aTv�<bh2�h4��K�ÛF��Y#&r��0���?���JɄ�(�S� Ð
=� b1�L'*H�!^��M!^w[Dx,D=�t �,��o1�\
q��{Uj@
��_��
��//�6}�U����Y�=�Z���W�d��
���b���(�	��u��
���
��tjj�cx��v�ؼa����8£z,�q�=ؿaD֚��
Ss>�5����u��d � �e�h��(�
�Q�ac���j�U��<݁�
�QX��m���h�p7�h�m��M��n�0����zA���@/�S�.�{��A�RQ���X�Xi�0�B9���XM��Wk���+����͢�
f���
A�>Da���
�0�f.mcY3ɥe�q���R;4a�d��	s�;�%�U�2����%\��|�������y����������2�
�
�}�����u���������������/��ߛQ
5����O��v�_�������o�����?L5�M��������������G���o��_�'o�����pK������������?z�?�ş�kD��g������?���
�:6��O����|@������y���q�����o�Wg�/���������?���zg�����ӿ��?��d�y���d����7�{w�ï��ÿL��?������m
������o��[��~�|����˿����

�|��>��L��~�'������������>s�gx\	u��c"���o>��?��Cf����8����?��_~�	����������.`@�%�g����������z�ltS>�
ϒ���6�X�����b��� [...]
���`b%+�s1��/��(?Z(�����|�'�ȩ���5�
�Y�(�
#D��
U'���1)�����G���QA �+�:�0���'��+��x
cA���щc���`�݂�����h��>c������N��J�v��6���'�3����|}�ٔ��(�g�
|
+	*���@`��`����(�{����S�(��r�:��v]�m޷�>�Y�	\7�3�i���v�����:�c>'{z]�ww��8.�=f����q�Pj�~ׇ��}����|��:�S�	�w� ���-Ҙy|9�1����ɞ�^W��
���xX�پ�\�
*�^#;`��
�.�=r]�=r��^#�9���5XR4җmY�����x��m�g{�z]�/܃�5�c����˰���)�˰93>�װ~��a��ϰ��z��
��:溁 �k[r�ח�
���
��(㨯���=��3�yn�,}?�3K�ឥ/�=K_�=K_G=��u�,}�9���6��z����}���������7���\��ra�mx�0��ra���0稗
s���œ7��6ކ�Q���x��z��}�
�w��篾�R��/���/wb1���ص���z,���e��z��a��z��nw�>�1�����=G=�����'�3�^��r��_��9��x]�m�Wtߧ���{z9��ۓ��z��38��l�Q�e}�?ě;��}[��p~��׉^���.��Aϭ�~����C��ݮ�}�c|��1_g{�z.��
�;����_,M)������G�X�b/����`�P���G�� ����� ��6>��Q��u�|��9�u]�݃��x����^�p���do�ua�y߄���v�gp���ž��o�u�c|��1_g{�z]�w��.?��u�����>��x_�e>w�:�W���u��m��~
�_gp��ٞ�^���=�t��F$k�8�I��/���o��_n�1��
�r���
��O����ե�a�{���b�ZA
�k�q�ge��z�a�Wo�}:8����?�������)���w���}����*^�_6Ʈ��z�s���9hv��)q)g��
�ٓД^W�/���3�+V�~{�)���}������퐠�}�c�O�Xϩ�c^������I����
�A��O��������������ܷ�����e)�{�)-����x����S=Ǽ����!�2��S�t~���Ry�����z��:�O�����%���v*�>�1�'p��T�1�k���X�
+eO
.�:1���q�<j��e|1�c6?�����:�o�l�9@��޶H��>�1^gp��ٞ�^��
��94����at 5կ��y���͹��͹��2�Q�
>g�9�̹�l[T��>�1�
�1_�2��z��݃���/G�0αp���~�
+�������~��
[�����}�(N�ı��`��}������~
+�A�-}}[|�?�k�~���C�:��ї���3n��_�:�������^G=��
n�>�{(���
���G��9��yn�~4�͹��͹��2��~	4�
�
4��h�_n�m|=�c�
�J����=�x� ��}�n����nK��������|��9�
���
��Į
�����c���
�䯷/��1���c>�>�=��3x�ߟ؏�7,O��b�֗�O�ϱ�c���e5 _uG�}P{��߷��j��v���Q��u�|��9깬�7��D��
+_������_��9��x]�m�Wtߧ���{z����ۿ��z��38��l�Q�e}�?*�lo����q?�<��D/�}Q��܀��V]������n�>�1�����=G=�����(r�0�
������G� W�_����e `�
]��GE�����iz��v�9��w���>��<�1�+���l(�uSϷz��������ׅ��}��fݿ�o��N���{~
�_�}��y�c^W�����@���������A�gz﫺��\G�;u��������O�u�c|��1_�y�y]�wW���>��/~��?��o���x�6��M�i>�y��P�.���)������6�拹}:}�|���A~���B��٠�G�Y��A�9kn���d|����=�ɘP���П�m+���^��7�/?�0T������y�`%e�'���(���(�����Qg���������lD
���12���
VZ� �,:���E/�
ԝ`@_F3��>,�þg�x��9
�~�����?)��	:�m��)�mD=���7
��Q���ձO����H8���	��G���B
�CF���ٜp�����8�y�C�b�-�C�o�_�����[s��]�PmĪ�_�Z��dc��
0�����������/�~�����mH->1��lD� � ���i���f5q����
+s��Y�O�H�_�����iu��ؠ �O�%�O��O��mf��7>"����Z�vh_2�Q6T�ٸ�Åh��-�7��㵗E~q���\�������
C
���0�N��x�2�
N�\ue0?��9-�JⒻ��J��`D=&�l'a#:�ȘX��?�
�J�,���<��W{N�0����0~�X[<�w�����N�b���VPƬJ}��@9���iǶ�,<�_����|Nr����������Q=[,UM��W<~�������#����y�
��0�,sՀ #�!��J�Z�3+y8y�'��6�s��	��&�9w�>h������o<C1a�W{����"��8��Y� yo;���|
�>�8��s�
��O
��oU[���g��h�<?B��3ϰ��s�P�^Լw%U�4�m�f�o>�#��1�0.��(�D+�
l
�c�f~�
�^��9cL`������|�����W�. ^�j[�G�������*����@�^���'p>0v��Y1��3462y�0��1$gk�{lblO����+����
�r�����/Ə���➏��'�q�P�X}��}b������n�^4 Tbr��0���jz�U�N��{��
O��O��Ks.��_JP	Z
�����
�Ԋt1
�
���5�@�c��ɇ�PJؘ�� ����D?2��g���
>B�ƶ7�'���>r����>��m���<Ɣc
֐��x��gP�3������@�x��s�
�s&�»6���)��G܅�U�O> �36�
���r���*K��rb'�f�B�w6�'y�A���R�A��
�
<@���Is��"2VO�9q��
gp���st�c��*p{w��q{l`����"��
l������#��ݍ���A��Wp�:��ר��(0�����
�>>��b�'�<�/
�3.��-���-׋	�Y�N,
槎��>6��o�xܸBrܾ��
��W����q;֋�?\
��E&�M��� $� ���O<~�
~��;_a�~�=�|}�9���K����m_���>?���=<D��v]~g [...]
Y�=y��R�y�?��BO<26�E�o�\h��k��2�5���5�Mq �,6��
�����{��lɷ��X��� �Y��� Gŷ}l�rr{�=m���(@鵾h
�4z�|~=vN�5��A�YK���2���煟6F f�
��z�A��Ƙ�x툎{�Ђv������s����w6�������!�Z�ҋ��N���K�E�w�ܟx"1�
Q�4Dv2�z��8������}Yu-t�|�f٣�
+�gm��-���uJ
x	5�˹|���DmŔ��^>�2�,�1[�}��cd%�Uh{�G�
Z$��������s)i�|�8{jn��f at o��������}���C>Fc�ۊW���[G�mK�+�^Z>�*��A��CF��[�
g@��?�����}��t�J��V5�Џ�Ƨ?�2��g?�υ��@ì>ČK �y|yA�u	
�
7_�}Ϛ"��y�l)p^E�`�o�x��z�����!�O0�M��nmN��'68~��5l�0���oi�y[���|
��A�}���F�_G��0��<��r���J�� 'J��Lg�d /����ԗ�KH�}�+�ƀ�2�a2������E
 �`�<w?�L�����
5~��&�ԊV
�����{9|�����k��8S�u/�����޻�R׼�۬�S�Cc.i�eIρ�M#
��Ux��0� HƼkLja<�D�,|�‰dq�9Ҟ�1ۅ�m�qUQO�&O��pz��R<�����y�#����Oc?̥q�=:8'�s�3�B��4�[���ol>�������߱?�j|(k�-�_�g�ȱ���
a�v1����v�ɨ��
��X��g�"�1�?��N��Wl�<s;���
4��1m�2D@,��v!���~o�T��*�e�5��
�5�i�78� �
A[Y3����{�@_���h��=z�w������~S�"a���;Y�3�q���bR�VڞE��Wq����K�N[{p95N��i+�D��*�G#Y�!:���[
w�K@�SC��1m���EK2�w�k��S�q��xx��4?���1z��c�����5D��^ h������ֈY d�'6�pXeԿ�
�f��
���g-�
l�nu�'cy⥫1�a [...]
+�<�N_��s�fi7�� t̖�>���T���8@�ie���Zx�6�@+W
+3�@;&�Ij���HKB��>�R�VIn �d�44oa�BX�g	�dE:+����d9)Y_S�#�*
+ at bKGGH�d���'i=�{ ��@��&󭞒V�f^%�B�?��J��OQ\ �)�g�7�t���:K`�DL4�M��t��'��<�����	!ڋ�� K'dg���!`�wR�m0�x0o��!G��O�A%���xچ�L�.���&ʒ���� LDr
{��	H� 
�J�Δ� ���v#��7I`�H���\'۞7�
ɓq��bcR&�<e ��y]W�8�<��-��,����KJbϸ�A���J=�
�b6�<�Ԝ̳I>��%D`ϒ�Z.ZG��O/��ӛ�VP�浪��*	l0W5a��+�|�0��u�:
Z����¼	�*�|��^']͒����*-m��z��i�`��	X�D�*��R�
+�,ө�lK�
/�+��լX&a'
+*.� ։i���F�M�Fؗ��V��U8l�̗L� ��|8@ f�Cl��p.�����L�JR}6f���])gA�<<��h�NbU�
�,ӖX�qIv ����Q��
	�]�EM�
��5��ta��3# ��M�0Fz���ٖ��rD ��:�l*k�e�E�#��b��L3��6�ު��e�d���&�g�o\��-�u)���V�W������#r�$�E�s��3�
T�&w�s��O�m[�tF����u�ҏ%��yu��s�`�,He$2��b\z�,u��
d}��FB����֜`&�u!���\��AhV�X��� �� w/3 ZI�B��'V����
�=! �?>�!���#
<�2��
V���l�M�� �%�/�z��Fʚ���l�N�Ö�}�\� d�>�D��yM�
I��X1e4;�m
C����
�g�-M�d�d�ò��:���%py�j��eI�\��JI�I�aC�&�T۔e&�Y�����\�T������%���s�{*K����I�d�
+�YE@|T7�4�sc�6j
�3Ԙd�_��1(��A���E׎�
�Y�+�.��f��	[��K�
�؟�gy�)N���~U�M	���R�@��A����<�¶6!i$��h�%)n�ee�̓�H�����&
W֔.���k���XPC�D����E�N�90	8OtsE�I�@�[ðxcua��Ѫb��4��0����"�g%z��[?K`�B�l���`S���䦵�'@雰
+Ae1�|2��6�JAF��`# �&
GRĩaK���@�&��"Y5�/�Vт�) ŀ-�ةb�arp����D�f�)!oP���A��
+�A�������q�Ŵ�bm� �
�b�Lr�%
+ƓJ�_\��Q6��:Y������ET�5m���
��SP���
OE�Z�,�`�JȌ 
+ǛcY��$5˼�D�Y���3�M��o���
�܄�2
�MvXVQȼ�1`�V���kҒ���#i�m��v�X9�*�
�$��)P%����	o����Lr|~�X9���5�����q V�Sk
+�����1$"�쵆

1��oNA Lk��B,h
�7.(ɍՊ�N-y t���$��Ѧ��^`%y��Z28o�5C��o��&��ٙ*b
��;̖�4X�����W��<<Q(��t
 �ɨ
ە��
+g����z��i?�ؗ��
�S&=̛�Ѵ�e�{e��A�SUl!��EC�#VI����y�‡&Y'_��ץ9>i�P�IfԖ��PNgٙ�� �!86%$#����Kg�d"V��]��+������c
���XE�~bU
����8�Zմ���o*�RItz;*�7�BդV�I� �Z��c%eNe|�j����ɇ���$��-���3�6$�C�t��o1��)�Q��a���[s�ބ�1KK�Ұi^��

��v�ȑ�J'���=bR:�C��e/G��dJ�v��
ȉ���C
�����—ś���",��9�9�ٹ_�9O�,�HD4� �fI�	�_%s
x�u:,��t�_�^<�i�HZ�x&��B'��}%�U��I�n2y�$CV�T�V�'~�NZ�F�:��~������,�J�k[�l��
+<c�odHH�_pM#mZM�7y�>E%L!�S
p�L%��
����4X|()
+NKO@[�5$��[4����G�u�FwN,K�KBDܐl�6�OS}�["y�%�9��T:��t-* 1�7�&�|2�.��[F`O!6i誊�@)�LX
�[MZ
pzK��.��D at R��E�r�/�Hv����+�x���a�8"
�r��P�*��xs�
���[�$u
Q��X��V�ф@&��-t�cd�B���E�5Vܜ��ht�Ok4E���D7OW
�D���5-ɒ���(����ji������
��!m��7x㗅��I��戣Z
I�]H�`����� ���*��K.ב�h)�
u
MkF(A�U�D�~=�o�&��%����ť=thm�c�x���7A�	^�4���
M����*#|��y�lM��hGW��5_�J/� ��e?8�	�M�+ �R2֢|
/5�%���_bh-�e�Vy���9��^ź�$���=%�2 �k��%"�H�(�2z6ZZrgC���g�
��\?	dh
M.
�
n�%� ��JW���6{"�3V�yr���R����=�lh��w�R՗ڕ�4��HV+�W,������{Oh��kHu�R�|QZ�������(��EzG�TP*Zr2۰*�Q>LI�pMw��z
��
o��N����D�m�����z���y�
+sj߸��>v�C1�#A0]|G
����y���2�O�7 �]����/�[�)~���I�}H at EB�`����K
5`��
v�K at W��&h���[u�a�{
+����%阫طemf�i�(����&������%1?�Q�k]��R��|V��<��`�u|���n���H:�
�,+�U�
nu
Nɛ��zO7�l�q7QF<F�u����y����lc|Dl.Y���m��*Y������,X�
+B�נ�|�09S��`�Π����F�
��������R	�7�+���`�zP�NB�&��$�j` �
� A]2&���
Y�}Ɉ�*#�Y�q% �������!��bX���
_#��ɞb��ـ[�}]�5TZQ��`8����f!:�/��)��
+nP& �^:*!���ld΀���N,NQa5

)R���ږ��SI�1�
W�Hm�'
.NA�
+��ƒ�� �� B`l>�,�S�N�y>��?k����6�rD il&
�	����(�0��f�%��i�5
 Ek&g^��)��s�,�II��x���Rҭc��(���x
�Y�x�K���;�撾d�% ��[�م�`t5
�2OkJ�D���׼�C���-�l���I	3 gi����㲞,h�8
+���f͑J�@�Q�X5�CHf0�',t���^ �rH��'�&�J��EI"�y�d�9��	�m�������ML�ʖ�I�,��(G�䭋W�(��.k��BJ�{�Q,��hK%�b�j �A�n^-�N��&UN
�a2x No~��NJ���.�0��#Z���T]S�t��%yI�)�T�,�I��@�}���t��̸BjK:+�:�2
�Lb�X�R�h��A`Uf�U��rD+��l���#�
�bX�1qW&�b`b���T�i'��4��)��N+@�|i� 0Y�����I�����K�<\M��*� (IS!�
����H���kJ��ij
_��1�q�bs$HȊ��l!
�
ҳ6��m
+�u�͎@����	��yy8R\����RD��Y����,�T�Oek�6
`˳N؊\N[a
5��d5+��5��IR�\��
莑�5{q�����{hP9M���s
�,sbR����/t���n��&,��� �,�P�i�֮����;ڰ|j�)]���8ڸōW��D�HB@�(6�B@|X�� Xg����}��1F�V���2
͐��&EU3�CY
+�6t��ݳc@[G�
�Z��jYU
ܬ9 �J�
N�� ̶rycYN��!�H��TD��,^ƽ�f7�q���5�xsLQ S�INYX9�ڏ"������o��M4OE �9�A����)�ZE�csQn���/��{��JS�a��sh�=,5Sh��
T ��=���5A��q
�d�D�IjAj2
35���N�6j! �x��#���L4��l���vD��v]U���I�(j�J6#�x�B�ui��
��@��������Y�6Iw���j����>3{�`��,�$�{vY�����~K����
.�E��#�����u�
���$�ы��'FGi�|��9?U-I ��"�\��jЄ�Ĥ����+��=M�
;;+9B R��R"���@��SESzG�`͔Mv� �f!�
��5d&��
�.x�qф�|1�r�LʋX�۩6應��A��@p*8�c[��xr�� �#�؎X_1Z8�~�u
L��'9�ྒྷ��
݄��D.�:e� [...]
�"��&�F�
1�<��Y��T>�-֐��t�a�p}�ڸ7T��uTJ�U[�oz;)@����Bx�V��T�
/��ԇ���HC��<)�5F��	02�e:̥Kӽ9HIJ+V�m6z���N| j�����x��&�tS�D��8G3���pѐԊ:
� �J�� Tǫ����r�Yg%Ĉt�R��Ԑ�
��
K:L�+8���+�"&q��Ec3�\�Ze�--)� 
//��Q	�q0���?�c
G&��7�÷X-��)$܍���@V�{�ٞHI_*ŷ�MLe�n�JN{X�=�|i�Dnq��>��x���aU��(*��,������B�W��e�V-u��TWp�\���[D�s���He=)�ɢ�w��$��
� 2�SoSc5�Ad
��)
����|3�P^��$��%���{Am���������U�R$T�+��2A��f��%���ڂx!�	�,�I!LR����Ԓ���n1V0�KŨ�8
+�r���m�ۘ|I�Q�󢣭JRp�)ҒU1aR�]fYNןH�d�a+
�1/Jb����$��(���e���X��<'��J����T֐yJ��_�f���9	���]�)ەp�K������qJ�k��s�]SF �[M�MD�C�C�%p6��:�  룔?��
��Ŭ-���21� Y��
�]��m�V
e�\�"-�Cdũd
�F�!��yebBƜ
���ƨDV�3���+�-�
�w
����:��)5����Y~*�vbb��t�4I�(
�ٟŒ�YL�7 }vX�D
 d��)+�(��&&"�[ڔ�M�<uҌ�&ƺ����LYM��*f(������sƸ �k�eT�{��I�@���
o�bI�$�²�䣲�z[��Y&H��A�f���$_�5�:fj4��P�S��@I^:�z���L0V�\��S R���j�x��,�:*KLϤ�_���ln��ۖ�%1��A9*�_���JC{
�zH!���0� �� ��͙�lP�W� H�\�T_�(�5��qk#H���� �I�d�����ة�FB@)$����)#
ПeY/�UQ���r� V��Q.��E�\Y@��y�nRq���7���� JA;�.��%yũ��|3="�
�
�SMI��)����%�ìw|�EjAek4�(I�)c&8�7 ��G�+���8m���B����*����&�J�d�
&C�]�X�xK[�,f~��3y�)����]
+�3L�Bj��G�# i]�e�b�]�EM68#А5
�FP��
�3(|�l�JD{����(
+�R��
 gq�'��Zs�bν �9(�P' g��{��)F��3����I�݈&�*�����$+���
�jI��	�b�u���
���–�7��iʒy
��ƴ"���5��R�W8��c�v�&C�&aG���R���;쪑��W��+�h�>MjW�˱��[K��{Bf
+I�NҵtO)S�T5:�5E�G�BD�
�	��� (��T�9��xt�W�rE:/ONj����BY��#��O;N��3q)�YJarf�������)Hs��l�hJ7�
?eS$�
ctK������\N�����檘=L���RI(�yR�z\y��#E� ��3��p�{�i��9J7���O��SE'e� 9� )Ѹ�(�����9xe	��;V�,�(C�ͧ���<�����
fš�7����x�1q��kRF��E
NE����
�[�t˺H���N��S�t�
�E&̤�0�
+�1k�K�Y�Dڥ*�f��%�
�H^���%dҧY0�d�>I>��{��um��P
�b
+�VN$�5��
�Ng��.�#0��O.e8�<����Lj@���%�5�w9}Z�p�Q�'�K���dmiʼn2K��"�EN�!ȗ#�d�sθ����|b]�D0��3�{[�Ҕ�sK�8���L�����)�f��E��VN�hy�7=�������D�i_3W�pR^/ �VT�\ ��^��m����̺�`����R at _
^Q6߾-��
!��G��V
^ڻN`Ǘ�uJ"+���F�k0�̺tE�}cIJ��EJ�Xmr
+l��]����We�v�`f46
�x��歮��\!dkM���@6�af��9)�
4V� h,kY��M�:�*
+�KW��Iu&�2I�t�>!��q������ ��_�����T�ksp�O�
�Z���"wXc�/�$�56F�C5-��*�d-9���˪�c=p��*m;ɍ�ʤ�f�nzn�^�������vBP�N)����K�+r��*�R
��G��;��E����Y�e����g�q��o3I'�7U�vN�{SI'0S{r"tU䜠W��v\�3�����^Уo3	��7�b��l���wQ>+ �vE)
$b�
���o>��`N��(��b�Y���9�J��X���@΂ J��:�O!M�r����1�
�fy�C4���	����B
��r�d������)�v.�\�ssy�����B�u|��Ű����ͅ���Mq�
=Y�~�M�e>F��ל(;b�6L�M�b�ȣH��!� �E���/�,������J�PB�V��A:W�	� �k��Y[J̔�p���'e�o�^;BQ���	zV
y�z�,��oQ�����m(_�+�c��
�����~pv^T��	A��� t,&����A
�|٘�zM���f~��q�*~gnk~U��NJ���e
+Sk���rC!�$���]�y����)�`��T���5RϏ�yV:SZx��#əN�w8猢��L��>�'��
endstream
endobj
109 0 obj
<</Length 48369>>stream
+c�cw@<8+�`�S:�)@���*���-l�J	
+�f�v%���/�ѣ��MT6����X>il/��
\֯;/c�2��6cCitr�&B���q�e�LA b�ڷv�����\������' Ŝ%��!�)d˲�"ux
/5hbhi<�,?#
Y�j�}*�Z�E�+"
���E`��ñ�!���ܔ�/{�Q�?[��_��'f��7���7��	�UvW
zOT���7
���Us��d��N`e(֏��@$�Qm�;
����S��
���"-FMXgz at 2jU�a�v�u�Wo�JE��gH1��l1�3|��n=����?�>-{�� ������7��o��#_�/8��~U����x��4G��c�/G�����e>>�]Lz�Ag�q�ߵ
�~����C�F�7�����֟�
?�:3%.������ ~�������g��
�=��w�br2�
\���
�����A�"�y
�����ӿ��p$��d��ʠs�5B������������ͻιh:��e(�fLP�އR}�tO�ǭ
ǚuY���G������� [...]
+�Tll7�M
�1O<h�
�ˏ�j�	+��Ӱ7>�
p�v�A<�{��������s9�m<d�r_�MF��*��;i�}*Rc
b<�|����@�6��J�>�o��kX����QF@�ؾI?tn��^gp�vڶQ�
^]����}߽%טG\6?�/�\ބl�i�vR�K��?����g��}� ����s�}}��7�xLW��U��B'/����/��[�����MK���7݋�k�����}bu�j���d��;�������4_��G�� O���s�A抻���:�ӔH4��a ��eˍ
f��lh�x Q��7��m��[e�uڱg��=�h?�wR·�����І��c�
�)������D�\@���j<�5�z9�Ͳ�{�| j�Ҝ�f��&﮻��~�]:?7���~v�{����:]l��CDB�#tw�����2�?vGo�@���6�{<%_������͆�����p�?u�4�{lo���/�nsl<�-�����������;�]����5�|�ܔ{�\�z>!b��Uڍ�]�:����8y8�y7��6>r�Gm�_�ww��_��r�v�i��E7Jc®l [...]
n-oW�w��\x7s4�O>�����x�\@ؑ�������7?��\=dm���iM�M��6�U�@�I6��[�����
sn<�s�7dc����`��������+�� _5v��u���8z���;���`�c�f��E�k�}S�N+~j.�S��g+�
);��7˪��,����,��P5��%I�.��`x��������L)��D;���h�9[s
��m�۲��5�Aa��'��[gc���јI�q�?a��p���i��J�������1��*�o[o�*/��4�e�},�.�%����7�}�`�����
+�
���&c����L�����
������
+�Ĥ3�H���.ؠ�i|��ӿ�q���DQ�<���_���M�����t��
Μ/tGe���]�_،ߗ���s����!lf;������<��!pf�=�!g.�.p������opm���8���Sc�m7g6��[8s�w�3�Gt`·��C��ݎ��
�_K�����4�ֳ�C����
g�3�w���4��s��
�����l�(wd*w8Y��u����t�3_�ۇ�I����SLt�����T�;����K��<��z��kH�s�o���=����x�
�ہ�}?
+��[|�	�mG��Az;p�
��W-�
��.q���v�n��[�6:�,paϘ\��_�Ny	�h�M��M�x�Mt�D����e�6�a�� ���q�����&�������~���fK���[N��[f��
2!�h�&i
0#;�>�@����ڥ�'��e��7X�Zt9k7����۬�q���%��UK��ߪ��.|�
?���>ʨ{=\�(��D6�"JݯJy�����+�6Ӷ����2wʊ�-\a
��*��_�^�
�A>zq���9���pJ�{|�����T1P���U�q\���=4
Ț��r j�Ҝ�v��&﮻��G��l�t��]6ͻ�_�Q�;�;-�]���-ă���^5Э�?o���>��������W��������g��-��6�>����s�
���|�9|�w�`˯����q�t8�Π����w�k�c����t����w����$!m<��j�/P4f﷣� ���!��J�릊�z��Mչ�����o�׆�����#"���7�&��`��b;��e�
� [...]
�1S���j>�w�M���.�?����[:���1�N1y���`��>�G�I9�?��>�?�����������Ig���}�`W��W�t��g�����.��h3��_eo
�:��~q1�|��RuY��G6���&k�&����7�T�{�Ec��hA�G��i�/�\H�=��j�	{��Ӱ���L��pT���
+��C����{�tE�lEL��e+�@�
����X����=�ɰ9
>�Py��?�ښ<R�lJ_Њ���v����
45
�����
��
1��v��
{0ö�6���5W�w �y��vǡ5ǖG�U{������(��]��q����1?��uaL�ѶoN�IC-鋛�:�A�U
p�p�7��A
�u-��<w��f
-p{G�Uh������xx%�~UQS5�
���8ӏ���j�?��˷�Ns
��:��-p��l�
h�N�`D_�
�XM:��Eb���[~)f���
2ll�Fڛ��I
v�'�S��C��-�fW��%
_
���_��;�kE��sћ|������c��Y.����i뾱��+��>���{[j?����
h�m��/ҍ1=���[��vD5���
�qH/���x{s�n�޽/���mp
��
�?n�P�Q�V��A6�ߎ�:���M���J�o�܌�o6��:�>�
�ݕ�k�w������J���m�dZ�~���i��q��E5f��?z׷�����
+��/�o�D��=#�6qd}�lO#�;KG���~�h��;�M�+�>�͛'��	����ᠻ��5_q{�t��ƺ�|Ń�t�����t�����:m�ݕ���a��Q�{,&�ah����B�ה,�������YoPDo�@֤�*��z ͹���`ʝQo��
�t�nj ��3�ߛ����)k�w��u��������6�fˏ��9
�x��I�9
�s�n:����
S��Q�-���u���{�۲,�>
��)��M��4���	>wr�lw��!���ryѪ��}bH�i�O͙��%�C�:^��{�csƸ���P5�Q�K|/b��`x��8�:1�>_��6�n�y���O�k�۞S�^LY�=;�t
[�	�3���m��0`և������7֕vE\��h����]��������짻�]�R�-����z�
=���0�
\|[�Y��~&�7���7�jNչ������\��vp�j��J۵��a���r�a��80�
]m'
���\��y�����v�=�?-'w�S�
F�8�}��L슚�Ö��{����;����ᮬ�

�}��V�<y.�������
@�^���8�O�o{�򇣟n��?��x�f�?z������a8&rT������6��j�8�ڶvAMo
�QHma+�u�`]e���ҙ֯��
������?Ç�o-Q�~l��_U�Zxut\�����-)��hӺ>:�-�J��ma�n
K�6��-(-�m
KU�n[�m�
e��m�+�:;���ԡ���U�k� ���]y�
�VV*�([�����KHM�K+#$t!Bk�!P�j�^�Tݖ�VОlK�:h*�
�B+�9S�X�vhֆ��v-���Ύ|[ˆ'4TՕ�
	
�U�|q@��6��	��e
U���,�.��	�M�	�u.���(��|z� ��3�g�Xa$VS�"��a� @��ԡ3 at 4�SW
�j��X�S��Z��`���&׻��D[�:���X�@�QRJ�"�B�k�t�o$��,u���4���
E�
+��V��-��
+3��XS3A����*�A*�B8h����V��[�pa������*Ug<y��`�"��R�B��iXVUXU��`�(!M
�1��J�	t�d�c	�d;�P����l�0W@Qmx���0���1�%}�k[�m�
Hڰc�m�ژV���2T`HC����߰)T	�QԵ� �"���/mX3��q�ӣ��_��y]
�֣o[�����E�*@�ȬB��
+�wcX�'waYP�.L��H���9�g\ߜo̺�9��
+��¾�9_��"pc��f�X γ�o����B�yV��6gf�^wagߜߑ��Es'���
�Z�Qwak����;��0��2�o���[��9���]X�7� �>z2Z���㒰�׵���������b}
܏ aJk�p�>v�X�zg(N�Ţ�`���4��<i�P6H=���Z8�0=s�������$Ȱb�a��ɐ����9R���\{,?�g��;O�
+;3O�b� ]~	��4N�tcd,a��-
�
�P����=�eT"
��v�1�:T(�
�2h��Ѡ~���̫Ak�aԻ��.F~sn��U~t��d��?'�+����d8�O��~ &�n{�Q�
�GY6G��(�]�G5�����@�:��L���ᇠޕ�ԟnݞ�M�DA���Q�z{8FU	8ca�y� �

+lF�ߏ�I!+g|qQ at kE�L��G�+��_�#@ÿg�&~�������i!� Sc
gG�l
��T
�S�
��*
^�P���ea�i
��5p�WT<|�
��0�(��Ǖ.�8,
:�F*b���j�kq� �"�RLj��YEu��	41���
���=�� 4����Q��
�H}��z\�����h��=��@�	���4# 4�@Fq����*h
�>�h��ƪ�0M''G�%��p���L
2]��K�~*)?5'D��DЩ霛�
+N�r��Kk�T,����
+�RNm9�� /�M�l<ɡP��(�aJ��$�������Oa
��I^¹uˆ���
��x(&}:�^�&����r)��bf/iq��K��V�5*~2�nA �b#UK����/AVK_Qo��,} ��-	�e����~+�D4��g�S-�����1� ����ׅ��
���
+���"-� E��s
�@ɺ���7��U*;}���HjT
R���Y��4:�Δ?���ӣ����&�
��.��d�
+���,
=�L��z���8��V�&�J� _*gt�nC�*h��>�
;�ٮs(z
�xބ	�����	
ȜQ6��V�}$� �.�v�I3�߃�]
���nj�?qE��N��
l��Tm��].^��'O�N.��ǀ�����
�
+���D�
�
+q����t>[p<�<<����t
흤�]�k�Y�
�];LS
�G��1X$`� ���_�|��΃�
�aIT�|P	C�:
D������+�,�P��~�q2��}O��
���x��"��`ܪ���8;(e�҄�?+� J}ڐ�tU5
�]5$���H�M�e���ϵܪr��i����6S~v2�A�!T����g\lղ
S-�1ղ
�h�]�h%�?�mqU��K�d�T���ܐ���d\B2����|�uU�rR�
��
�ʟ,*?���G�WS������t��4|����[�{���ʑ�H���ϵ���^������0���
+S�G�s}�؏�?hq�>���K���P_/����?�
�+��Q鿟q��%+_/Y�z��_�{���2d�y��g[;_��B��wծ��]�Yt� v
�
�,v�;�E0���
O�k:W��G��&ZOn�i�b�
T���H�/�$�/��[-��pe�eM�ĬP�b邢JGѶ�Y�c�ʬ��PYT�Վ6�}UhGÛ��o
��h;�`m`�d�GS��A/��
�!Ƕ��C���h�4]�:UO%pz��[P3"��2
�B1�qg�͸���
+Ĩ��:��> �;������B�'��$��%��v8�j�m��;9yrqq{�j8ᛶi} �z�! Lejˠ@�XƵ-����#��aP��i2x�<���'DPO�)Ӊ25��~v�
��eF!�QZ�:�i�6�T{���ʋ�U8���!2H��� �v�P����q�"��`�.YRɺ0,Q��0�M[:Yߒ�b���E?ǂ��V<��z��p�{1
]�)�\s)�g����?�I��j8���X؅�Q/�o��O����'����t,��
�m�F��$�J~�Y����>��y��>��Gs{��y�-��o���>�ְ���� 1�cS��u���晻d�Q̲�&
��\�-ۛQN�qo�UUp�toB%���r�
[:Yߒ��t#����n��.ө훵�i��x�l��"k��~��λ�PI�M$�&
+
+*�d������T9[.D���Qh���흃��/C�����g������"�
+�y֧��PR��1:��Ӗ
"��,���X�q��\�
+�y
�j���
I���m�����T0�i��;D�6x�!�Q�v�c�Kh BR���4�(8d
�PC'!�U���Z.����CPOm!T���� �փ�״�tfI�UX��1ЪjWJ��ȵ�5
^F��/�jk�%�/�����k��
��~���
�}Ƒ+��,�~���'�#O����
<U:�-�z������� �~�9���6N������/��	˽����Ug��w�CO����C��~l��S ��� 
$.�y�.z��_�-/�X.���J�������3��%�-�~}���'���g�?�~_��������������6��տ��_����pvS >�7b[�L�%Q-Y��[�n���c
Xm!"|s<���_��(Ռ&Y8
b�L�ȷ$0�u��T��/���LD7��?�bǟ� /��}
�9�n!�	��#r�������/
+`'b�����aY���<�0�M� ��Y���݅
��pgq��U�h��"�7��p��>�6�e]�����'��a+��ݬ=�ig/ތ����� ٟ�B�b[�
u�����G1VʁB>HSy[| ?�� �ٿ�����}��A�::�
X�uᯯ���c�����4Jԕ�v�0)�.+�v�
_�. ؁�*�d[�ĝMÔC�6!�vW�A ��v��q8�"������ZE#9
�
D�Y��\O�"bX�”����+NE��6��O�����p�pΩy	�^g�z
+��1!��.SL=-f�����@3<G+����wm�KX
��WUB?�n���m|]���oBV� k�����
 �A�~�Ȉ�
+�|A>�fA?

��N�K �o�RH?X�p���_	KK�-<��C'���7����8E?	�c]�Tġ%+c/!D�
�$,a8LFΔ�.�\��ZrP�2<h�2���,���� N"a	�1���MA�7l�~�ń�h���RM�"S�����$�0��
���r2����|<��F���D�9*��\�iҕ 
�	LI)��v���p�Uҕ�Sa(�*��V*#.�l�T+A04�Kc�3ɸ��d<
+�qF���D�9� �|��L
%G�
V�c����$�kK�TI1�W
����&�-�b�+A02�KC��3���`<�QF���D�9��i+�ۂ`%&D>���Ar�Sǃ����(-��	B��4#. �b�+A04�Kc綋mI=�$�Qp5
g�[|O$��N<�؂Y�+%�:��>�(�� �,���B�#P!��ZIH�x��.�;T��+e;!I�J��_�ҁ�� 
v)�\�F�x3 �>����ē�d⡑�ÃO�QA2��
+���ŵHc�(HƠL��F�y��xA��Âd4��#'��H6K�)٭ �xh$���I"*hF�SA3���Z�o�^P� �`�EHy��pA/�^A0W�q�@��͔�Vn���'��y�%� II�H��j$l1b$cP�Y�c#�<zj�ܗ����L�����3xc�Ч��2�$S(���:	<�`,e���D ��-2�2�	V�p\��F΍g�Q3�h
��|�����5K�R
˴"
��5I:�V$eR��D��'}�tb����x'��Z�4��eQ�	£M8��F��(�L#��x<��8^n2�� �ID"AH�"��=��!L���!�4��h��+S�gƍ
kBY|O��F)pe
+��C�
+�$�d
+���ID2�I2�L�3��$* �,�p�x��b���eQ߹��p��F��X}Q�^}�U4�6��{�m�
�˪j�˒� (���m�
��
��U���O���?��������?�ς5��g�-���|E~A�	.rl�@�6�7oƹ�wpoĊ���.�9��c���c�b�(8b�g����
 ���
��$T\�	�;:B��+d����u� �.�E�[N�E�����Ps���wp�`�Y��4���5�4A�]�&�(���
�)3�

*��pޥ��;9U��[e�ܱ��s� �.ΕE�[N�N�i`c�
$�w�`Z�;�^���e���9 �
00��]0
����
\3�����y�s��5�;8h�ڜ�f��I��s� psG�j-'��4��
�`f�8��us7���9r��
]90�9g 7w�,��
+�b��3
l��	��]=��ك�f�=���
ͬ��;}Pkՙ6�
*���
+�8�u
+�x�P!�f��4��[(
���S��b��j� �˨ n�4*��)���qT�S����(˻S���i�ȺS���k��s�(��{)��Sd����p��z7�Z5n��

P�`;Eֻ8�
+�u��wqD�4+��3j��K�%e7wS�lI�;��
+y�$��UY�-����j�N��ג��;���Zts�V�PKbn���2kI��
[��Y/������ŲjI���^Y$-	���+
�%
7w�Sg��Z�qs�XNKBn�
�2hI��
dY*-)���l�>�)�4—����.nz#�o���l*�H�/��X�S�̋5f˕��*x��a1R@ x�D+ȵ+�r�5QYL��פ�
� T
+:b�h�SR�
\�eF
KŬ�2<2J
&%
���~
��*�č�
̳c�ؐ*V���
�J!Ύrn�OI����1b�j�J�5t&����1�p�Q.5��5Y�ᥠ��
�+�Ү�
$V0�HO�ω�^v���i8��^�'l��M��dq���	�[1�k���CL5���k3p;	C�b�`L��XS�b��J
ƍe�t�ʟ_
S--
�l�
��PJ�� 5d�p��?8
����
یy���/�$�K>�B����8�=v�)*�H�*�Z�D�<>��
cV�����Z�fO[���� �
Tpy�!mb��)z#�3Y��#����b��f��o.�+Ʃ���|��I����!@^[YM�	���z6�]����S�
�I#��Z��0����#���^h�� H�xV-�J�a���5>%x�1~
+��0W�7�jeR�M8�%%9&���57��?
z�
͡Z�G	�>E�?-q��{��?�S���=;� w�
��r���M+rv�٨�F��b�K�T�(��qQ�X��K/+i�my��'ů12>b�3~��/ae|�	P�#H�錻����R�2�#�>=��Ύ�~���*Bˁ�Y8V5b�N��I�1?�)���H|$I���H�Y�8�*�+b=��I�H�BW�\^�����
"F�4�h���
��Y1�U$���xt��)u�����q ~��m����E����+N��.zǩ;J�]��,h�	�'���Lk"5M)My^h33Q�QёO.�e����:�n�%Hj�O;,.l�\ݕ�Oc�zy)o�룙�7�/y�
�ڝ�g�L�uZ��b
ӽ���
��v�E|osYa#|)�Ƙ��Iœ�8��]�úq�Jxp��H'����+5�@�y��:�=�?_�ķ7������9�Z���vڶ�����>�{$��j��˙ր-�+O�^��&V�k��n�J���*x�ޜ�����.C�FS���"����*,��
v��Ll�w�Ҳ	R��`;��6�.W�qg���, ����v����~
�v���x~<�倣�o� �%��KJ^��ς�aP�G�L[9�������o�2N�҉Qp�>�����4�<�E_�pƃ�Ų���ܭ����
ʇ$�;��<A�SV�E�Jc,U��1D�h?���mq1��5Cp��`��Z|/�c	�p�͵����f����E7:� [...]
Z��90�Y<�fr:74�45<��e1ܰqÄ��T���`!z��u
��c`t��Y�qPV���6�0>�;�`
ć�E`�V��qt���—��;t:�����,Ǫ�����H'�&hH�k������vF�d���%
]�Ł�acǴI>��5:�k�,�]�ăD
t����-���Eh]A�sUK� !�
��`�û�aaն�m?p�@��1�<*|a_B�z42�*���i�8U� 5	�jp�m&�L�BO�
��� �aw ١����J����
Ã�Q-FzC�D@�H�3C�>.ݹKYuq=kA��xC�E
a�l"!�jn� ��
at��,�0�M�)Ԛ�)�U�|�b���l�䔛��j.8]~�9����<O�'LKB`jD�4Ǽ��!Z�=Ԇ/�(
S1$�RoZ�ӣd�p�2��4�_����)��K�
���r��Vx�S��B��Dț�����x�)ȣp�
41
0�GA%Q�WU
���
�1e��>V�6
��3��밐
�����律��\��v�tk��K1�[&+t[J�R�@��m����3ju��Ԥ�MOi6O�uH���T
,Q�\�0?Qe�r�G�P��?�k|
���?�!�����'�A�mkkQ��ڦ|+ab�Y|Y:@��k�[La�k�%��aaʑ��N
�B��-���d���Xd��U�S �%c���t8��_�
$�wBH�SA�A�|X
�Um-L	dD�i�����R�n
2��@��O
��U��FŔ��E�#SB%���X~��b
h��K�����\�B�8%Í@0CBG�����327i���:���a�dBm�S������X�
+�6H]A;S`���m�b/�^?�����L�N;���/n:�b��dK�D|䫒dM ?>���Š2f�
+]kб Q��1J�2D�ģ
����J�Y �
B[a�e���yd�а� H��b�rV at l|
-�fY�A!�ƞ��h�NW-U���P
|H��e?l-5�A6c	�s�!�7z
$��sD���ђ����KN��
i��8
2���1�VG�;C_�S7 u3l���X���ы#�
Z�(\9���et3@�R!߮�(Q8A�@�vX�&�sEN��e��<�1��I��r��|��EQ3C�x高&1Uç���S¸"�����f�����'l�Xu��;K��!��r��1��� �Vk��%1��1��Ô8��O�%PzJ%6�����;��ۜ��&}׸�@�H O-$��QЈM��hrp��
+~!D�1 �A�EBA��X_Beg�������
+�av!}��{�UN]N�yD�u�i(�MPe�J��{j�*��M��b�a!��A`2Ar`�
+g���f�L%��4����3:�t���l���{pQ^Ǐ�G�Ta(��L���A~�_ѷ��}�Z�ǯa
cQB� Q��ag&�-�2V)��x`JcN �{�ߊ��w8
d�9T�+z'~�]<���O3�-uk���4����&
0���*�����l@>���
���f�J7i��.�ο47�1�"���W��z*���J�5����Z�&���B�_�zj�}z�[- �fbW�S��t�jj�h�7h��.��Oq�W,P�-^�
ҷ����TZ1
��������}�_���\�*���۠Z�3Xᛟ�8�5VD6�
�&�
�xMO���z?w�<u;�W���	�
�W��yk�M� %k�46
�	ҍ��^*�Z�>Y|���T
x������
��
���8}���>�q���$�K��oG�lt:7���8}���>���4�

�p[5�~&���}�`�m�`���
�	�=>>ʹ
��~��_|��e�6��św/&G�\�dt;~���3��Z/F� ���<������o��.Rɟ���T�f�n��.ϗ��u|�h���V֘.lZ7�"Tx��L�|X
/���
+���.��~���;����8���E�*t&�k
>��v�l��Y���v���V;�:���?���:LI����l™�Up~��
A0�'��OFa������&���ס�=����^!�Ꞇ�d��Hʥ_a����Y`�V��NM����"6�n���5�.�64l���7h�sN̢]�mv[X`k-�������q+�O�#H�
+���͒��#��8���oT�%������?�fZ���7�H��R��4������h
���G��k0�m�_�A��I��x�9������?�
���'�$�Х"�O��7����a�P)LzoЊ"4
+�S�?����{�^8� �<�ד���
0�tƽ���h��ۼ���/F�b���Q�DZX�@��m;�

?�^��k
�
x�8���Ͷ�;j=~�M��t8���M�
j��]E
���2�~��;xq�饑��@/h%};��[�`��?�'�kk�)t�� $��պ��h1Mh�MC2? ���ƨ�������q"��g
\�
BzP��5?_k$�M\+Se�N����g��cD��n����������w/n��֝cvpn3���>��c<��{qu5�N��	XR?Ux��ߢV3
�;7A�~
O"H���h
+i�,�����U8�B�{��0^�K�xD�|0*���������O{�~�c������]�3�����!�4�AT�}!�}Q&�7�h����4i��
N�Ér8Q'�R��K�R
��=�&G���G�B6�6���p4�Ɵ��� e־r.D[@��mZ��k|;^����}�7ݕ@��B��[�=���ލ �T�A G� M�L� ����ٿeی'U�`��9מu;�w@�f'�O'ګ�'�Wċ�/:𢝒
���x��u�'����o�������y`���JV�QᤫSxg
tFB"�쓜� aZFZ3=(>�=MLe
ĕ�����D���1x�K��p
C=�4���.%��'�Xk!-|X7�8��rQ�$�>C�-Wç�4�lf�B�-������>Y�0�HF>XC��N��$�[�{C�����Q`z Q
+u�K������
d�)k�o�/H�Ȩ�*.)��j�C6
afr)a��
+��"D�"�l��|
�@U
+�����%�}*���
+2�,[
vW�4��nyh�=
PBP��0tb[��K�D�̒��vqi|ѕqP���p��
������w���d���m�Я��Ҳ+ai(����U��=���/���UX
i��5Z���쇤������z�ˠw1��6Ny����ҋ��Ԣ�=������d���{g��
=]u�_�voŽ�LUB��;F�U=�kūn�O�R?�'C����
���	��U�uao�Kkz<U��ϼ�
����u��P�vu�����(�9���,3��/{H������3r�w�>��y
+e3;�����] I�M���x�m�h�D�
Ln���d�vz�`�ZWVz�K,^
X|�f����x�]�Y��gIh�ɛ���%����I0U�6�`��
1�U�eWIz�h���H�ݝc�?�m�����M������2�
�{�l;?X|��
+^��@�Xn�2��ſun:=�}Y���
M�en�;�q��>vǰ�|��������!�
)�7��o�aݎ�/_���oGS�7	@��^�G��7��aIu�Ɋ��Z��~�	��{5
u[��� �? ���n�>,�S�}�:Z��R|��m�_xT����0E]UU[T��
+2}�Zc�TU��Q�����Z����/�m���-t����7�
�[޷��2
�`bf^ş,e���	�+B~�|(��]x�?�s۟����{�7}�))
+�����Ow3/�HK�e`Ǔ�ט����eNc�6��N�;�t��/�4���	]��-���?�f�G//�����z�n����	��¸0�̣W?��^u/��ⓁX��=�aT���}�����΢� ����nw����I�-�Q1ԝ��O?v����b8����EG.
�/Rai�x��RK��T�;o��I'��S!���ڡ�$�Ļ�u:]J�
Ca�N1�Q�UԪ��_U�:��6D�*T�1ѱ��+��;�5�f�fk���'t(c�\D+�~�����<e.U8�ֻx	��D�k�vV��)|��_��u>~n�
+

H��&ݤ���8n���a�J�1D޸�ypB�(��[+�цeY[��&tBh���ᓿ��j#T���)E��
+��e��$LH��v�f���`
+�W��V1��SJ<]�C'p&�4�
\�񮆊
�����ڤ˧�(�U����`�5t�>���"��j��R����|�����J�������"�s�6�U�

,UW$��*
8�*1N\=2&���[�o��a
�=���l
,��VY�� 4P@ƌ��
+hfhW���L�@X��G�
�C
]\���<8!����ԕ�^i�
g�t�[|���u��f���,kJ���R�^II��;��6FZͮ�",��!�
�]��w���|�t�c>f��Ԯ<
$�˓�zf^��ӱcȮ�|���{N���hի�1�z\��.�/I��H�D`"m�:.U)1�Wg��3�
+J\�l�0!�3�H]�}#�1�vS
�@��Z��C���
�����A9Y�2�ho�O�m��K�BoEK
���0⚙���^�@?��*x'q=���3�b��iH�_]���K
<�G[!���B�,��P8R�)'=콰{�D�
)�S���pR �xx���h=�}�T��7]����
��ua}�^o#��Eô7p�	�qw:f�*N\�
܁�q�>V#�棊�*�*x'5
U0���:��H@�"
���+
��1OK"
]�H=tw�5LF�"w��4�~�T%^(bΗ�,f�x ���M�i*aj4��Z�ȸU8�p�Ծ�p2�ª#[��4|DX���
/	��*��+D�u>�	aN6�<���tv�'
<��T|�.�
+��	/��;$B�s8vҚ��K�m�,�
�Yw$mX�Q�
+D�'Fag��6d�J��5P �誝��Ѕ���M�nK�6�QR_m�*�!V��B�7S���4��V�
Y{��u�p����ǵU�L��bt�w��R*���{����&�pU���B?
���A�Fz��S�d˭���ƂXeU��J�h<�
�0�|��P/�n��ij8(�c���
�!���Ao��� `�[�u$au��ok
���u�~�-o���
1���6"
p$C����P1A��5��dS옢��~�H
�E�
���|Q�0�|��Cd2
�x�i��<���؈�C_##�R��C�L:�[�b�
���_��2p^
�v>��pz�x��
�
��N
>FM�gAmC�*��e���:�
��4�!at�DY��	Njž	��
~0*����M��x���%-�kS�t�B�:
³�I����~ �Be֯̊֝r�چr���
��������?
=}x{û,-���5]}#e����R ��Elɂ^���#3�
�s�A�H
�#-�(B��X�
+�Tx~Y��
˔Tc��؇8�5̨L(����b��D��r'�ft����mQF��
�5��>H\����
;�઒��[g�G��
+A3���y}����	ۨ���C
g��4�M�
�;;�t
n"e�o��Ü]
[��3�*߬4�zt;�
�]����
�z�rx���t-u1�t����
�H�:��^�I�����ϫ�0���N8��0��eoO�K.3O!?�[ �xt�z��0
�1����Ǘ���*���0���`�ׁ���{�/�m��*�I~�
�o�贂��_�
�=�oB���fUQ�b�G��1������;
�o�I�S݆���uwr{���� ,���.\
 �pz�*�x��~�ߵ^u�a!��@�h�L����a���N�ԩ�Xs�;�
��v�v[a���������ew!E�k=���??�a�zo¶I�o=~2
+�K���Q|�E� w�/G�D.r|Я�	?�����?tF��i/�~��B�xI���ԓ�Է
���2
U4 N bX�q3��PY�3-һ�q0
,�r9����=���c���i�?q�b���k��k�����.m�|h�ա��b^&���d_n
�e��+
+�u4]�_|_����[ӕ}إ�߶�4~�m0��Ww|�9fe� �
��r}ֽ������d���<,G>���d.��&���#Q�-���{���Ѓ񵆧�ͺ��ֻ��kG�
+>�lm�ګzݨ�uSޕ�â�[p�#�{=�
]tO �����p@=t����e��>��'����L�&k�(�
q����K��Ϙ��q:ހ
,�(��3�̃;܁Wq���0�G�n��Qy2��4��t)'���h�f�]�k��)
+c���W���j�_M���
���b7^�=�/�*c=��M+��3�����l�?��=�@��ߌg�Ϛ�`&��}��D�����/;��*`��w��w/Gë^��Ͽ+�[bx{�G�F����
+q��x��.���h�{qӹ�M>N�)��_��^n�O���Vt���g��Τ����
h����:]N�jl(�,ړ�B���N�����2��I�����D9���{�nk��=M�Z��i����cF[`�
&�V������]�?���m�O��D
~M�wV
�:A^w��LBݳ�E�Ky\,�%ei���O˒��?�n��L�Z�1�?
O3�k��� ����z����_7���s�:�I8
�
m��X�6
���V��k�۽���XR_ާ����7�����m\ڢ
��|����<�)~�]�R������Ƥ�S�q�3�0%i�
��˛^{z"/��/�'�[On'C6�/X��E�V��`x�>�����l\Q�7
���m��0�f�pݺ�܄E7�]��;ٸ/��6u�N8�/>�6{��4u�H-}Q�Cg��Ceh��K�5D�63õeG��Ĵ����ڢE֖����čN;��qfsbj�У3��m����eo�)��J���~g���
X��$�?U;6U5+��!I�|p���3�߅���W�~�&
lc���
k��A��T�.����@���;�
R���핰k��c�U�
��yC��
�S7Ev�M���oߌ��g�P�l>�=$�s�����ʰ����t.�5"iP�T4�@��t���,�>\vݽ.�F1���O�$R�6�f�Q�Nn�N�������Fcjֺf3����)[�(n�U{w�!���*Q �	%F����r�z3#\J
W�w��i���$

�hT�MWЊ�4�����{���dEm��
+��@�h�吸

Q����7�D�%T����� _e�ufo��D
�
+B��R.��0�U����A��Jx��V�nI���z�b��mm�@R�Y8�H]}�t��������l�z
D	:�y���.��c\q��ŌR5So�N���LE
���l4���m8z����"ĉ�o�0�`�jÐ���-�.�t�� :��*����0��K���W�N,i�f�3�t6����>�g�dzU�ל1ݔH�������$�F#mH)\�q�@S
LҢ������ڊ��V�	��@�
t9��D�G���jҭP�y�d,�����
���yo�����r1
+Z���_�-�Փ^��b}�
%l��\�[��2^�u^e��F�
+Y	����2�o��
5f����|h�`��k-+Y8EK�V�qfkO��[�7ˍ�����E�T�$
,��+
��X�f~
��������\�iw
�N��2�k�\Q;�Fú�v��\q������8����WM F�vG0
Y�\Ra�1v�
��/��Z)�@3A�������A���L�8?֫���p���H� ��l�� 㙖
��<[�L [���
<�X�'�z
��2l�烫�F��(Y[�-%�U][���j>�ٓѬ���A���p[a�&ﺭ�(ѵ���2n���;h�;`�we�H!���1�I��V�v+�2@�;����uD�V���(a��[�}ԛj�ϭ�W
���&C at q�m�&P���w>���ss�@<
Ʒ�{�����2��� ��6�nx���[������ݘK^�z7����Ϊt߀��~Z���7x���
����̓�h�&�q�jvW�-�4^`g���f���$��p3����S�'�:{��z�
+p��[�k�������kӮ:�i����楥Q񝂐v���WSB�l�I�z]*�ղR?�bV���H�
�=*/'���6c#nrQ
�����)blF�Xv-���-&U,ӌV	߬A��3
����	s�'X�kf[���M�u�}򿤖��U�W�ޮPJ��y���ݼ	y��ب
rXF3Z]�l�]#QByv1��؇����8�[����z��7���a:k]��h7o�߷� ;^]����BQ�_Á>[|��L#��Xޠ���
+����.���?� Խ�)fQƓ~;�7Q\n4[P-�4�����u����s�����>�+`7@�%��Bo��{;�����fk�����I�$$!��3�y��b$[�eI����N
���Ӂ��_���pb�I�	h�`g��	�4h�
+St{E[���p�X��0-�Ǟ���妋:�閧zxq�d[6�� ��:�g��������� ���;�3�	�j��D� �g
�H �5{�t� -Ҧ��)Z (r�L`�D�!���h7:ӒK�H�L��7'˖
�$�bBY�"1��%���3(��	�>M-�<"I�԰�
(
�ۼ���V&��nevZ�
�8�½�0,h���vɖ�a��­��(��e���K��a#u�B����R

�\Xf�h��mF��d����t���läd0lu�]"������~,9�����J�먦@�CՑ+�$6�m&'��@�	T�Vlǃp����WH��7f|�t��V#��E���m��od�H�n�h|��n����b�n����p��wR�6;_Ƿ�(��:��@�a���xj)Td%��f��#f[�ф�+�l=;iX|B�ߘ�	�6��
�`&A%�hv�~�[-6V�7�!����
,��:�F�����L@˅�E Ԏ���B��hG�sK�ܬ$Dh ��#�Q-�p�HH��YbƩӫ�@B�K�
��;��#�V��n"�%t�3q�w�J�f��ݢ\�-k��+3a�e�N�3�N6����]f�E Ɂ��É��	i��
4�$�}
�v��o	Ý�pY����w�:zk��pX���p[��lS����֌!
[݌�h%�N{��
�
�D$q��2��
+�섵����z
+�*�*g�����#��#��w��[�%�I����/����J�e�
+���%��>�X[�TN��A}��Qwo�h�Qz���,I�G��z�Z-�11��3���{�a��H7��'>�qB}�z��x�)!�@�S���'}~�*Ф�5�gl��I��
<�
+�׉��\�TG����|f�zK�g�#�X�͉a訃#���ٖ��������>ޓ�y/����:,�*�$�5j���{�j.E��3J&b�����}
b�M�D�j�S$��iW��Q��i{�F�;>lX���WsP�".Wf�pUͭ�-��c��6d�͇��)��W�3�gX��/Yo�'@���8���_g�0��>�z���U����M���[7�-�r%�w
<k��?Ӱ&���
�?:���7�u0��7
�X�5��}SS��Z�X��Z|ᲆ?լ���~q%d��re'��*���

9�'nc"G\v�u���u&�C�����.�VL�]|��^�{�
Y��9�N��d,�+���ٹ���v��7y��*&Ƙ�ͶTs0��Ƙ�[��iZ�@c���
�8ѱ�m=����$���
\
�r`��+�4��y2����H\;�z�%17w�D,�񧔞�)
�Q�q3�2QNS�B4
�gOq	g.��Jf����G���(
���`V��?�H�ރ��j�7��Y���Wf�ۈQjz>De"
+��f
#�,]M�����aID�t}��G
Y�1��1�T��s7�X�f=b]���-���!v�E�د5��2�ˤ�#n|pB<��
��!}�?��B���	�2E$\.��7"1��@�
� IhNj�,
#H
+=t�t�B��kɍ�R�HI�J#�qt�T�yRÚ5�~�U"ͽ)���%��m��H���"êӄ��?d�<���4�C���Yk�����ȾhI"�I�\L��I��J�!�Wjb*�R�l֔�xS���P��G~�
�o�����(=�N���ʐvPFN��2���(�~֡L��e��**�ӼC��)K��la N7�Q*d�
o�?mS�j �Vu�)%O`�hk.��rR�4��R�_�*sŕR���G��؍*�vtV���*���V�LQ��`�*�XU�n١��ͪ�J�U���jT�T�Ee���e�j�_�Κ�B�,��j�)�P�ɥ�g�N��U{���:�= �8�k�){Ԭ�N���
T7,���k���#�����K�zx����E�נE5�B5^�]/�v��5���&�ưyM.�K4�
�ъ�A[�K�o�Gt��r���a	=�'[
2
94�����h��í�k|%�����:�I��YM���i�SP�.'ǚ�rh3gG�#�լoh5�Yk�Y�ZG�g�����6��;��be�-uCamc�;h��e^;w���m.=�^֑0��P�Y�ަ��u�t�_�E� �M]
HE]uW����D7�
r�M
s�.��R���&zk�Xһs��>�?[��#q֗,멾��7��=�կBU�E�x��8
MGep���!�r�
)�zf(k�#Ck?�ƛQװ^�:��~�1����������rl$���X0c#�<G�3j��{,Ƌ�ǭ@M��*f���)`�wL�3�2'g���+�M��=aڎ�
�rW]��h�hv��Qs�pl�����\wv��a��7�-?K�u��,�X'oqi�Kdy X,ٺ�a��j�([KY6��ƪ��V����Ꙫ���5շ}��]����c�?�t�h ��Tk,�
�X�RRa����e���8���(��V<X�/���㵽ۆ���
ߥJuz\Em���h
�[6�oٚK�tK��Si�z����
��=�
��%w,c�y��on�q(EL�:����
�5�L˨t4
�c�R�
g���4�
���V:��8�Y�z�ļ��G���2ba��\d�ZqU*�x�۸�ޖ�m�:\n�~J��,��6���m힞��y
+��|��	�RO6
sx�!���F~��b��Ͳ%oLS?xˉe�;���z�L>êU��H%dV8�k͖ߪ$�_�����x���������h���`:�3�����$
y���q�
��V<2L���h9	��l�q�C�d�
+�^��5�C�v
�~�:h�fs|
N�p�+�G���#o�ZLZ#Q�]��P$2ݮQ��[G��*����Ƣ1��jG˘6���y��m,{�N�~,��
N��[
+{<j˅��%���uBݼN	w�p&2E���5�Q�0�WVMh��y
�D��D+öEj�g�o]z��l]#���9��������'����졓|�hԟR�7O��:�\&B��a�H���P:����k�I<��M�2����	�T�L���e��Y(�+v��`�_���
����yN�\p`�)�\E5���o^�#�|h�竵�-��#�{�,D]�N��T
+�@�(Z�H�1[�������Z[�-G�R:ܽ�������ײ��+�E�U��ѥ��YK�O�����{��m���Xg�Z%i�W�E���(�ƪ�TK>]�����5ä���:Y_�]��k��]w��x=�
��sMⷡ�U

+�]�k��U6N�P��J�~�E���\��q�v��^m��ul�62Z�۾e.ܮ�|��6��u�������δ?u��	ҍ��v�;>�zHϢQ�=�7��ʕ\o_ݸ���K�/"�y�6��Z-1H�p�`�\�zG�:�����߹LF��;
+G�Q;R����Ry��)���i�4>�?O�RW����L���h�2-~��	v�n�
�̑���R�
�m����=u�̋��u��G�
�͞^+��b}�#K{y�_����r�>�V��޿*�|�ծ��篫4I�V����a�����t]'�����0�eϦu�g[es�F��o#3;�Ϋ�%�xw7�b���m��3){c�
�VT
J���7"z�
C�c#=R��ev�Xz����N�����?Cw�V @��.w��TF��寛�같E�'��VO�+���H����u �dv>��t:�v���L����Br%�[^$-�/J��S��U�l/�:�.��W�C��Ά;z$W	zِG��ds��}2�񾈓z4Ÿ�rŰ;���P�;�r�H61*wT/ZcLk�[�m�Z��֯�c�p� -����M��lwfG{�'.g�m
�-�W\��$�sXI&C;ޢ�]�r�
�����]0PE�^���q��`mr�dcw�#��`D�[+�Ɲ�;mne�^��-5m\Ƒ���
��� [...]
����q�o2��?Z���_�K�����yr����c��n���{^�D�z���x�?����@4�`�=��G�g����+Vس��e�bs[	܃�d>�n1\R�f�pQ�1A��4e욢99%�K�b�dHĤ��W#�Y�.�/RK��/���#���M&ly�;
*
;��„�#���	���9k�*Ј?��n�2��"U�E�Y%�
+�&�Ţ]<��M[ �� Q��������m6E���T��1�R]I¡n�~7چ%�Xf,L�����r���-uq��v�^$�&{�ٲ5&�J�ӹ��E�+1�5��8��ǖ�����9�j�	�;Fam���|���=x�
�
+3{~�ъY����
M�
+���3NgY_:�@�k���i�Rjd|v5����?����ieL�sK3�YG��J=���il���擱{��*�#�>��,�
+���"��D�5�~�{�
�8�:>�qBd�u at 6�v�nر�o��������l��6�Җ
A+�F�逪�0y�g0/�Kr��}c�>��lcU�ڡ`@!�A���j��;H�Pl�w�֬Os�S�	�(�!c����>�j�@��2��!2�<�,�<nJ
i`��-
X��C���F�AN#�O
}j&�
�1'�4�e�
�=:��?���A}�DbĀ"5<xs�ڮ�I�J���5{L�FZu�5��)b�V��K����U�
�~��k��o��Ds��IDs-�M��0��rJ�&��'�Ĕ�' ;�C*��!Y�&��
s43d�tZ�
�|#L͐ǹ:���L����瓒;p6�ot�'�R�~c�#�

�2N���V4��k����e���\�IɛI�o�<!�*�cG�*ʧ���;#u��>�t��c,` f��	����{Ghr܉�KF�ⶀyH•���_��^��z*�m6Z���d|��`xv<%��m��s��m\����
�c��*�������T� u��}��so��&��tI
�c���wx���y|u^���4wJ6�*�z��?�i�`%��!{��L;sC��9foќ!yL���0CXWA
I�kS�h5�_D���Lx�M0b��
��i_����	���ML�G�
�@6#Z0k:�
�Am�gs0�ەG) �����`�f[��e����A����c�l
1�j��=�$zdI
���
,)����}�~��iX�6�M4�O��� �b�vSm
]v�-s(�*O��ؤJd�)3H8��[8��aG7��#hl��,p�:�����WG�>������eu��3�aY|��2��<5C�Q��
��i��
�r��0�[�o?�Ozo�����7�w�݁��?�闠���
~H����PX����_d�S��
�%�9 ��Bu�&~��*�w
0jT�k>��.?#�(�@,D]'��u�R
�F�0�Ӊ�II7�GnT
�Y$����nP���X8%1�oLo���'†$o�
e�wB7:q�A�{|����
���j,���EΔP#��n�b޲?,b��-�H�=��<�,�"J,��%n�`�6$*��1�'

��
�����o��vB
�
@J�˝
4�Ҵ��>J-���gp���

5���7H-�_(�7C'��1��O��7�2\�?bC���U�B@�ֻ��'j at p�H����e ��̾��?�E���oÈV��
��M��PK�:K7��� 9�T
?VB��z3u���j�:��
+�V���
Z}[��e��^`�dc.-P��
Qk��o�Ȩ}��Pv w at F�@P��}s'̦��Ȇ�6��`��Uj��%�MsV
�톌x��ͨ��?�/A�S=F��X	�0�v��X�P
㎔�l����O������a( �L���l�
�d�g5t�<g'F�˹b`��ZE�v��
�vq�
 0��֛e�y�\��J
���c�ַ�6eG�5�(�zp(
ma7�E��m
�_��?ߢ
��p@�������a����Qc+���d��O���p����Y���!PԊ���؊[W����+.�8�����|�@°研����U�y��y�aNVGČ
+M����(�6��x��\��Q�z���M��p�ɖ{��
�%
y>�2�����7��21���lL6�4A�@�R�M���\��f-���d�gP���uhC�[3�����'�L�\����������h�P���~z{�`��s����ˎ��s���`^��.�3������z0��ll�7վ4�Y��ck[y��;0�t�� ��H����,>���b
���=X��p}t�!��#�lK	��%
]�V��_0/��-RC��
P3�]m3�rs��c���JM��
�*���sbp��Mka9�l΍�?��~��ڨ�6ٍ�AoR�t�;0�wC��b
��[Vo��
L�굳=������b�U^�^VG��] N&����$�m�1���!ʰ�Y`s���
�=���\�+a���� ��Uu)�]�q
�3��am�#7zjg��?��<3�KAF�6�N���b�b�b
ţ���6 ��9a4��
��w��\j���5�+�<5^D|M�
��ش��
+Pj��Z���w3-1
��/0m?1;s�:�R�
*����V��zw}����.�N
�]��W��M���k��v��F�S���]��U���B=Z�E�2�;�ENf�5Z���|��gf�a ��Ro��(h
�3 �[#�����:n�$/�
+0{��su�zƇ�O�M�w`��>VUͤn���^��\�� q�_a�
��f����ۮ��؊�^�yhL;8/���S�DQM|����ܤm&0o[K�g�u�J���ƣ�`6�q��j�X手�������G���3o���U0��Ht�
+���x!^ߵC����I�HO~J1G�vy���
+�V&u�p�F	f&�-��g��9�P�
n������ Ei}�|��L�	��� rw 1��\͛�QWȁJS�3v��˄�A>�3yI�c����N�Z� 
f	��`s���a�
-�o�"
�R��h���t�1�Tu�}�to��h�u{^�0���a�M�F0���޼3U��Y����:
0�d�i@�bk�%s���T����)��q��,]�e�. �#�g�;Wdw�
��֯]L���‡{��o�.�l
�����)1�zM�3
�3
�����!i!�.���z?������%��̆V�77<��f��ܡ<~���|���Kd�+����3���:��Q�&����ju�h(�BN"=ڠO�gS���~�,�ٴ2�%wL�R��`���ݘD
&
��5��
�
�,ð��n��i��]u�ogW�{���O��ۨw�-�H�������Kc���cm�c-2jxz(X�^�]Qţ��T$`����F�$twA���y���Q�.VK���
$8�X��Ԧf ,\sKK%���UA�63�&s6�_
l�Ij�Rl��$��|�P� p�lĨ��&�d��e}e5���
����RE�3J�'�"y�R\X\r���Aor5ȁS"���3��
��25�H�.����������a:'u٤�=����*�b��s�Z�����V��eiJ���x[�����ҰFn�X�V�5���<���
����p�"�y�+�}�@��4���N���x�2�hW�4z��0��m��}�1��.4�L�X��<`�u�4R���`S�#P�ӅێL��ߓ�Ij��Y�q�KX�,��j��y��$6���R�HO�"�ӑ�4M�E7�-�%.�4rÜ�m�Yy�	i��{RM!ߙ�e� 쐓-�Ǚ�Kw��z`��I�W華�o�B
����lV
+�b,��9M[w����s�A�d/Ar��X9XV}��������x
d�ϋ]����1�:�ڽod�QO�|�a@�K������$��ޑ���DŽQ:����������5r
��U*E.=3���8�Ph	s3
J`k���
�ϔL�=�nb\�6��S��I���ɬ�.M�)fJ�\(P�2���d�9�����t�������Gk°�N�{b�h�Ӊ�{�jR+�B½it�I�$)�EQ��vW�)+I\�Աdh
Z3A�y�V���?`f
�p�ku�ܠ�!���9��ȱ��X��%�b��Ю��熮�Q�f�̐�1m �0�b��wٶ�,E,��vW�b��!C�����B�dkpR2�|D(�
�O�L�\�r*
T�0��c����;j+�.���Zij+�f"Q\���� s�=��wՏ�(�%�h�����7��,��bG��=#�:�_3�>���j�j9�ٶ��}�ML�ʾ���W�vS�OLuo,w8�Ğ�ms�
�%����
N�� �dt<[atG��Z*�i�C �2�z�1~�]�o��&Q2���E�@j�-Dƨr�1o.��dl��(��-�|�Uj�����x�{o�F�q� X�S`a[ͣ�>:#����uQ [...]
���M�z�Ei
+��* �QET��#
ɂh������t��.'��,���+����h�
	Lm�Qۻ�p`l�@ݯ���s��vN^;Z�Μ�A����nz
���8>���
����NA]?��F0��r��:
[1�1o��b�O6G���_��O�
�=\�Āb�\�T?���V���f
���0
}��
-��!:׼���	f
��@}�q�����k��<ve�6�B
�<�b��,~:�r
��
���
�m�2�B�|�1��d��T�"tϨa`z�Fy���d���P
��A�Χ��E̻�1��(bl댈٪�#���,b�X�9�m���X�9/x
P�?Z��2]
f�
*����bׄ1�k [���k���L97�đh��K��C��qى

��;	���	3�԰����Z��۟
���r+��\�M�c�N�Q���CID��3O�vm
��cr���n�zz"�Y

{�`��/�m�Ei���
k"(����E�,��'1��S�x�=�R����gd�	"={�ҔËF��HJ�=k����XOסE���BH�[Nl�.���.8H�bϚ�V#c�� ���G���KBH1r>I�"U����
��5Œ�SKxV�c���Q�3��iN�[���"�7��Sݺ����8���ΩG�K����
=��R3
!U���!�y��!u0�J
�����Ma���Aut]�R�C{V
b��Tض���H
��ѿ�����\�A�!�0��Ɗ��DH�K���O~��
F�R?R�x��ُ���� Ҥ�ZE��-"E!�`,� b=�%5Dkx"p�4���}�#͹c-iˬ�!uUr�:�T���䌵
�r��S�2u<�ת�Ki�pZ	!U���z��c}B��c��,�4k�f�3�Eie�SH��<�B7���!�cum�#�4��V㩇�`���s�^����5�C���{DiX_�7�0
Eh���� �t [...]
��H��5�t�5p�:�,_pRZ_rcӆt)��XN�EF����C΂tU. �������K#
�&
+)��!0%�Zi���pEa��O3��yO��fwN�
��W>��*
���W�pǚ��PM�Bj�r9�HU��sܡv�Y�{5�楜Hm�'��t��w5#j<޳��<�V-������ ��/Bo��iC��\R	�s@�4�&�[�6޹ܤ�o��F�Z
\;��N�J������K9E�^.
<�v
�Mk�H��(?�r�1e��Ϧ3���X/g4:�"o]�B$t�
+���2���|�Y�מB��
|kz�T��>��
�sij�N"DSL�}@W���1CC}Z�DަL����}��R,%�z�^�ׅ���y�0��m�n�g�M�-�Xa{ܛ�3��[��Mwx�3t����vp�S�P�b��-�4�Iᷳf��(�3�s������"C�i]��B�>����=R���[��[[,],Ǡ�h�
!��D�3������>p`Y`P̄�)�/%h�,n6� ��s��ь�l��q`W���������*����-L`kfN��
+�נ7���ٚ���l^�<��8ܑ�ߺ
+��@KS&Ξ��+�NJ�6X2Ӫ-�sX7��k)ӓ�F���~�[6R��K�d6ZR%�7�"��
�@�v΀��qж�H��S
)�s�H�
�
`�C��{C��p	�c#uT�l�#?,�S�F�@Ji��p��j�a��VK
��\`�H������X]*���Ő�$�&1G㢥t
+Q�P�����$��12���*n���zb&�p��jWT�x�e!�RY
"2 ���5ʒp�
��pM	�ڤc�n�S
�pi�0�	���d��8ob�����Xg|k�G��4@�i;q����o:�U�h�c��@��
�A�yz�!pC엤�d;���^(�@Y�d|h�e�g�h\�
oM��>:�e�(
+C�?�
8���F@Cc{�E@哵������ ԥ����Y�S֕�g����}.<w M���ArJ�$:��mW
�/�gs���@+i�$�g�
�W��ϔg���i8㣰P#���%���
�?o~KQbI ��fv��'D,l�=6?�e�U��Gjm����n�;3/�R~|~K20>%!��l��EO�?��G/)zh/
w6�	<�;'� ��Lf5�R&@�D�x�ʧ�P���t�n�'�%���"�R�P��]�����^�ZC�!��`�Ѡ&:*z�^C�JX��Lj��p at 7	���ߗ!�ϱYɢ����i
+%�G!� N~�0s��-�K��L�إ���������W�ض��wݍ�9�Q����{;-���Ҕ0����)��blWʘNf�g� �X�#e
f]���~��b���v���ތ0Úb:-LjJ֞5!w�t
+��v���rEO��|��Z�����Ț�i�+U�����]{�_kO���#�Ă㳾�W)�W>�#C���*�.E
k�1��$2�/T��U+w��+�N��\{N���J�~ l�
%���]Z>�n(_
��x^0Q���=d�P�+��
$�׺�al�f��5\Sp�$�֎��K���zyiJ��EJ�z
���R|K��U�H������2}
+@�0**U��Ru�}ϴ�5%8ݥ�\cAl�ϢBA���ʭ#�M<^GhK���+) �#\����n��&��=�2
��w
��EŚ�{69},�	2��:���hJa
qy��S#

ܤ�#$>D0��x�.1����
 $�7���# Ƨy��
_�Wο!�o�0�".���0׊h��y�+�EܛL�l
D%�0|�l��46Ǖ���EAQ�B�zs�K���^�L,�^�%��+�a)��z�̯��-q�^�xH�
Ϫ��F��;Г�>�(xe�p�f���}h��gNrh��,�h�i[����`���sf�ƀ2��}tO��]�=:>$
�,��
_Oũ#�a���
c�x�4��Ƹ�t��J�!x�}exշ��R� g���#[>d�� �#ol�����,�J|�+#�����-�l��j@�U�wZ2|����v���-�p7:p����ߴY�ePG�.K�z���Fլ���$LX ��^�����n�
�B�~�Y尗���I&C`طr%��ʑɒ�J͚�-�{bŇ�5$?�M�@���I��B&���
�?�{��0�	�[�xn�de��7��|�Jh�u�AP������{+q�#r�'�D4�2I�V?���*�z��D�}-�
�R���D;���z�/��k�C!�F��
�R��k�
�����_�!�������_(8_��?8{��
D��"��R�2<��X��;e~L���9:|��B��h�������a����-���8��)C�*Pyb � [...]
+;����Pd��6	���!
	7���G��]��6���L�
��
+�
?�Y�*6%
R>���4^h��5] �!O��Y�>��WA��Ql'C����ܔ�ΥNO�R๮y����t�.�/RG(�nL#�%w[R�
�&���&#r�k����b����b͵�m!�㋭���H��@`�O���br��,�s2��I^��&�b�-V��[6GXF�S�cЏbU�z2
.���/��Q���3�r��c
P9t�t
+�or�3�����Ig��no�8�N:���-�E�t
7[��
::~��9t�t
+�or�3�xق��Ig�1�o_��Ig�=rx�ˡ3JfЉzH�̡�Πc�.$����2�^n
�x&PD0�Ln���kIsz�

�d�
+E�pmd�����E,�|O/�S�Xrѩ�*_�)ɥ?ˆZ�`�xy�`Ʒ�� �d�@`�?�=o���"���"�KpG��)�a��]be>�p�I��e�
�O~�;�ǰ-�I��cG�J�OO~
+³���o�Ɯ��Y1�Qj/��N(�X�}�t
+y71����M�=��YI�K槉��2
 YX�R�!"a� `��Yԙ�K`�,`�&�\%����ߐ�#eSԧ�������r�L_���ȇ�����m{R�,�v�D�H8��n�iRF����G<�_F/��s�V'���q�-1�o䁽�
���1����3"ۤ��B�:%
�e͟W-4�D<y0_ΟT
�˜�� ���
��9K$E�uf�0����)&�3�!�ďB�
��U�)�u��O��z���*�K���H�L`d����u��� Q���|�����\��G
§�O�]	cOP��ZJ
�k�>��@���8�x��L��;C�'#�6�x�۫��#�`V��-r�ȑ�뻓CJsI���D%��ꑶ�yl���WY�<-R��{��;n�W�qC\�#�%#=Y�>:%'�K�p����.:͟�I���S�\:I�wx, Ṣr���4�w	�������R�K��v1�
��<2��鏴�O���Ȅ�������{xa���Ȅ�,����2�{dDcෙ?�Ȁ�i�� ����(䤡}푁ق����i�
+#�U~�
��
��
��
@�,N�%�>�����q9��!�9��.
1 ���>\:B~���:����Y��4�����1;*us���1�{�r�.�
�*������=�Չ��p3�_�P�u�T��^�n�
��UZ�I*�N'
�%��	�/���������'������(h^F�_�ý��i>
?���'�#�|�o�����XD��(�͈��|8ƣ�Wgn"�pB�h�<�vs�T��G�pO��ȱ��AJ�!J�_�Ɏ��]2,�[ʉ�|���l�׾O��BHf$ [...]
����2�R�p�`B(϶�w��pdd�s{Q	�q��yu۔�W��p��W�{���3�a�_d�v���
+��]Fj������t�QF�H�7�%�D2R�?�� 
+�d�+8ŗ��򯁖pi�|Jp�H\��n���"�c��^L��e(�
+��c�f뼓
+w�`����
+Ǣ�,�g�p��-?M�{aW�Q*
\/�;�_���^��_����c���������;���
�� ��1H�*?����	��=(Á
��\d��3>s/��p�Jacݨ���� ��7�#��큕W'�#����;Jo��LW�dgN�p��3wm�^�X/�\���Ub��c�L�P�2"l�Thp?4��s�Zj�d�KNmڐ���#����'ן��<�0;�W.NUo�8����/�i�%�5E��Nס�!eg�a$fȋ u���}O,�M2��`�ʰK��5Q���ز����r�x���6Rn
+�Y��<J���`�ˍH֙^2�NI�,�H12(� u�PG��g�
����.q�ɲ�-���C���H,��G"�����#M$j$�Ъ�

nOL.���rg_��=�I��Ua
|Q���%��:��[�"z?�yl��|�T��ː[^��!��?N��}�x3�I<�'(X�?��W�	��p��T�E<��,��똫�*�	t	U�u�$'�
G]{�'t�s���q}��{���������ɍ�}ե��d]n���K�^���w	�o$+K
+��~T�����u6�l?�W�tB�t,��Q6����M'�^�6�����Ϗ�;�_e�	��w���e�ɻ!��l�7v�/���I����Z��M'�ga�@�G�tB�t�=�f�	)��=_fӱw�t�*|�M'�KGٕ�M'�%���G�tB�t\M�/�鄔ӻ��ϲ���a�2�NH
��W<�N����w��)�W�tB�t�P�b��b_e�	
�����M�pw
�(�6����KG���M'
���tB h����tB�cT��?ͦ2��.����a�
�(9dlo�7
~�M'�����*��� 
�� ��΋�B.K'�!�tW�N|��{+��a��?�W�V�y��zuB�I[��tzY�V�N*Qq��
y,@\D��>��eu�
Q��pu�o��ӥ��6.�N/���$�D�f/���{��0g��
wK�?����E4��2w̝*�*��e�8���T��~��D�O���'
�{w at Oe�Ĥ�d���e�=��Bwof$<��{�����܉��;<��kF [...]
+w�z��w!9���j�Td��~��%
������@F.,K���hb�H9��2�4]T��d>ʸ,��=W4�/�Y�o
����ZE$Љg���#~/�D�=3�_.�����h���}�2l~�
��i��
Mc!��$��r��������l���]mA��s�%�bҶ�v%L��0����i�uq���b�ďUx�V~�b���ø��a�[�d�g-�
Y�]��H����E�G},nyaɾ�
+�r����
׸�7��:�S�ƻ�
�ڂ���"y�[�pֳ��L�;�����
D���YɁY�-�l?��8J��U���&[U��8E�)W5�@�1�$
�Y�0��r�m=��=ƻī�V;l:��p��Ȝ�}�ܿ���f%N>��?)�%�9�4�셐*P��%��wV�S�(R����"H]�G�3��,��Ҥ��Bʯ��3��HY�i
+�"�����Er��:�Դ�u��'��-�f0~�Du=G�#���L7�e���jUh�RMn���!�I%ᕛ
i�O�+t�"H=)N�B>��ӜRg�L���}B-��e�sts��J��ݎ�&s.��p�������8"U.6i|�]����U3�PQ�x����.���A��
+�϶�h��Tf����X7���$��/B�dL
e�u��B�$�҄�>�J{���Mbw��;I�|c|��[у�fM�;�zU�D�K���w�:NK^��QZ���?ϖW\Y�O�e:��o�L�'><���
+�'��K�����P��O��)Z�[�X?A������9�qd��^�?�,'������/��j���w�ɼ��.-yA#c۞�5;R��,WŒm׿��y�`�ۀe�z��1s,��o)�U����_v%�ew_���2��� ���[^�#y���
�K$�\x��@�٩�}�{�ʱz��Ɗt���D�9������sw޻D�co�r彜TN1Mi�}i
Ȟ����e!�x��}ߗL�l]�_"U� �����
�K�����b�E~>��C�S���v�� r��w� E��S��=$� ޮ�dz^�����H���rs %tKY�rs eU1����z��2� �� �ɲ���c%J������|�F;}���g
=M2O{��(�tE��*����Ӣ|
+Am@�S������(��
��|�W��O��QZ����(�x���,�'Z��|�.�
]�(;"�&+5�V�����~�
%P��X����P����wC��~�G�ʩ��2N�O��IW��}7ԋ�~r�����#
Q�n�/��I�,�l����'��
E}R׏?\nU?A��u����
����IM��M]��� 򺮟�]��~��;�[Ϳ��'
q&���a�;��|�y���}]?�ܶǪ�*+�e)�7Pɭ�'m��4�o��ݨ-\���z|���q]?�~����"�3�^]?�00*#�/��1*"U�X9�ߦ�HT���]|Q�O�(�(>/���p-
���/��IW����4Rz�*��Wu����rE?��']��z|o	�����-
��~R��/#�㹮�̼�hۯ��	�=��ea���;f���߇Yio�gP��u�$ϛ�`��I]?i3��z|��������e(\��z|���B�7<��{��F�/��IW���||����rLV]?i5��c������gUrk�P]�|}���"���u�^��M]?i(�O)/�c3�I���w��!���#�Jh��
����	��]�o��I��� [...]
K�݃�y�Qa��5MlO/3/T~i�|!�d4G��ɣ$]GP;�k�;*˚�x��\�Q���*P��^��A�6Kj�CK�dF;����&�2ޓC�4ufwN�
��H��dڌ�V=+L��[K�ı��U�Ю���[���o�=���}�t��>���H4b�z�z1@�x
ck,aKE���Ld^��)װ`�:�aҫ���{U��-�2&h��jᅒ

<a�	z%u�tO�}��jd�������
�Ksm�
RSf

�p�WÈ3�5�0���=\SZ�㚛lWi)c�:
z�#�Xd>���G����c�̜� �3�lWCr��d�y?���s�	�;�3&��T4�h�N�c:���}�L�`� 1=^�M+��+��z�G�
+n:��pa��#t�
��j��q��xt���1���P�xJ
�(��)3i�/p�vx*�?�ٰ�
#TE�&��D��1L��/ ��
�a#+�~��p�?2`'���T��F�����7;��m��@jӇ#~�����m,
���&�
�M#�����n�s]G�+jc6}�
� 
+��,>�˹�
b�y)�g�^۳�V��U�=-apT��U����G�i�����Ʉ-�Tu��N�ݓۄ5�mD9n���
� �qO�pfsw6��i����!i8h	���6q0�&�m����ݟX�f���[o�v�
b�������6�6�����
̶��	�Wߏ����-H�FGo��
��_��=
�T�Rh�}����:��ߟl���5��~��M�
�[q��ѓd"_�0[�e넖��碤�}�٪TX���ʒ�w�&-7�Cr����@&b=”�$��RQPn��3��Ҷ����
)3zʧ�2�Տ��-�ʐX�3|k�3g�	w�:J&S�D�G���#e�A��FᑚxJd2ux�*��~���;@�9�#�S�˟}�7i��ٳz�
3y/�Y�5�D�)g��#K�4nZ�D&��xRjx��F�#q�:��KC=����ɜ�$?a�1k2b��
O�r6�;�qjK�J�a�ij�����K���e飇�ʏ�f%��L
+�

&�i���C�nP
��Q{�4)2������"p�1�n�t�#�niӻA;2�e}
ܮAi�*��>��gj銢���`���1k//��S4~���g=�kPR�)h��t~��76S:�����Y.3*d�dd+��@��  ƒ�,\�Pm`���?�6�8�Ъ4�9ϕV�5m[�EE�b�@��̻�q�HiRS҄�ǁ�6��
+6��� ǛRF�d�g��Q����\t'z�b:K�NV�}��%#��]A�����z�����
I�%����P���{,-^�qS��-�BŨߛZ�c�h�̼��ٻV��Y��5J��z0\ۥ=uC�P7e
�
4���⚹(�v�!R��v�=*o�a�4a`��s���;�ne��D������3��#3�}�Mۢ|'A�M�n��K����@�"�{g�{��,����r�`yp�[4	N�VGP�
�H��yq�;�;�| �goT�
+1�c� Z�;��x1�=R~�^����$ =/b ��/����W凳Q�
��4s���
F��Ȳ�K����`)�b�0�Y ���b�����`,O�8)o Z�,�oM��Z�;��X�������<F�?}�c�˗}� _�X}�5����^��c���<ƥD[j��H)�c��w����b
JH���wk�N��Fky�D�|�j�?e�[fJ���~9�"�*Pyø*�����*ի
�v�>@ ������x�b�wd)J�g�/��<���G�1^��������0�����#���W��q�Rr>jT��r�tp��F	�s/�
��@L�i1
9Ĕoi%���qk�#'�
�83DoO�]x3�R��le<���X��P��C�Y���3����h��vP�����6������pE����pl��I��<,����Kq��A=�,'+�„o�C�e�~����

=7�|]�6�
��c��3�D�F�>��
�Y3e��Ғ��w�fն�
�N��]�]qO��mps
=Ɛ�#�*���҄�c�+��RZ�"<v\ԶcL	|��& )5nܸ&C�h}:d(�\�):qP�=��ۉ�ID�������=��	b���؃�]�u��������c�U]���+��7�rK [l��EqSmj�n�֝�h�-
��c��汑��>��H�<�>'?��k�
�z���ʂ�K�6��vt��6"���4M��"x˭�Z��}k��W3�]��+�
�
+�"a��X1���D��R�5Ji��{���B֮1�S�U��kl5�=�hg ��}��>�}�CT�� / ���������}\1;���9 �%�?� ���B�e�A��ә8�p,=��+7��O|1�gKy_$Βn�	�̣�~'+偷j�� F6�w�l(�
�6X�<�z�
�yT���Q�L
|V���hb��� *��v��W�
�F9��>_�� l
9m�@"
I��n�
+9j�nȍv�/���:4��61��&���ǸnC�
,[�Z,)[�4}^�6fuZ(��a���N/�.���ѮA�Y�j��@�h��[ʐre�mXH$l�6��Wt�47AlZ����D�(�-)� -a�ە=<�k��
�
e�ݸw��#��{
����9�Mgt4�M�٨c@( 1HL
7JS
צ
����q�
_.���G����[�Y�
Ӯ�@�}6��B!��t�_ /�
p�7o]9���wA��Dl�m AG��
<�
iP´�A(�B�|��C����vl��% XC�v��~gۛ�ޡ猏IܺJ� �o片�=$R�
����o�>�����q��
}����_�S�GԉU倛���S�	ֵ�rX��j��)a��"���c�~gХ���[�\{�l�22}:3����0�.�`@�k�ˍ&i�qh����&s����X�Ѩ)�ޏ]�&��>U��W��E�/�c���c���i��:[NF������2ԓ����
+��0Ȓ9+#2�E
��i�Y�5�
����e`�	611���6�$���)
��Y�F̚.�`���?S��I�;��œ�-zf�ZiJS2�g�U�PX�1�����s_��bl�QyX�~�/
���8>h�Xy��A
�
��= �jA_<Z����;{�V���^[�c�S�b�O����	9����/{~ǹG8��
�|�C
(���z�	H
*$�t��L�X۞�t"[�H��~����� *3s���f�
���f ;���F�x���zd��f
�·
�cU�������C�n��m&���Ʒ�ikd ��}�V�4�f�	o��0�vwe9@��="�ұR���Q�4#����ea���m��޸��Ԇ���j$n:�M4��9�
�L

�f�j��H�&�����U�U�����U�U(���tU�lR�/NW��Vi�}��?C
+���q{���u3;O��j��I�WX�i
��{�4�Ug�u!���v���������i���M��hgv�s���,#G�b�wt81�&�)�OpḎV���=T��i����qbA���AL6���Rg�آ��0/�V)�7���)vM�/��:���� q0��Xϋ�5mg�6Y�D�l�����
�[��[�A��A?����X�>��fcYY��+�a�
�O�"���ۼ����:�c
����/�BA#�K~��`m�b�X�7������(⏭�����ai,�`YQ/ �
7\`@d����;�^����D<>�"�*
|GR���!/���PQ�9z���k
��c
��z�*u��|e
o��
+
+���V��
oX�@��6�����
z��K�^td�l�7#����x�^P���*����.����^6vLP�*��Z#K3`�׸H���6�Y�71|�K��Sgv�q��m`��#;��~�:(�Ԯ̴� #����Ma\w��Sgi0l�L�0h��R���#ʌe�u�P�����_�4-��2a�����$
с�~{�>4�	�ʻ�:�z��I�>�w���<9̣^&�Ѯ��X&itB���3�o>9vv-Ao���Ep��G�ه@1��	 MD_h��y���HMY�"��&��w�.��^N87��LX�E����g���Co!�ht�r��	$��B�'n�	]�Џ
�����ЂcI�����F�
5�PZ�-&�j�S�y��!@+��Hy��
Kd��(�o���5�D+-�z���UJo "��A�dM�
����
1�r�S��::��[�7�p�ܔ�Ƀ3��sw@߼I
�X���̪#7��Z��dI쇽54�2�#<�d?b�bOiz��WJA}l�S�}�#Јr�?r1iG��
h4�dy�Jr����Υ�H%�!iq���Н��f
r����GX�M�m�9!U.����
Ą��@�YY p���9~tz�N����U��ڻ�޸���
�?�
BXK�T���ɲ��
<Q��`��iF�X�eY��/��9�֫�ُ"%
+�g�O���u��u�����D�+�?U��w�~qv��s��u�����o�_uM#&�t�KU�
N���Qmg�s���
�og�>�7og�����{����	�ʭ�.��
��ye���Ο��{e��ә@j~ڙ�^Ȃz�����*n���A��lk{�e�u|���������6�	��`��셋҉�&JKg���oJc���qo
+y!�j4�w�-/�^g��Jm�d��޹j��Z����ud����&X8vUN��x��m��;L|�D�n㊬db�e�����'�o�?f�9`��OÓ���������$/��>����w�)cPn\O���:��2��)��@]W������dL�ݼ@Ʈ<����\* ���]�&��9
�/�M ��o�p���O���Q�]�����ͼ�J� �ŚKO��*�����;
F��f���Wm�Ç��{4�-�7��lԸ����уG�Ľ���0ŷ�
\�|�����_I�+hx�/�s4,������ג���w����Z^�a]�WU�B���+E�oH�����'��݃�o?���/Go?��g�^��j%���?;�}�솾�C|����M�??�%�~�,�Y���W�_/���A/�5/֗O��tH��U��?�U�ny���+o>����wZ:�Ƣ�;߷r��
+�Q�w:_�o�����Ø����S�r�˧�~��}@��|z�WO�M�%/�^��ӡ?�m�x�����+�|z�WOɱK_>�}�WO7Wx��گ���W�|z�WO����|���������߭�����m���|���ξ�
7@�|���|�?���������=�����<i�&�=�x��o77��Iߜ��.���{�����O<;z��t��W�o}�`�;�g��?N7@�z����M^t>��7j�����ܒ�R̡�;�+�)��oG�'��}w�_���_������Lw8���˧GϞ
����ܻnԲ��;�|�� �ۤ����׿��;���F
�n�7�����es�`_l�
�2�d1�d�2��CV:�:���M�o\Hs��IyN���t u4�
+��x�g���L	�
�%?G�� h�}�e8�r�\Z�n���`��2v��u�n���޷����z�z��.��B�d�&)rƛ<�l�1�լb����u���aڅ|0~���Uq:�<�x�-X��ѐ�:?�gHVNz�z&��
�v ��$�؆��"\6�ժ<!�O7��9[��5{� Bbf�0ς���s46����`��9'�(>j�1NS�ITr�
 j�2v����
+R���1 at q�^��ON�!v: .�;���5?琧��&�}�
�P
�����:�]�:G�\a3�R�'i�u:������M�a�׳��T��7s�~QIm-��ݬ��{��l�Ź�F!d7�Z�Y��w�0[+|<�F�$�B:hg�=�%t�8
�*):Ө9'�U�y�
V��1��A�k�[���6��u6�X�F/Z_~� �����:�p8��t0V�]���`7z��up���w�D�����G���ja��<׈Y��]�H�LJ��4��p��ɦ\T�$C��OK'*��Ќ_ME�
6�5��`�ʠ(r(uY�Q�s�=�<�cQo�0tg�Έ���u�y��م
�axc�p
+��z�'�~�{��i!�A!�x5*������@�
�iS����R�c�M���9�i���(�w�@���2��u�
� Z�&n�Kh�6n����vBI%Xt�����W���_�ޮ�'lD���1�Od�M�H/
P��A�s
T?Q����MrE��c[�E9�d

%�Z�P�����{�� ��T��4�2Bpk�r [...]
+e�>BOr�=T�M�9qu[Mh�����I����>Yc�����J��6D�AK��6Сe�
X�Ɛg;`e\[��w���KNcw�4ܒ�*P,���M����p.Z䫋E���`�v
ӳ�N���h�(P-<5�
8v���s�/!�k�b�@c4����9�ygw�.?�N�A�St�q��7ZwehȴOS�{sF]v*‘������>Y;�qQ�\�sn6YB
R?|krӱ�
�
k�(`X��Q�j_��w�O
�5�(6+�6st��۳s�t,Cvmyh�����!�)Fq���N��ݿ�P�
l�u�����o1
���-����ah��}�; D(���J�C*q�*؇7��u��ջ�)�:��
iV8^��9��ƍb�9��s��
�_妍5n��_
��W��~u�ڗ�~5��\j'��Զ�Ɖ��є�s������TS1����m�g�3fM�P3>���WG9��Z�-)(��.�n��4�rWd̚vAQ/ U
��i� 
o<�6I�����
�w��AmU������"��Q��
`�p�P �|�t9-���k� P�<�ؗ�
B�,qE��!u��̀Y(
��>�=��%�����ܰ�:��F�+���իK$#f�L�,��JMe��b��a¼�s�-�'ê
�<� ��p8Y��@�V�,���yu����ׅ@'1q��{5.�
$FUcfȯ�p���NN
�0�a�
�#�����'��[�0^#]��N��`����SR���&Y��-�R	9ų�������?$�X���;��Q����(VZ��Q41 ߃=A�?@�� �T�Ca�C���\K�ϖcB�_�oj����6�g
W��9��a��"�6�b�H1�]rj �leQ8b�jI��u�!,m���(�&ټ֋�B��`S5<�}Q+V
�7�٠m�Ɋ�
�uoܿ�����/|y�G�J�b]0��� �UI��P���4�S�U<���d;���H���$)w�k���?���W���<�)�)��~ ~�=GE1?�Y�S>�O�k�#����6�_��b��UU��� A-�� �8�1��r�CU�����"[�%X��#&�Z|K���V���j��u�a%��Z����jS
K!���cu����J�6�]h�ϯuS�|�`pZ�.țJ�m;(O^"�>xD���j�DB�
ъ��
���-����:h�
E�ա�pd5

D�: [...]
P]�8��
�Y^�8���D���RX�rgh�,-	'Kq��������p����f�P[�x3�����%I�Ͳ�
�6�ڠ�M������l+{}�'B,��\PYO?u��(��Bhc`5]i��w0V�
/o�[��B
�_���-)� "���)B���WR<F8H
+%\dQv+Ԛ
���KQD���� ��s����! ��50)
c�`+x�yٍcQn�"���9�򒬭���E7����!eL���f���Ε����EMė λ��{B�P)J6&��9ޤy
p�o:�z��*퀨	d!#�n�@,�%IF6d�f �x)F!Je�ߖל��VbIO������&��=r��<�iZE��:���ϑ
�<A
�
�M4W��#��5���P}=���6�q8������ �H
�
 �Ŕ6�X=���Ґ<���3�d���?HA���,n4�s��v�s�U�m�jcc�
+�B
�HPr����[憇�0X���YB Λ9�3�3�
��J��K:����Z�
�V�;+a�Kٰ��K(C�;�
��4�u���V"
���\
+#�
)��q�|�Y�+i��<P����Y��U��-���R�����@[��P$\��I��G
��Ə�@[>�SjVʣ|���pj��-�S�-z��y���>��+>'�j���'z��3	�u_�ڿn�W/�����Z��P��_��>:�Nz�����Ѝ��lC����oF2�ьd.iF�lFR��xi�rב�
�g��t~�9S�G0B]a��e�%Up��xDjH��@J�B���"g��fE�v��6@˪�@�
4�}�
���e_�"�(kO5��D��l�T�_϶0yb~̠AJJIJapq�.*���pLң���àn�&�4\��֫���i���92%�u
6��S��y���~���C!�N	�QR��.<�|b�xZ�b|���ּ�d�bx��e�`�Q���ԣ���X�%5'6���RI�$���hq٬�����!kAX��e����uu�%��Mf���tI���
��fi�an�"�^|�X<�ޗ�q&O�s
�f���И)'>�q�����ċ#�p�y�
l��%;�Tb
�2�J��,B��})k
Ȟqq@���eװ\���
��Y�T�@����.��S�:5�,���4[��f��
�ʲ�aCP����_�w�O
��XXG��
������EŤ����oΪ4 � +-4W��H�&N�"I���.�!�
)�سRw�WB
M����QfW��u��[O���a�v�kX7��U���A��t���]���nX�w�9�P��t��{O7�~ �]�f۪st��Z��h�$�&1R���cZ�Up����l�2xkjY�c�K��ؘD�g�>Pnc�-UH��j4Y�Pf,%�cG(�^��ʆV*-입�A<
���B<�gّE�`�"
.!�!����`�Z�Cm�蜣8
��Ҭ�a[9�R�����04�
8�;�]�$m
��.N��'vi1��&�A~��*�l��]ͳ�H��!H%�t��B2²�ɻF��\;#4��++iF
 ��\L�u0*��9ȝF g0M0%-_1�]._�X�w�5�r�.�d��jK):�b�-,��  !Hf`dE�F?-EN%�Ι�KJY��u4�H
Ͳ�K�,�QBR��
�E���s�,�4#=+zŁ���d2��TZ!	axi|����!�Dz=�ca�`�N{�Wœ�S�I���|:`��>A
+s/$C�Cl�S�:,
�Z���Tf�s

�a�m��R;"�sv�0�M+�=�=]�V�l��������
�
�\��y\"��(9�8@�)�iQ�I7-,��X�H�ȁ>rŤ�	�)��
<f��zg'7�
�81�b�6UD�H��"<c��崧�/�N�6�s%�W�y�R Mf���&Df�w�k�98/aZwg�Vl'�e
#�z*�=�wy�#@��l]�
��Zp[�K�:̭G�LR-��c���Kͦ���K���}6�K8
��U/ё�%l��)
U�KH1���;	�P�⫓ ���zp+�P���;	g`D��=�Qy�1��w
����Hw
�~�3I�Yu \���Mt����'uq4��a��$	�.�l�L3����j���0�L@�K[��*I���#�X?K�� J�8#e�a��Ć1v��HS&�n�v9�b�w��L�t�3%�Y쟼F�
I����lؐ��~���s5ˮ�j�����	��j�8���hQs�^L^�1��)�*H����R\��-������$m��N�Rɰ��J��|�D��N�4�ȾIBZ^sPܚ~�,�!Y��Y�)�&F�6�-��=N��4�,��XղÅ�+vr�{D���x�mK�$3��X~�幨%����.���4�����!���eS���Kc|;�|���!�PC�؊�
�Ғ��� 7*���N�� /
O��r�s
+�zx3���.�� -f<�������R�xi��Ϧ�y
+W�Q_�5Ɲ�߰,�Y
����e�
+Y����R)�Y:
����(lC� Ę��a����Ԑ�
�uLKm�M�6
)��r�iD5�o�̳��7�����FZ�oä�ẇ�ޫ��i�+���8���-.�&��Q��ˀ��i:yt
/��#~�_�
)㝮\��K�2�׼8>�8�U����c:µy���)#/�2]�m@�GHv
ق�aへ�t�1��{k.F6��
�]�%��ؖb�X?+���"��\i
29w��nZ"E�%6���I�iҀH�S^U�q�dƚy,���.4g|W��Ô]�fl��B�c��g=�
e
'��S&�►���]��n>�X��D#Cަ�z���6�S)w4n�c9V��a2p��)
�q[�76
��3ؗ��v֋���&�<��Ҿ4r[�7�ּ�m���9�3cV����Ӧ���m�B'n�r{�)o��k�a:zr��/��>;y�y��ѯ'���'8y�_�'/N~��8�~���߈`H}���/
n�AP�s
endstream
endobj
7 0 obj
<</Intent 17 0 R/Name(Layer 1)/Type/OCG/Usage 18 0 R>>
endobj
33 0 obj
<</Intent 42 0 R/Name(Layer 1)/Type/OCG/Usage 43 0 R>>
endobj
32 0 obj
<</Intent 44 0 R/Name(Layer 2)/Type/OCG/Usage 45 0 R>>
endobj
60 0 obj
<</Intent 69 0 R/Name(Layer 1)/Type/OCG/Usage 70 0 R>>
endobj
59 0 obj
<</Intent 71 0 R/Name(Layer 2)/Type/OCG/Usage 72 0 R>>
endobj
71 0 obj
[/View/Design]
endobj
72 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>>
endobj
69 0 obj
[/View/Design]
endobj
70 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>>
endobj
44 0 obj
[/View/Design]
endobj
45 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>>
endobj
42 0 obj
[/View/Design]
endobj
43 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>>
endobj
17 0 obj
[/View/Design]
endobj
18 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 16.0)/Subtype/Artwork>>>>
endobj
88 0 obj
[86 0 R 87 0 R]
endobj
110 0 obj
<</CreationDate(D:20121102140745-06'00')/Creator(Adobe Illustrator CS6 \(Macintosh\))/ModDate(D:20130204151208-07'00')/Producer(Adobe PDF library 10.01)/Title(Web)>>
endobj
xref
0 111
0000000004 65535 f
+0000000016 00000 n
+0000000234 00000 n
+0000051967 00000 n
+0000000005 00000 f
+0000000006 00000 f
+0000000008 00000 f
+0000195977 00000 n
+0000000010 00000 f
+0000052018 00000 n
+0000000011 00000 f
+0000000012 00000 f
+0000000013 00000 f
+0000000014 00000 f
+0000000015 00000 f
+0000000016 00000 f
+0000000019 00000 f
+0000196795 00000 n
+0000196826 00000 n
+0000000020 00000 f
+0000000021 00000 f
+0000000022 00000 f
+0000000023 00000 f
+0000000024 00000 f
+0000000025 00000 f
+0000000026 00000 f
+0000000027 00000 f
+0000000028 00000 f
+0000000029 00000 f
+0000000030 00000 f
+0000000031 00000 f
+0000000034 00000 f
+0000196118 00000 n
+0000196047 00000 n
+0000000035 00000 f
+0000000036 00000 f
+0000000037 00000 f
+0000000038 00000 f
+0000000039 00000 f
+0000000040 00000 f
+0000000041 00000 f
+0000000046 00000 f
+0000196679 00000 n
+0000196710 00000 n
+0000196563 00000 n
+0000196594 00000 n
+0000000047 00000 f
+0000000048 00000 f
+0000000049 00000 f
+0000000050 00000 f
+0000000051 00000 f
+0000000052 00000 f
+0000000053 00000 f
+0000000054 00000 f
+0000000055 00000 f
+0000000056 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000196260 00000 n
+0000196189 00000 n
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000196447 00000 n
+0000196478 00000 n
+0000196331 00000 n
+0000196362 00000 n
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000000000 00000 f
+0000056443 00000 n
+0000056634 00000 n
+0000056140 00000 n
+0000056069 00000 n
+0000196911 00000 n
+0000052432 00000 n
+0000067843 00000 n
+0000065158 00000 n
+0000065045 00000 n
+0000054797 00000 n
+0000055508 00000 n
+0000055556 00000 n
+0000056327 00000 n
+0000056358 00000 n
+0000056211 00000 n
+0000056242 00000 n
+0000063137 00000 n
+0000056831 00000 n
+0000057088 00000 n
+0000063385 00000 n
+0000065194 00000 n
+0000067918 00000 n
+0000068119 00000 n
+0000069129 00000 n
+0000081966 00000 n
+0000147555 00000 n
+0000196943 00000 n
+trailer
<</Size 111/Root 1 0 R/Info 110 0 R/ID[<4E63411D91554E25B414C89B3B6A1FA4><920D55B3232646249A54E8F4D7E5BA26>]>>
startxref
197126
%%EOF
\ No newline at end of file
diff --git a/scripts/make_emperor.py b/scripts/make_emperor.py
new file mode 100755
index 0000000..e9173ab
--- /dev/null
+++ b/scripts/make_emperor.py
@@ -0,0 +1,627 @@
+#!/usr/bin/env python
+# File created on 06 Jul 2012
+from __future__ import division
+
+__author__ = "Antonio Gonzalez Pena"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Antonio Gonzalez Pena", "Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "antgonza at gmail.com"
+__status__ = "Release"
+
+from os import listdir, makedirs
+from os.path import join, exists, isdir, abspath
+
+from emperor.qiime_backports.filter import filter_mapping_file
+from emperor.qiime_backports.parse import (parse_mapping_file, parse_coords,
+    mapping_file_to_dict, parse_otu_table)
+from emperor.qiime_backports.util import MetadataMap
+
+from qcli.option_parsing import parse_command_line_parameters, make_option
+
+from emperor.biplots import preprocess_otu_table
+from emperor.sort import sort_comparison_filenames
+from emperor.filter import keep_samples_from_pcoa_data
+from emperor.util import (copy_support_files, preprocess_mapping_file,
+    preprocess_coords_file, fill_mapping_field_from_mapping_file, 
+    EmperorInputFilesError)
+from emperor.format import (format_pcoa_to_js, format_mapping_file_to_js,
+    format_taxa_to_js, format_vectors_to_js, format_emperor_html_footer_string,
+    format_comparison_bars_to_js, EMPEROR_HEADER_HTML_STRING, EmperorLogicError,
+    format_emperor_autograph)
+
+script_info = {}
+
+script_info['brief_description'] = "Create three dimensional PCoA plots"
+script_info['script_description'] = "This script automates the creation  of "+\
+    "three-dimensional PCoA plots to be visualized with Emperor using Google "+\
+    "Chrome."
+script_info['script_usage'] = [("Plot PCoA data","Visualize the a PCoA file "
+    "colored using a corresponding mapping file: ","%prog -i "
+    "unweighted_unifrac_pc.txt -m Fasting_Map.txt -o emperor_output"),
+    ("Coloring by metadata mapping file", "Additionally, using the supplied "
+    "mapping file and a specific category or any combination of the available "
+    "categories. When using the -b option, the user can specify "
+    "the coloring for multiple header names, where each header is separated by "
+    "a comma. The user can also combine mapping headers and color by the "
+    "combined headers that are created by inserting an '&&' between the input "
+    "header names. Color by 'Treatment' and by the result of concatenating "
+    "the 'DOB' category and the 'Treatment' category: ","%prog -i "
+    "unweighted_unifrac_pc.txt -m Fasting_Map.txt -b 'Treatment&&DOB,Treatment'"
+    " -o emperor_colored_by"),
+    ("PCoA plot with an explicit axis", "Create a PCoA plot with an axis of "
+    "the plot representing the 'DOB' of the samples. This option is useful when"
+    " presenting a gradient from your metadata e. g. 'Time' or 'pH': ", "%prog "
+    "-i unweighted_unifrac_pc.txt -m Fasting_Map.txt -a DOB -o pcoa_dob"),
+    ("PCoA plot with an explicit axis and using --missing_custom_axes_values",
+    "Create a PCoA plot with an axis of the plot representing the 'DOB' of the "
+    "samples and define the position over the gradient of those samples missing"
+    " a numeric value; in this case we are going to plot the samples in the "
+    "value 20060000. You can select for each explicit axis which value you want"
+    " to use for the missing values: ", "%prog -i unweighted_unifrac_pc.txt -m "
+    "Fasting_Map_modified.txt -a DOB -o pcoa_dob_with_missing_custom_axes_value"
+    "s -x 'DOB:20060000'"),
+    ("PCoA plot with an explicit axis and using --missing_custom_axes_values "
+    "but setting different values based on another column", "Create a PCoA plot"
+    " with an axis of the plot representing the 'DOB' of the samples and "
+    "defining the position over the gradient of those samples missing a numeric"
+    " value but using as reference another column of the mapping file. In this "
+    "case we are going to plot the samples that are Control on the Treatment "
+    "column on 20080220 and on 20080240 those that are Fast:", "%prog -i "
+    "unweighted_unifrac_pc.txt -m Fasting_Map_modified.txt -a DOB -o "
+    "pcoa_dob_with_missing_custom_axes_with_multiple_values -x "
+    "'DOB:Treatment==Control=20080220' -x 'DOB:Treatment==Fast=20080240'"),    
+    ("Jackknifed principal coordinates analysis plot", "Create a jackknifed "
+    "PCoA plot (with confidence intervals for each sample) passing as the input"
+    " a directory of coordinates files (where each file corresponds to a "
+    "different OTU table) and use the standard deviation method to compute the "
+    "dimensions of the ellipsoids surrounding each sample: ", "%prog -i "
+    "unweighted_unifrac_pc -m Fasting_Map.txt -o jackknifed_pcoa -e sdev"),
+    ("Jackknifed PCoA plot with a master coordinates file", "Passing a master "
+    "coordinates file (--master_pcoa) will display the ellipsoids centered by "
+    "the samples in this file: ", "%prog -i unweighted_unifrac_pc -s "
+    "unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_5.txt -m "
+    "Fasting_Map.txt -o jackknifed_with_master"),
+    ("BiPlots","To see which taxa are the ten more prevalent in the different "
+    "areas of the PCoA plot, you need to pass a summarized taxa file i. e. the "
+    "output of summarize_taxa.py. Note that if the the '--taxa_fp' has fewer "
+    "than 10 taxa, the script will default to use all.","%prog -i unweighted_un"
+    "ifrac_pc.txt -m Fasting_Map.txt -t otu_table_L3.txt -o biplot"),
+    ("BiPlots with extra options","To see which are the three most prevalent "
+    "taxa and save the coordinates where these taxa are centered, you can use "
+    "the -n (number of taxa to keep) and the --biplot_fp (output biplot file "
+    "path) options.", "%prog -i unweighted_unifrac_pc.txt -m Fasting_Map.txt -t"
+    " otu_table_L3.txt -o biplot_options -n 3 --biplot_fp biplot.txt"),
+    ("Drawing connecting lines between samples", "To draw lines betwen samples"
+    " within a category use the '--add_vectors' option. For example to connect "
+    "the lines by the 'Treatment' category.", "%prog -i unweighted_unifrac_pc."
+    "txt -m Fasting_Map.txt -o vectors --add_vectors Treatment"),
+    ("Drawing connecting lines between samples with an explicit axis", "To draw"
+    " lines between samples within a category of the mapping file and have them"
+    " sorted by a category that's explicitly represented in the 3D plot use the"
+    " '--add_vectors' and the '-a' option.", "%prog -i unweighted_unifrac_pc."
+    "txt -m Fasting_Map.txt --add_vectors Treatment,DOB -a DOB -o "
+    "sorted_by_DOB"),
+    ("Compare two coordinate files", "To draw replicates of the same samples "
+    "like for a procustes plot.", "%prog -i compare -m Fasting_Map.txt "
+    "--compare_plots -o comparison")
+    ]
+script_info['output_description']= "This script creates an output directory "+\
+    "with an HTML formated file named 'index.html' and a complementary "+\
+    "folder named 'emperor_required_resources'. Opening index.html with "+\
+    "Google's Chrome web browser will display a three dimensional "+\
+    "visualization of the processed PCoA data file and the corresponding "+\
+    "metadata mapping file."
+script_info['required_options'] = [
+    make_option('-i','--input_coords',type="existing_path",help='Depending on '
+    'the plot to be generated, can be one of the following: (1) Filepath of '
+    'a coordinates file to create a PCoA plot. (2) Directory path to a folder '
+    'containing coordinates files to create a jackknifed PCoA plot. (3) '
+    'Directory path to a folder containing coordinates files to compare the '
+    'coordinates there contained when --compare_plots is enabled (useful '
+    'for procustes analysis plots). For directories: hidden files, sub-'
+    'directories and files suffixed as \'_procrustes_results.txt\''),
+    make_option('-m','--map_fp',type="existing_filepath",help='path to a '
+    'metadata mapping file')
+]
+script_info['optional_options'] = [
+    make_option('--number_of_axes', type=int, help='Number of axes to be '
+    'incorporated in the plot. Only 3 will be displayed at any given time but '
+    'this option modifies how many axes you can use for your visualization. '
+    'Note that Emperor will only use the axes that explain more than 0.5% (this'
+    ' will be shown as 1% in the GUI)of the variability [default: %default]',
+    default=10),
+    make_option('-a', '--custom_axes', type='string', help='Comma-separated '
+    'list of metadata categories to use as custom axes in the plot. For '
+    'instance, if there is a time category and you would like to see the '
+    'samples plotted on that axis instead of PC1, PC2, etc., you would pass '
+    'time as the value of this option.  Note: if there is any non-numeric data '
+    'in the metadata column, an error will be presented [default: %default]',
+    default=None),
+    make_option('--add_unique_columns',action="store_true",help='Add to the '
+    'output categories of the mapping file the columns where all values are '
+    'different. Note: if the result of one of the concatenated fields in '
+    '--color_by is a column where all values are unique, the resulting column '
+    'will get removed as well [default: %default]', default=False),
+    make_option('--add_vectors', type='string', dest='add_vectors',
+    help='Comma-sparated category(ies) used to add connecting lines (vectors) '
+    'between samples. The first category specifies the samples that will be '
+    'connected by the vectors, whilst the second category (optionally) '
+    'determines the order in which the samples will be connected. [default: '
+    '%default]', default=[None, None]),
+    make_option('-b', '--color_by', dest='color_by', type='string', help=
+    'Comma-separated list of metadata categories (column headers) to color by'
+    ' in the plots. The categories must match the name of a column header in '
+    'the mapping file exactly. Multiple categories can be listed by comma '
+    'separating them without spaces. The user can also combine columns in'
+    ' the mapping file by separating the categories by "&&" without spaces. '
+    '[default=color by all categories except ones where all values are '
+    'different]', default=''),
+    make_option('--biplot_fp', help='Output filepath that will contain the '
+    'coordinates where each taxonomic sphere is centered. [default: %default]',
+    default=None, type='new_filepath'),
+    make_option('-c', '--compare_plots', dest='compare_plots',
+    action='store_true', default=False, help='Passing a directory with the -i '
+    '(--input_coords) option in combination with this flag results in a set of'
+    ' bars connecting the replicated samples across all the input files. '
+    '[default=%default]'),
+    make_option('-e', '--ellipsoid_method', help='Used only when plotting '
+    'ellipsoids for jackknifed beta diversity (i.e. using a directory of coord '
+    'files instead of a single coord file). Valid values are "IQR" (for '
+    'inter-quartile ranges) and "sdev" (for standard deviation). '
+    '[default=%default]', default='IQR', type='choice', choices=['IQR','sdev']),
+     make_option('--ignore_missing_samples', help='This will overpass the error'
+    ' raised when the coordinates file contains samples that are not present in'
+    ' the mapping file. Be aware that this is very misleading as the PCoA is '
+    'accounting for all the samples and removing some samples could lead to '
+    ' erroneous/skewed interpretations.', action='store_true', default=False),
+    make_option('-n', '--n_taxa_to_keep', help='Number of taxonomic groups from'
+    ' the "--taxa_fp" file to display. Passing "-1" will cause to display all '
+    'the taxonomic groups, this option is only used when creating BiPlots. '
+    '[default=%default]', default=10, type='int'),
+    make_option('-s', '--master_pcoa', help='Used only when the input is a '
+    'directory of coordinate files i. e. for jackknifed beta diversity plot or'
+    ' for a coordinate comparison plot (procrustes analysis). The coordinates '
+    'in this file will be the center of each ellipsoid in the case of a '
+    'jackknifed PCoA plot or the center where the connecting arrows originate '
+    'from for a comparison plot. [default: arbitrarily selected file from the '
+    'input directory for a jackknifed plot or None for a comparison plot in '
+    'this case one file will be connected to the next one and so on]',
+    default=None, type='existing_filepath'),
+    make_option('-t', '--taxa_fp', help='Path to a summarized taxa file (i. '
+    'e. the output of summarize_taxa.py). This option is only used when '
+    'creating BiPlots. [default=%default]', default=None, type=
+    'existing_filepath'),
+    make_option('-x', '--missing_custom_axes_values', help='Option to override '
+    'the error shown when the catergory used in \'--custom_axes\' has '
+    'non-numeric values in the mapping file. The basic format is '
+    'custom_axis:new_value. For example, if you want to plot in time 0 all the '
+    'samples that do not have a numeric value in the column Time. you would '
+    'pass -x "Time:0". Additionally, you can pass this format '
+    'custom_axis:other_column==value_in_other_column=new_value, with this '
+    'format you can specify different values (new_value) to use in the '
+    'substitution based on other column (other_column) value '
+    '(value_in_other_column); see example above. This option could be used in '
+    'all explicit axes.',action='append', default=None),
+    make_option('-o','--output_dir',type="new_dirpath", help='path to the '
+    'output directory that will contain the PCoA plot. [default: %default]',
+    default='emperor'),
+    make_option('--number_of_segments', type="int", help='the number of '
+    'segments to generate any spheres, this includes the samples, the taxa '
+    '(biplots), and the confidence intervals (jackknifing). Higher values will '
+    'result in better quality but can make the plots less responsive, also it '
+    'will make the resulting SVG images bigger. The value should be between 4 '
+    'and 14. [default: %default]', default=8),
+]
+script_info['version'] = __version__
+
+
+def main():
+    option_parser, opts, args = parse_command_line_parameters(**script_info)
+    input_coords = opts.input_coords
+    map_fp = opts.map_fp
+    output_dir = opts.output_dir
+    color_by_column_names = opts.color_by
+    add_unique_columns = opts.add_unique_columns
+    custom_axes = opts.custom_axes
+    ignore_missing_samples = opts.ignore_missing_samples
+    missing_custom_axes_values = opts.missing_custom_axes_values
+    jackknifing_method = opts.ellipsoid_method
+    master_pcoa = opts.master_pcoa
+    taxa_fp = opts.taxa_fp
+    n_taxa_to_keep = opts.n_taxa_to_keep
+    biplot_fp = opts.biplot_fp
+    add_vectors = opts.add_vectors
+    verbose_output = opts.verbose
+    number_of_axes = opts.number_of_axes
+    compare_plots = opts.compare_plots
+    number_of_segments = opts.number_of_segments
+
+    # add some metadata to the output
+    emperor_autograph = format_emperor_autograph(map_fp, input_coords, 'HTML')
+
+    # verifying that the number of axes requested is greater than 3
+    if number_of_axes<3:
+        option_parser.error(('You need to plot at least 3 axes.'))
+        
+    # verifying that the number of segments is between the desired range
+    if number_of_segments<4 or number_of_segments>14:
+        option_parser.error(('number_of_segments should be between 4 and 14.'))
+        
+    # append headernames that the script didn't find in the mapping file
+    # according to different criteria to the following variables
+    offending_fields = []
+    non_numeric_categories = []
+
+    serial_comparison = True
+
+    # can't do averaged pcoa plots _and_ custom axes in the same plot
+    if custom_axes!=None and len(custom_axes.split(','))>1 and\
+        isdir(input_coords):
+        option_parser.error(('Jackknifed plots are limited to one custom axis, '
+            'currently trying to use: %s. Make sure you use only one.' %
+            custom_axes))
+
+    # make sure the flag is not misunderstood from the command line interface
+    if isdir(input_coords) == False and compare_plots:
+        option_parser.error('Cannot use the \'--compare_plots\' flag unless the'
+            ' input path is a directory.')
+
+    # before creating any output, check correct parsing of the main input files
+    try:
+        mapping_data, header, comments = parse_mapping_file(open(map_fp,'U'))
+
+        # use this set variable to make presence/absensce checks faster
+        lookup_header = set(header)
+    except:
+        option_parser.error(('The metadata mapping file \'%s\' does not seem '
+            'to be formatted correctly, verify the formatting is QIIME '
+            'compliant by using check_id_map.py') % map_fp)
+
+    # dir means jackknifing or coordinate comparison type of processing
+    if isdir(input_coords):
+        offending_coords_fp = []
+        coords_headers, coords_data, coords_eigenvalues, coords_pct=[],[],[],[]
+
+        # iterate only over the non-hidden files and not folders and if anything
+        # ignore the procrustes results file that is generated by
+        # transform_coordinate_matrices.py suffixed in procrustes_results.txt
+        coord_fps = [join(input_coords, f) for f in listdir(input_coords) if
+            not f.startswith('.') and not isdir(join(abspath(input_coords),f))
+            and not f.endswith('procrustes_results.txt')]
+
+        # this could happen and we rather avoid this problem
+        if len(coord_fps) == 0:
+            option_parser.error('Could not use any of the files in the input '
+                'directory.')
+
+        # the master pcoa must be the first in the list of coordinates; however
+        # if the visualization is not a jackknifed plot this gets ignored
+        if master_pcoa and compare_plots == False:
+            if master_pcoa in coord_fps: # remove it if duplicated
+                coord_fps.remove(master_pcoa)
+            coord_fps = [master_pcoa] + coord_fps # prepend it to the list
+        # passing a master file means that the comparison is not serial
+        elif master_pcoa and compare_plots:
+            serial_comparison = False
+
+            # guarantee that the master is the first and is not repeated
+            if master_pcoa in  coord_fps:
+                coord_fps.remove(master_pcoa)
+                coord_fps = [master_pcoa] + sort_comparison_filenames(coord_fps)
+
+        # QIIME generates folders of transformed coordinates for the specific
+        # purpose of connecting all coordinates to a set of origin coordinates.
+        # The name of this file is suffixed as _transformed_reference.txt
+        elif master_pcoa == None and len([f for f in coord_fps if f.endswith(
+            '_transformed_reference.txt')]):
+            master_pcoa = [f for f in coord_fps if f.endswith(
+                '_transformed_reference.txt')][0]
+            serial_comparison = False
+
+            # Note: the following steps are to guarantee consistency.
+            # remove the master from the list and re-add it as a first element
+            # the rest of the files must be sorted alphabetically so the result
+            # will be: ['unifrac_transformed_reference.txt',
+            # 'unifrac_transformed_q1.txt', 'unifrac_transformed_q2.txt'] etc
+            coord_fps.remove(master_pcoa)
+            coord_fps = [master_pcoa] + sort_comparison_filenames(coord_fps)
+
+        for fp in coord_fps:
+            try:
+                _coords_headers, _coords_data, _coords_eigenvalues,_coords_pct=\
+                    parse_coords(open(fp,'U'))
+
+                # pack all the data correspondingly
+                coords_headers.append(_coords_headers)
+                coords_data.append(_coords_data)
+                coords_eigenvalues.append(_coords_eigenvalues)
+                coords_pct.append(_coords_pct)
+            except ValueError:
+                offending_coords_fp.append(fp)
+
+        # in case there were files that couldn't be parsed
+        if offending_coords_fp:
+            option_parser.error(('The following file(s): \'%s\' could not be '
+                'parsed properly. Make sure the input folder only contains '
+                'coordinates files.') % ', '.join(offending_coords_fp))
+
+        # check all files contain the same sample identifiers by flattening the
+        # list of available sample ids and returning the sample ids that are
+        # in one of the sets of sample ids but not in the globablly shared ids
+        non_shared_ids = set(sum([list(set(sum(coords_headers, []))^set(e))
+            for e in coords_headers],[]))
+        if non_shared_ids and len(coords_headers) > 1:
+            option_parser.error(('The following sample identifier(s): \'%s\''
+                'are not shared between all the files. The files used to '
+                'make a jackknifed PCoA plot or coordinate comparison plot ('
+                'procustes plot) must share all the same sample identifiers'
+                'between each other.')%', '.join(list(non_shared_ids)))
+
+        # flatten the list of lists into a 1-d list
+        _coords_headers = list(set(sum(coords_headers, [])))
+
+        # number of samples ids that are shared between coords and mapping files
+        sids_intersection=list(set(zip(*mapping_data)[0])&set(_coords_headers))
+
+        # sample ids that are not mapped but are in the coords
+        sids_difference=list(set(_coords_headers)-set(zip(*mapping_data)[0]))
+
+        # used to perform different validations in the script, very similar for
+        # the case where the input is not a directory
+        number_intersected_sids = len(sids_intersection)
+        required_number_of_sids = len(coords_headers[0])
+
+    else:
+        try:
+            coords_headers, coords_data, coords_eigenvalues, coords_pct =\
+                parse_coords(open(input_coords,'U'))
+        # this exception was noticed when there were letters in the coords file
+        # other exeptions should be catched here; code will be updated then
+        except ValueError:
+            option_parser.error(('The PCoA file \'%s\' does not seem to be a '
+                'coordinates formatted file, verify by manually inspecting '
+                'the contents.') % input_coords)
+
+        # number of samples ids that are shared between coords and mapping files
+        sids_intersection = list(set(zip(*mapping_data)[0])&set(coords_headers))
+        # sample ids that are not mapped but are in the coords
+        sids_difference = list(set(coords_headers)-set(zip(*mapping_data)[0]))
+        number_intersected_sids = len(sids_intersection)
+        required_number_of_sids = len(coords_headers)
+
+    if taxa_fp:
+        try:
+            # for summarized tables the "otu_ids" are really the "lineages"
+            otu_sample_ids, lineages, otu_table, _ = parse_otu_table(open(
+                taxa_fp, 'U'), count_map_f=float, remove_empty_rows=True)
+        except ValueError, e:
+            option_parser.error('There was a problem parsing the --taxa_fp: %s'%
+                e.message)
+
+        # make sure there are matching sample ids with the otu table
+        if not len(list(set(sids_intersection)&set(otu_sample_ids))):
+            option_parser.error('The sample identifiers in the OTU table must '
+                'have at least one match with the data in the mapping file and '
+                'with the coordinates file. Verify you are using input files '
+                'that belong to the same dataset.')
+        if len(lineages) <= 1:
+            option_parser.error('Contingency tables with one or fewer rows are '
+                'not supported, please try passing a contingency table with '
+                'more than one row.')
+    else:
+        # empty lists indicate that there was no taxa file passed in
+        otu_sample_ids, lineages, otu_table = [], [], []
+
+    # sample ids must be shared between files
+    if number_intersected_sids <= 0:
+        option_parser.error('The sample identifiers in the coordinates file '
+            'must have at least one match with the data contained in mapping '
+            'file. Verify you are using a coordinates file and a mapping file '
+            'that belong to the same dataset.')
+
+    # the intersection of the sample ids in the coords and the sample ids in the
+    # mapping file must at the very least include all ids in the coords file
+    # Otherwise it isn't valid; unless --ignore_missing_samples is set True
+    if number_intersected_sids != required_number_of_sids and\
+        not ignore_missing_samples:
+        message = 'The metadata mapping file has fewer sample identifiers '+\
+            'than the coordinates file. Verify you are using a mapping file '+\
+            'that contains at least all the samples contained in the '+\
+            'coordinates file(s). You can force the script to ignore these '+\
+            'samples by passing the \'--ignore_missing_samples\' flag.'
+
+        if verbose_output:
+            message += ' Offending sample identifier(s): %s.' %\
+                ', '.join(sids_difference)
+            print sids_difference
+
+        option_parser.error(message)
+
+    if number_intersected_sids != required_number_of_sids and\
+        ignore_missing_samples:
+        # keep only the samples that are mapped in the mapping file
+        coords_headers, coords_data = keep_samples_from_pcoa_data(
+            coords_headers, coords_data, sids_intersection)
+
+    # ignore samples that exist in the coords but not in the mapping file, note:
+    # we're using sids_intersection so if --ignore_missing_samples is enabled we
+    # account for unmapped coords, else the program will exit before this point
+    header, mapping_data = filter_mapping_file(mapping_data, header,
+        sids_intersection, include_repeat_cols=True)
+
+    # catch the errors that could occur when filling the mapping file values
+    if missing_custom_axes_values:
+        try:
+            # the fact that this uses parse_metadata_state_descriptions makes
+            # the following option '-x Category:7;PH:12' to work as well as the 
+            # script-interface-documented '-x Category:7 -x PH:12' option
+            for val in missing_custom_axes_values:
+                if ':' not in val:
+                    option_parser.error("Not valid missing value for custom "
+                        "axes: %s" % val)
+            mapping_data = fill_mapping_field_from_mapping_file(mapping_data,
+                header, ';'.join(missing_custom_axes_values))
+            
+        except AssertionError, e:
+            option_parser.error(e.message)
+        except EmperorInputFilesError, e:
+            option_parser.error(e.message)
+    
+    # check that all the required columns exist in the metadata mapping file
+    if color_by_column_names:
+        color_by_column_names = color_by_column_names.split(',')
+
+        # check for all the mapping fields
+        for col in color_by_column_names:
+            # for concatenated columns check each individual field
+            if '&&' in col:
+                for _col in col.split('&&'):
+                    if _col not in lookup_header:
+                        offending_fields.append(col)
+            elif col not in lookup_header:
+                offending_fields.append(col)
+    else:
+        # if the user didn't specify the header names display everything
+        color_by_column_names = header[:]
+
+    # extract a list of the custom axes provided and each element is numeric
+    if custom_axes:
+        custom_axes = custom_axes.strip().strip("'").strip('"').split(',')
+
+        # the MetadataMap object makes some checks easier
+        map_object = MetadataMap(mapping_file_to_dict(mapping_data, header), [])
+        for axis in custom_axes:
+            # append the field to the error queue that it belongs to
+            if axis not in lookup_header:
+                offending_fields.append(axis)
+                break
+            # make sure this value is in the mapping file
+            elif axis not in color_by_column_names:
+                color_by_column_names.append(axis)
+        # perform only if the for loop does not call break
+        else:
+            # make sure all these axes are numeric
+            for axis in custom_axes:
+                if map_object.isNumericCategory(axis) == False:
+                    non_numeric_categories.append(axis)
+
+    # make multiple checks for the add_vectors option
+    if add_vectors != [None, None]:
+        add_vectors = add_vectors.split(',')
+        # check there are at the most two categories specified for this option
+        if len(add_vectors) > 2:
+            option_parser.error("The '--add_vectors' option can accept up to "
+                "two different fields from the mapping file; currently trying "
+                "to use %d (%s)." % (len(add_vectors), ', '.join(add_vectors)))
+        # make sure the field(s) exist
+        for col in add_vectors:
+            # concatenated fields are allowed now so check for each field
+            if '&&' in col:
+                for _col in col.split('&&'):
+                    if _col not in lookup_header:
+                        offending_fields.append(col)
+                        break
+                # only execute this block of code if all checked fields exist
+                else:
+                    # make sure that if it's going to be used for vector
+                    # creation it gets used for coloring and map postprocessing
+                    if col not in color_by_column_names:
+                        color_by_column_names.append(col)
+            # if it's a column without concatenations
+            elif col not in lookup_header:
+                offending_fields.append(col)
+                break
+            else:
+                # check this vector value is in the color by category
+                if col not in color_by_column_names:
+                    color_by_column_names.append(col)
+        # perform only if the for loop does not call break
+        else:
+            # check that the second category is all with numeric values
+            if len(add_vectors) == 2:
+                map_object = MetadataMap(mapping_file_to_dict(mapping_data,
+                    header), [])
+                # if it has non-numeric values add it to the list of offenders
+                if map_object.isNumericCategory(add_vectors[1]) == False:
+                    non_numeric_categories.append(add_vectors[1]+' (used in '
+                        '--add_vectors)')
+            else:
+                add_vectors.append(None)
+
+    # terminate the program for the cases where a mapping field was not found
+    # or when a mapping field didn't meet the criteria of being numeric
+    if offending_fields:
+        option_parser.error("Invalid field(s) '%s'; the valid field(s) are:"
+            " '%s'" % (', '.join(offending_fields), ', '.join(header)))
+    if non_numeric_categories:
+        option_parser.error(('The following field(s): \'%s\' contain values '
+            'that are not numeric, hence not suitable for \'--custom_axes\' nor'
+            ' for \'--add_vectors\'. Try the \'--missing_custom_axes_values\' '
+            'option to fix these values.' % ', '.join(non_numeric_categories)))
+
+    # process the coordinates file first, preventing the case where the custom
+    # axes is not in the coloring categories i. e. in the --colory_by categories
+    coords_headers, coords_data, coords_eigenvalues, coords_pct, coords_low,\
+        coords_high, clones = preprocess_coords_file(coords_headers,coords_data,
+        coords_eigenvalues, coords_pct, header, mapping_data, custom_axes,
+        jackknifing_method=jackknifing_method, is_comparison=compare_plots)
+
+    # process the otu table after processing the coordinates to get custom axes
+    # (when available) or any other change that occurred to the coordinates
+    otu_coords, otu_table, otu_lineages, otu_prevalence, lines =\
+        preprocess_otu_table(otu_sample_ids, otu_table, lineages,
+        coords_data, coords_headers, n_taxa_to_keep)
+
+    # remove the columns in the mapping file that are not informative taking
+    # into account the header names that were already authorized to be used
+    # and take care of concatenating the fields for the && merged columns
+    mapping_data, header = preprocess_mapping_file(mapping_data, header,
+        color_by_column_names, unique=not add_unique_columns, clones=clones)
+
+    # create the output directory before creating any other output
+    if not isdir(opts.output_dir):
+        makedirs(opts.output_dir)
+
+    fp_out = open(join(output_dir, 'index.html'),'w')
+    fp_out.write(emperor_autograph+'\n')
+    fp_out.write(EMPEROR_HEADER_HTML_STRING)
+
+    # write the html file
+    fp_out.write(format_mapping_file_to_js(mapping_data, header, header))
+
+    # certain percents being explained cannot be displayed in the GUI
+    try:
+        fp_out.write(format_pcoa_to_js(coords_headers, coords_data,
+            coords_eigenvalues, coords_pct, custom_axes, coords_low,
+            coords_high, number_of_axes=number_of_axes, 
+            number_of_segments=number_of_segments))
+    except EmperorLogicError, e:
+        option_parser.error(e.message)
+
+    fp_out.write(format_taxa_to_js(otu_coords, otu_lineages, otu_prevalence))
+    fp_out.write(format_vectors_to_js(mapping_data, header, coords_data,
+        coords_headers, add_vectors[0], add_vectors[1]))
+    fp_out.write(format_comparison_bars_to_js(coords_data, coords_headers,
+        clones, is_serial_comparison=serial_comparison))
+    fp_out.write(format_emperor_html_footer_string(taxa_fp != None,
+        isdir(input_coords) and not compare_plots, add_vectors != [None, None],
+        clones>0))
+    fp_out.close()
+    copy_support_files(output_dir)
+
+    # write the biplot coords in the output file if a path is passed
+    if biplot_fp and taxa_fp:
+        # make sure this file can be created
+        try:
+            fd = open(biplot_fp, 'w')
+        except IOError:
+            option_parser.error('There was a problem creating the file with'
+                ' the coordinates for the biplots (%s).' % biplot_fp)
+        fd.writelines(lines)
+        fd.close()
+
+if __name__ == "__main__":
+    main()
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..b8c7648
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+
+from distutils.core import setup
+from glob import glob
+
+__author__ = "Yoshiki Vazquez Baeza"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Antonio Gonzalez Pena", "Meg Pirrung", "Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+# based on the text found in github.com/qiime/pynast
+classes = """
+    Development Status :: 4 - Beta
+    License :: OSI Approved :: BSD License
+    Topic :: Software Development :: Libraries :: Application Frameworks
+    Topic :: Software Development :: User Interfaces
+    Programming Language :: Python
+    Programming Language :: Python :: 2.7
+    Programming Language :: Python :: Implementation :: CPython
+    Operating System :: OS Independent
+    Operating System :: POSIX
+    Operating System :: MacOS :: MacOS X
+"""
+
+classifiers = [s.strip() for s in classes.split('\n') if s]
+
+long_description = """Emperor: a tool for visualizing high-throughput microbial community data
+
+EMPeror: a tool for visualizing high-throughput microbial community data.
+Vazquez-Baeza Y, Pirrung M, Gonzalez A, Knight R.
+Gigascience. 2013 Nov 26;2(1):16.
+"""
+
+setup(name='emperor',
+        version=__version__,
+        description='Emperor',
+        author="Antonio Gonzalez Pena, Meg Pirrung & Yoshiki Vazquez Baeza",
+        author_email=__email__,
+        maintainer=__maintainer__,
+        maintainer_email=__email__,
+        url='http://github.com/qiime/emperor',
+        packages=['emperor', 'emperor/pycogent_backports',
+            'emperor/qiime_backports'],
+        scripts=glob('scripts/*py'),
+        package_data={'emperor':['support_files/js/*.js',
+            'support_files/js/js/*.js', 'support_files/js/js/ctm/*.js',
+            'support_files/js/js/ctm/license/*.txt',
+            'support_files/js/js/postprocessing/*.js',
+            'support_files/img/*.png', 'support_files/img/*.ico',
+            'support_files/css/*.css', 'support_files/css/images/*.png',
+            'support_files/emperor/css/*.css',
+            'support_files/emperor/js/*.js',]},
+        data_files={},
+        install_requires=["numpy >= 1.5.1, <=1.7.1", "qcli"],
+        long_description=long_description,
+        classifiers=classifiers)
+
diff --git a/tests/all_tests.py b/tests/all_tests.py
new file mode 100755
index 0000000..20b1197
--- /dev/null
+++ b/tests/all_tests.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+"""Run all tests.
+"""
+#Originally based on the all_tests.py script from the QIIME project
+#(http://github.com/qiime/qiime) project at svn revision 3290, now taken from
+#the E-vident (http://github.com/qiime/evident) project master branch at git SHA
+#dde2a06f2d990db8b09da65764cd27fc047db788
+
+import re
+
+from os import walk
+from sys import exit
+from glob import glob
+from os.path import join, abspath, dirname, split, exists
+
+from emperor.util import get_emperor_project_dir
+
+from qcli.util import qcli_system_call
+from qcli.test import run_script_usage_tests
+from qcli.option_parsing import parse_command_line_parameters, make_option
+
+__author__ = "Rob Knight"
+__copyright__ = "Copyright 2013, The Emperor Project" #consider project name
+__credits__ = ["Rob Knight","Greg Caporaso", "Jai Ram Rideout",
+    "Yoshiki Vazquez-Baeza"] #remember to add yourself if you make changes
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez-Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+
+script_info = {}
+script_info['brief_description'] = ""
+script_info['script_description'] = ""
+script_info['script_usage'] = [("","","")]
+script_info['output_description']= ""
+script_info['required_options'] = []
+script_info['optional_options'] = [
+    make_option('--suppress_unit_tests', action='store_true', help='suppress '
+    ' execution of Emperor\'s unit tests [default: %default]', default=False),
+    make_option('--suppress_script_usage_tests', action='store_true', help=
+    'suppress Emperor\'s script usage tests [default: %default]',default=False),
+    make_option('--unittest_glob', help='wildcard pattern to match the unit '
+    'tests to execute [default: %default]', default=None),
+    make_option('--script_usage_tests', help='comma-separated list of the '
+    'script usage tests to execute [default: run all]', default=None),
+    make_option('-p', '--temp_filepath', type='existing_path', help='temporary '
+    'directory where the script usage tests will be executed', default='/tmp/'),
+    make_option('--emperor_scripts_dir', help='filepath where the scripts are'
+    ' stored', type='existing_path', default=None)
+]
+script_info['version'] = __version__
+script_info['help_on_no_arguments'] = False
+
+
+def main():
+    option_parser, opts, args = parse_command_line_parameters(**script_info)
+
+    unittest_glob = opts.unittest_glob
+    temp_filepath = opts.temp_filepath
+    script_usage_tests = opts.script_usage_tests
+    suppress_unit_tests = opts.suppress_unit_tests
+    suppress_script_usage_tests = opts.suppress_script_usage_tests
+
+    # since the test data is in the tests folder just add scripts_test_data
+    emperor_test_data_dir = join(abspath(dirname(__file__)),
+        'scripts_test_data/')
+
+    # offer the option for the user to pass the scripts dir from the command
+    # line since there is no other way to get the scripts dir. If not provided
+    # the base structure of the repository will be assumed. Note that for both
+    # cases we are using absolute paths, to avoid unwanted failures.
+    if opts.emperor_scripts_dir is None:
+        emperor_scripts_dir = abspath(join(get_emperor_project_dir(),
+            'scripts/'))
+
+        # let's try to guess cases for qiime-deploy type of installs
+        if get_emperor_project_dir().endswith('/lib'):
+            emperor_scripts_dir = abspath(join(get_emperor_project_dir()[:-3],
+                'scripts/'))
+
+    else:
+        emperor_scripts_dir = abspath(opts.emperor_scripts_dir)
+
+    # make a sanity check
+    if (suppress_unit_tests and suppress_script_usage_tests):
+        option_parser.error("All tests have been suppresed. Nothing to run.")
+
+    test_dir = abspath(dirname(__file__))
+
+    unittest_good_pattern = re.compile('OK\s*$')
+    application_not_found_pattern = re.compile('ApplicationNotFoundError')
+    python_name = 'python'
+    bad_tests = []
+    missing_application_tests = []
+
+    # Run through all of Emperor's unit tests, and keep track of any files which
+    # fail unit tests, note that these are the unit tests only
+    if not suppress_unit_tests:
+        unittest_names = []
+        if not unittest_glob:
+            for root, dirs, files in walk(test_dir):
+                for name in files:
+                    if name.startswith('test_') and name.endswith('.py'):
+                        unittest_names.append(join(root,name))
+        else:
+            for fp in glob(unittest_glob):
+                fn = split(fp)[1]
+                if fn.startswith('test_') and fn.endswith('.py'):
+                    unittest_names.append(abspath(fp))
+
+        unittest_names.sort()
+
+        for unittest_name in unittest_names:
+            print "Testing %s:\n" % unittest_name
+            command = '%s %s -v' % (python_name, unittest_name)
+            stdout, stderr, return_value = qcli_system_call(command)
+            print stderr
+            if not unittest_good_pattern.search(stderr):
+                if application_not_found_pattern.search(stderr):
+                    missing_application_tests.append(unittest_name)
+                else:
+                    bad_tests.append(unittest_name)
+
+    script_usage_failures = 0
+
+    # choose to run some of the script usage tests or all the available ones
+    if not suppress_script_usage_tests and exists(emperor_test_data_dir) and\
+        exists(emperor_scripts_dir):
+        if script_usage_tests != None:
+            script_tests = script_usage_tests.split(',')
+        else:
+            script_tests = None
+        # Run the script usage testing functionality; note that depending on the
+        # module where this was imported, the name of the arguments will change
+        # that's the reason why I added the name of the arguments in here
+        script_usage_result_summary, script_usage_failures = \
+            run_script_usage_tests( emperor_test_data_dir,  # test_data_dir
+                                    emperor_scripts_dir,    # scripts_dir
+                                    temp_filepath,          # working_dir
+                                    True,                   # verbose
+                                    script_tests,           # tests
+                                    None,                   # failure_log_fp
+                                    False)                  # force_overwrite
+
+    print "==============\nResult summary\n=============="
+
+    if not suppress_unit_tests:
+        print "\nUnit test result summary\n------------------------\n"
+        if bad_tests:
+            print "\nFailed the following unit tests.\n%s" %'\n'.join(bad_tests)
+    
+        if missing_application_tests:
+            print "\nFailed the following unit tests, in part or whole due "+\
+                "to missing external applications.\nDepending on the Emperor "+\
+                "features you plan to use, this may not be critical.\n%s"\
+                % '\n'.join(missing_application_tests)
+        
+        if not(missing_application_tests or bad_tests):
+            print "\nAll unit tests passed.\n\n"
+
+    if not suppress_script_usage_tests:
+        if exists(emperor_test_data_dir) and exists(emperor_scripts_dir):
+            print "\nScript usage test result summary"+\
+                "\n------------------------------------\n"
+            print script_usage_result_summary
+        else:
+            print ("\nCould not run script usage tests.\nThe Emperor scripts "
+                "directory could not be automatically located, try supplying "
+                " it manually using the --emperor_scripts_dir option.")
+
+    # In case there were no failures of any type, exit with a return code of 0
+    return_code = 1
+    if (len(bad_tests) == 0 and len(missing_application_tests) == 0 and
+        script_usage_failures == 0):
+        return_code = 0
+
+    return return_code
+
+
+if __name__ == "__main__":
+    exit(main())
diff --git a/tests/scripts_test_data/make_emperor/Fasting_Map.txt b/tests/scripts_test_data/make_emperor/Fasting_Map.txt
new file mode 100644
index 0000000..7873ced
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/Fasting_Map.txt
@@ -0,0 +1,11 @@
+#SampleID	BarcodeSequence	LinkerPrimerSequence	Treatment	DOB	Description
+#Example mapping file for the QIIME analysis package.  These 9 samples are from a study of the effects of exercise and diet on mouse cardiac physiology (Crawford, et al, PNAS, 2009).
+PC.354	AGCACGAGCCTA	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._354
+PC.355	AACTCGTCGATG	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._355
+PC.356	ACAGACCACTCA	YATGCTGCCTCCCGTAGGAGT	Control	20061126	Control_mouse_I.D._356
+PC.481	ACCAGCGACTAG	YATGCTGCCTCCCGTAGGAGT	Control	20070314	Control_mouse_I.D._481
+PC.593	AGCAGCACTTGT	YATGCTGCCTCCCGTAGGAGT	Control	20071210	Control_mouse_I.D._593
+PC.607	AACTGTGCGTAC	YATGCTGCCTCCCGTAGGAGT	Fast	20071112	Fasting_mouse_I.D._607
+PC.634	ACAGAGTCGGCT	YATGCTGCCTCCCGTAGGAGT	Fast	20080116	Fasting_mouse_I.D._634
+PC.635	ACCGCAGAGTCA	YATGCTGCCTCCCGTAGGAGT	Fast	20080116	Fasting_mouse_I.D._635
+PC.636	ACGGTGAGTGTC	YATGCTGCCTCCCGTAGGAGT	Fast	20080116	Fasting_mouse_I.D._636
diff --git a/tests/scripts_test_data/make_emperor/Fasting_Map_modified.txt b/tests/scripts_test_data/make_emperor/Fasting_Map_modified.txt
new file mode 100644
index 0000000..aee6c01
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/Fasting_Map_modified.txt
@@ -0,0 +1,11 @@
+#SampleID	BarcodeSequence	LinkerPrimerSequence	Treatment	DOB	Description
+#Example mapping file for the QIIME analysis package.  These 9 samples are from a study of the effects of exercise and diet on mouse cardiac physiology (Crawford, et al, PNAS, 2009).
+PC.354	AGCACGAGCCTA	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._354
+PC.355	AACTCGTCGATG	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._355
+PC.356	ACAGACCACTCA	YATGCTGCCTCCCGTAGGAGT	Control	20061126	Control_mouse_I.D._356
+PC.481	ACCAGCGACTAG	YATGCTGCCTCCCGTAGGAGT	Control	20070314	Control_mouse_I.D._481
+PC.593	AGCAGCACTTGT	YATGCTGCCTCCCGTAGGAGT	Control	NA	Control_mouse_I.D._593
+PC.607	AACTGTGCGTAC	YATGCTGCCTCCCGTAGGAGT	Fast	20071112	Fasting_mouse_I.D._607
+PC.634	ACAGAGTCGGCT	YATGCTGCCTCCCGTAGGAGT	Fast	20080116	Fasting_mouse_I.D._634
+PC.635	ACCGCAGAGTCA	YATGCTGCCTCCCGTAGGAGT	Fast	20080116	Fasting_mouse_I.D._635
+PC.636	ACGGTGAGTGTC	YATGCTGCCTCCCGTAGGAGT	Fast	NA	Fasting_mouse_I.D._636
diff --git a/tests/scripts_test_data/make_emperor/biplot.txt b/tests/scripts_test_data/make_emperor/biplot.txt
new file mode 100644
index 0000000..9cda4b9
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/biplot.txt
@@ -0,0 +1,4 @@
+#Taxon	pc0	pc1	pc2	pc3	pc4	pc5	pc6	pc7	pc8
+Root;k__Bacteria;p__Firmicutes	0.0840155767161	-0.00823816376882	-0.0237652607133	-0.0246683610503	-0.0161996992959	-0.00519164089553	0.00523283639012	-0.00419865957436	-4.84141986706e-09
+Root;k__Bacteria;p__Bacteroidetes	-0.111274634594	-0.0209166829829	0.0260589784114	0.0328465662311	0.0153651318051	0.00344370141059	-0.0108953599091	0.00392235426624	-4.84141986706e-09
+Root;k__Bacteria;p__Tenericutes	0.0286906406139	0.201636329198	0.0718385470146	0.0192870226318	0.0521872985074	0.0191261795707	0.0154613880758	0.0208652785029	-4.84141986706e-09
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/compare/pcoa_unweighted_unifrac_rarefaction_110_0.txt b/tests/scripts_test_data/make_emperor/compare/pcoa_unweighted_unifrac_rarefaction_110_0.txt
new file mode 100644
index 0000000..810e2b4
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/compare/pcoa_unweighted_unifrac_rarefaction_110_0.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	0.281857587421	0.0507270541945	0.0374722443357	-0.0370074137223	0.15782793716	0.23359469867	0.174998809955	0.0219541318555	2.90129006273e-09
+PC.635	0.18215230676	0.132916775615	-0.100389688466	0.190011634826	-0.307283733566	0.00430749577241	0.0469580790424	-0.0617163043591	2.90129006273e-09
+PC.356	-0.239606359013	0.178116346653	-0.316300212034	-0.0116338896148	0.114589435481	0.0915396854904	-0.13794374686	-0.0311556901155	2.90129006273e-09
+PC.481	-0.00185204749533	0.000144820879621	0.193898131008	-0.287576892734	-0.136510560285	0.0663599958119	-0.122469896669	-0.112458070691	2.90129006273e-09
+PC.354	-0.26374234825	-0.0210295238149	0.0988255433483	0.0121138624232	-0.103635007298	0.0460046967376	0.00789775732032	0.278122708738	2.90129006273e-09
+PC.593	-0.230830555897	-0.223723283753	0.170329067036	0.238984412555	0.0849242959848	0.0470740492498	-0.019643537342	-0.135134205905	2.90129006273e-09
+PC.355	-0.196195568243	0.169658119414	0.0424034255545	-0.0848245709891	0.0670053766901	-0.230905170908	0.195959371579	-0.0577704185506	2.90129006273e-09
+PC.607	0.133124236385	-0.378152288866	-0.239139015591	-0.103687426251	-0.0141771679839	-0.0969160055476	0.0312930958801	0.0232026349583	2.90129006273e-09
+PC.634	0.335092748334	0.0913419796777	0.112900504808	0.0836202835077	0.137259423818	-0.161059445277	-0.177049932906	0.0749552140687	2.90129006273e-09
+
+
+eigvals	0.461382484898	0.282586294059	0.259635090345	0.202508723789	0.192582035266	0.160350654061	0.138031252593	0.123015943185	7.5757356253e-17
+% variation explained	25.3493979248	15.5259305472	14.2649394718	11.1262876043	10.5808928707	8.81002784101	7.58374941088	6.75877432927	4.1622806072e-15
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/compare/pcoa_unweighted_unifrac_rarefaction_110_1.txt b/tests/scripts_test_data/make_emperor/compare/pcoa_unweighted_unifrac_rarefaction_110_1.txt
new file mode 100644
index 0000000..50b2e0b
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/compare/pcoa_unweighted_unifrac_rarefaction_110_1.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.288198422836	-0.0394400561594	0.155709945044	0.110165008813	-0.074622228146	-0.05065773954	-0.217629816961	0.138926064051	-3.95896644411e-09
+PC.635	-0.241287260869	-0.0113094708189	-0.0710427892223	-0.256796747044	0.221116161933	0.0148414610436	-0.086024926776	-0.0794987090308	-3.95896644411e-09
+PC.356	0.192207680814	-0.321726747298	-0.128718397379	-0.0122628266319	-0.0944977974365	0.230759463288	-0.0469763035028	0.00172079771458	-3.95896644411e-09
+PC.481	0.0176612523534	0.00735659824953	-0.00875781460244	0.310873339977	0.232611629491	0.04047016844	0.0620835537987	-0.0467431680005	-3.95896644411e-09
+PC.354	0.309591634418	0.0532890136432	0.0605879085161	-0.116905391818	0.0952884677703	-0.0505700193742	0.0916760416469	0.229224682475	-3.95896644411e-09
+PC.593	0.274193203664	0.295804203663	0.203716993255	-0.0279513960593	-0.0782935419173	0.0749433377744	-0.0918945086138	-0.133359461171	-3.95896644411e-09
+PC.355	0.133038027649	-0.233356301889	0.0324518672876	-0.00492825007504	-0.0653959447949	-0.2815873084	0.0336704720139	-0.119216430311	-3.95896644411e-09
+PC.607	-0.0622832861598	0.2368583004	-0.396154332152	0.0382634195983	-0.119360328521	-0.0594795163388	0.00817199024745	0.0213023627761	-3.95896644411e-09
+PC.634	-0.334922829033	0.0125244602096	0.152206619253	-0.0404571567605	-0.116846418379	0.0812801531067	0.246923498147	-0.0123561385027	-3.95896644411e-09
+
+
+eigvals	0.483314104562	0.306299367707	0.272267531543	0.192446818524	0.164885211801	0.155283759113	0.139844959373	0.112956103924	-1.4106073775e-16
+% variation explained	26.4496618781	16.7624214416	14.9000082591	10.5317706051	9.02344471155	8.49799930298	7.65310148381	6.18159231783	7.71963570387e-15
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/makefolders.sh b/tests/scripts_test_data/make_emperor/makefolders.sh
new file mode 100644
index 0000000..8d4f184
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/makefolders.sh
@@ -0,0 +1,10 @@
+make_emperor.py -i unweighted_unifrac_pc.txt -m Fasting_Map.txt -o emperor_output
+make_emperor.py -i unweighted_unifrac_pc.txt -m Fasting_Map.txt -b 'Treatment&&DOB,Treatment' -o emperor_colored_by
+make_emperor.py -i unweighted_unifrac_pc.txt -m Fasting_Map.txt -a DOB -o pcoa_dob
+make_emperor.py -i unweighted_unifrac_pc.txt -m Fasting_Map_modified.txt -a DOB -o pcoa_dob_with_missing_custom_axes_values -x 'DOB:20060000'
+make_emperor.py -i unweighted_unifrac_pc -m Fasting_Map.txt -o jackknifed_pcoa -e sdev
+make_emperor.py -i unweighted_unifrac_pc -s unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_5.txt -m Fasting_Map.txt -o jackknifed_with_master
+make_emperor.py -i unweighted_unifrac_pc.txt -m Fasting_Map.txt -t otu_table_L3.txt -o biplot
+make_emperor.py -i unweighted_unifrac_pc.txt -m Fasting_Map.txt -t otu_table_L3.txt -o biplot_options -n 3 --biplot_fp biplot.txt
+make_emperor.py -i unweighted_unifrac_pc.txt -m Fasting_Map.txt -o vectors --add_vectors Treatment
+make_emperor.py -i unweighted_unifrac_pc.txt -m Fasting_Map.txt --add_vectors Treatment,DOB -a DOB -o sorted_by_DOB
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/otu_table_L3.txt b/tests/scripts_test_data/make_emperor/otu_table_L3.txt
new file mode 100644
index 0000000..2e40779
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/otu_table_L3.txt
@@ -0,0 +1,9 @@
+Taxon	PC.636	PC.635	PC.356	PC.481	PC.354	PC.593	PC.355	PC.607	PC.634
+Root;k__Bacteria;Other	0.0202702702703	0.0469798657718	0.0266666666667	0.027397260274	0.0134228187919	0.0134228187919	0.0136054421769	0.0469798657718	0.02
+Root;k__Bacteria;p__Actinobacteria	0.00675675675676	0.00671140939597	0.0	0.00684931506849	0.0	0.0	0.0	0.0201342281879	0.02
+Root;k__Bacteria;p__Bacteroidetes	0.695945945946	0.523489932886	0.193333333333	0.143835616438	0.0738255033557	0.389261744966	0.285714285714	0.275167785235	0.653333333333
+Root;k__Bacteria;p__Deferribacteres	0.0472972972973	0.0134228187919	0.0	0.0	0.0	0.0	0.0	0.0201342281879	0.0333333333333
+Root;k__Bacteria;p__Firmicutes	0.209459459459	0.395973154362	0.773333333333	0.787671232877	0.89932885906	0.41610738255	0.700680272109	0.456375838926	0.22
+Root;k__Bacteria;p__Proteobacteria	0.00675675675676	0.00671140939597	0.0	0.0	0.0	0.0335570469799	0.0	0.0201342281879	0.0133333333333
+Root;k__Bacteria;p__TM7	0.0	0.0	0.0	0.0	0.0	0.0	0.0	0.0	0.0133333333333
+Root;k__Bacteria;p__Tenericutes	0.0135135135135	0.00671140939597	0.00666666666667	0.0342465753425	0.0134228187919	0.147651006711	0.0	0.161073825503	0.0266666666667
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc.txt
new file mode 100644
index 0000000..ce07a51
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.276542163845	-0.144964375408	0.0666467344429	-0.0677109454288	0.176070269506	0.072969390136	-0.229889463523	-0.0465989416581	-4.84141986706e-09
+PC.635	-0.237661393984	0.0460527772512	-0.138135814766	0.159061025229	-0.247484698646	-0.115211468101	-0.112864033263	0.0647940729676	-4.84141986706e-09
+PC.356	0.228820399536	-0.130142097093	-0.287149447883	0.0864498846421	0.0442951919304	0.20604260722	0.0310003571386	0.0719920436501	-4.84141986706e-09
+PC.481	0.0422628480532	-0.0139681511889	0.0635314615517	-0.346120552134	-0.127813807608	0.0139350721063	0.0300206887328	0.140147849223	-4.84141986706e-09
+PC.354	0.280399117569	-0.0060128286014	0.0234854344148	-0.0468109474823	-0.146624450094	0.00566979124596	-0.0354299634191	-0.255785794275	-4.84141986706e-09
+PC.593	0.232872767451	0.139788385269	0.322871079774	0.18334700682	0.0204661596818	0.0540589147147	-0.0366250872041	0.0998235721267	-4.84141986706e-09
+PC.355	0.170517581885	-0.194113268955	-0.0308965283066	0.0198086158783	0.155100062794	-0.279923941712	0.0576092515759	0.0242481862127	-4.84141986706e-09
+PC.607	-0.0913299284215	0.424147148265	-0.135627421345	-0.057519480907	0.151363490722	-0.0253935675552	0.0517306152066	-0.038738217609	-4.84141986706e-09
+PC.634	-0.349339228244	-0.120787589539	0.115274502117	0.0694953933826	-0.0253722182853	0.067853201946	0.244447634756	-0.0598827706386	-4.84141986706e-09
+
+
+eigvals	0.479412119045	0.29201495623	0.247449246064	0.201496072404	0.180076127632	0.147806772727	0.135795927213	0.112259695609	2.10954116963e-16
+% variation explained	26.6887048633	16.2563704022	13.7754129161	11.217215823	10.024774995	8.22835130237	7.55971173665	6.24945796136	1.17437418531e-14
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_0.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_0.txt
new file mode 100644
index 0000000..810e2b4
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_0.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	0.281857587421	0.0507270541945	0.0374722443357	-0.0370074137223	0.15782793716	0.23359469867	0.174998809955	0.0219541318555	2.90129006273e-09
+PC.635	0.18215230676	0.132916775615	-0.100389688466	0.190011634826	-0.307283733566	0.00430749577241	0.0469580790424	-0.0617163043591	2.90129006273e-09
+PC.356	-0.239606359013	0.178116346653	-0.316300212034	-0.0116338896148	0.114589435481	0.0915396854904	-0.13794374686	-0.0311556901155	2.90129006273e-09
+PC.481	-0.00185204749533	0.000144820879621	0.193898131008	-0.287576892734	-0.136510560285	0.0663599958119	-0.122469896669	-0.112458070691	2.90129006273e-09
+PC.354	-0.26374234825	-0.0210295238149	0.0988255433483	0.0121138624232	-0.103635007298	0.0460046967376	0.00789775732032	0.278122708738	2.90129006273e-09
+PC.593	-0.230830555897	-0.223723283753	0.170329067036	0.238984412555	0.0849242959848	0.0470740492498	-0.019643537342	-0.135134205905	2.90129006273e-09
+PC.355	-0.196195568243	0.169658119414	0.0424034255545	-0.0848245709891	0.0670053766901	-0.230905170908	0.195959371579	-0.0577704185506	2.90129006273e-09
+PC.607	0.133124236385	-0.378152288866	-0.239139015591	-0.103687426251	-0.0141771679839	-0.0969160055476	0.0312930958801	0.0232026349583	2.90129006273e-09
+PC.634	0.335092748334	0.0913419796777	0.112900504808	0.0836202835077	0.137259423818	-0.161059445277	-0.177049932906	0.0749552140687	2.90129006273e-09
+
+
+eigvals	0.461382484898	0.282586294059	0.259635090345	0.202508723789	0.192582035266	0.160350654061	0.138031252593	0.123015943185	7.5757356253e-17
+% variation explained	25.3493979248	15.5259305472	14.2649394718	11.1262876043	10.5808928707	8.81002784101	7.58374941088	6.75877432927	4.1622806072e-15
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_1.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_1.txt
new file mode 100644
index 0000000..50b2e0b
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_1.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.288198422836	-0.0394400561594	0.155709945044	0.110165008813	-0.074622228146	-0.05065773954	-0.217629816961	0.138926064051	-3.95896644411e-09
+PC.635	-0.241287260869	-0.0113094708189	-0.0710427892223	-0.256796747044	0.221116161933	0.0148414610436	-0.086024926776	-0.0794987090308	-3.95896644411e-09
+PC.356	0.192207680814	-0.321726747298	-0.128718397379	-0.0122628266319	-0.0944977974365	0.230759463288	-0.0469763035028	0.00172079771458	-3.95896644411e-09
+PC.481	0.0176612523534	0.00735659824953	-0.00875781460244	0.310873339977	0.232611629491	0.04047016844	0.0620835537987	-0.0467431680005	-3.95896644411e-09
+PC.354	0.309591634418	0.0532890136432	0.0605879085161	-0.116905391818	0.0952884677703	-0.0505700193742	0.0916760416469	0.229224682475	-3.95896644411e-09
+PC.593	0.274193203664	0.295804203663	0.203716993255	-0.0279513960593	-0.0782935419173	0.0749433377744	-0.0918945086138	-0.133359461171	-3.95896644411e-09
+PC.355	0.133038027649	-0.233356301889	0.0324518672876	-0.00492825007504	-0.0653959447949	-0.2815873084	0.0336704720139	-0.119216430311	-3.95896644411e-09
+PC.607	-0.0622832861598	0.2368583004	-0.396154332152	0.0382634195983	-0.119360328521	-0.0594795163388	0.00817199024745	0.0213023627761	-3.95896644411e-09
+PC.634	-0.334922829033	0.0125244602096	0.152206619253	-0.0404571567605	-0.116846418379	0.0812801531067	0.246923498147	-0.0123561385027	-3.95896644411e-09
+
+
+eigvals	0.483314104562	0.306299367707	0.272267531543	0.192446818524	0.164885211801	0.155283759113	0.139844959373	0.112956103924	-1.4106073775e-16
+% variation explained	26.4496618781	16.7624214416	14.9000082591	10.5317706051	9.02344471155	8.49799930298	7.65310148381	6.18159231783	7.71963570387e-15
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_2.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_2.txt
new file mode 100644
index 0000000..c0f39ab
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_2.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.291811048864	0.109699868326	-0.0858327621887	-0.0363345352382	-0.129522215769	0.0946765004148	0.18546581707	-0.161410801774	-3.81682081455e-09
+PC.635	-0.262457627274	-0.022626332813	0.0736327155898	0.099865206029	0.252989493775	0.194545268592	-0.0570948823666	0.0648783371866	-3.81682081455e-09
+PC.356	0.243530111276	0.18098079751	0.240516390145	0.111565470096	0.0291936951093	-0.0724851408509	0.180523264931	0.0791331945494	-3.81682081455e-09
+PC.481	0.0317283466647	0.023520525631	-0.108004427973	-0.340561640415	0.0143178896587	0.00500991913185	0.0297273010405	0.18028375502	-3.81682081455e-09
+PC.354	0.252356079994	-0.0295592806587	0.053965064225	-0.146470876747	0.163744711812	-0.0432902997015	-0.073942629745	-0.230567456995	-3.81682081455e-09
+PC.593	0.219781351954	-0.190123853643	-0.318779637872	0.19093778913	0.0404517558983	-0.0259224225036	0.0598160623192	0.025103879586	-3.81682081455e-09
+PC.355	0.165602711122	0.258948926594	-0.0361519732043	0.0746828273727	-0.187954959261	0.108232520138	-0.19916017374	0.0142934132673	-3.81682081455e-09
+PC.607	-0.0227688247286	-0.394436460907	0.198777377966	-0.00819196960445	-0.190306098497	0.0232182765318	-0.0299585405632	0.0151297883867	-3.81682081455e-09
+PC.634	-0.335961100144	0.0635958099611	-0.0181227466881	0.0545077293766	0.00708572727353	-0.283984621752	-0.0953762189457	0.0131558907721	-3.81682081455e-09
+
+
+eigvals	0.467151269331	0.309553202359	0.227982661044	0.206249025551	0.181879322705	0.147537350405	0.129834142405	0.123424786986	-1.31113090173e-16
+% variation explained	26.045283575	17.2586514611	12.7108143484	11.4990897172	10.1403953008	8.22571270052	7.23869820902	6.88135468802	7.31000392838e-15
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_3.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_3.txt
new file mode 100644
index 0000000..f17655b
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_3.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.259617725178	-0.127034975645	-0.0666808280154	0.127224251991	0.0270017842986	-0.121258111474	0.227939491925	0.125932662581	-5.12433159378e-09
+PC.635	-0.230580732193	0.0290048715434	0.170223356474	-0.193514286497	0.2390531539	0.130626842785	-0.0540311448301	0.0881623374927	-5.12433159378e-09
+PC.356	0.18593872779	-0.179339928765	0.295255813109	0.251307747921	-0.023167290942	0.0651066216606	-0.0501858865665	-0.0167258121957	-5.12433159378e-09
+PC.481	0.0594752775311	-0.0254891827269	-0.170501936086	-0.0432672988769	-0.249283817422	0.181237574696	-0.0584663805567	0.16680218977	-5.12433159378e-09
+PC.354	0.269797452688	0.0183043121851	-0.0280642571328	-0.135835386225	0.0206362226036	0.107699227944	0.227921893927	-0.155873183763	-5.12433159378e-09
+PC.593	0.218402226292	0.15672503794	-0.241445897729	0.153830519787	0.237264079606	-0.0354645631936	-0.0966404593444	0.0217144747081	-5.12433159378e-09
+PC.355	0.195650496999	-0.157765174624	0.0383077783424	-0.213856871786	-0.0475634366516	-0.255782153207	-0.0881647064341	0.0279497971843	-5.12433159378e-09
+PC.607	-0.109180192011	0.428093856324	0.14022111843	0.0317045158159	-0.154471859932	-0.0875356535256	0.00289973864488	-0.0319091564379	-5.12433159378e-09
+PC.634	-0.329885531917	-0.142498816231	-0.137315147392	0.0224068078709	-0.0494688354606	0.015370214314	-0.111272546766	-0.226053309339	-5.12433159378e-09
+
+
+eigvals	0.438193449813	0.303149574232	0.248737850995	0.208018516631	0.205845750596	0.155032935288	0.142263489179	0.129401808481	-2.36328968548e-16
+% variation explained	23.9365818458	16.5597285816	13.5874553374	11.3631371051	11.2444484482	8.46876772324	7.77122901735	7.06865194134	1.29096126393e-14
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_4.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_4.txt
new file mode 100644
index 0000000..03a2a1a
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_4.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.312618299505	-0.134917946942	-0.170972453493	0.0764753169825	0.0882699204763	0.0276567284685	-0.212890432382	0.122188819584	-2.46789851573e-09
+PC.635	-0.2132884209	-0.0740598897997	0.233675836919	-0.0721843071969	-0.294362560484	-0.0581637097257	-0.0768981998571	-0.0497853485604	-2.46789851573e-09
+PC.356	0.230640963532	-0.147797202059	0.188571605262	0.250626343249	0.0528628049705	0.184196793739	0.00488519962926	-0.0523484688119	-2.46789851573e-09
+PC.481	0.113891469456	0.0353961826273	0.00231336231365	-0.317900761628	0.136307065545	0.129946530278	-0.0830703875081	-0.105120468296	-2.46789851573e-09
+PC.354	0.300865315041	-0.00167418800107	0.0173927546379	-0.104037138843	-0.0781377850209	0.00792213105867	0.0844633760972	0.251399335162	-2.46789851573e-09
+PC.593	0.224939037265	0.232076272393	-0.287304464764	0.106642854351	-0.167846724183	-0.0125772072917	-0.0507811125218	-0.0891715910491	-2.46789851573e-09
+PC.355	0.167848130414	-0.213287333783	-0.0171692055242	0.00764992667748	0.128119605953	-0.285815540661	0.0329031894541	-0.0676559260392	-2.46789851573e-09
+PC.607	-0.185356415177	0.393132633935	0.182670419671	0.0719091507839	0.147767400013	-0.0733886145175	0.0310639380622	0.0303376984908	-2.46789851573e-09
+PC.634	-0.326921780126	-0.0888685283709	-0.149177855023	-0.0191813843756	-0.012979727269	0.0802228886509	0.270324429027	-0.0398440504794	-2.46789851573e-09
+
+
+eigvals	0.519914026682	0.30858919143	0.258164238227	0.202727303981	0.188511335665	0.148695541505	0.142995892141	0.109437810611	-5.48147077553e-17
+% variation explained	27.6691989526	16.4227454812	13.7391901418	10.7889032015	10.032346472	7.9133978122	7.6100693307	5.82414860791	2.91717279507e-15
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_5.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_5.txt
new file mode 100644
index 0000000..a77c412
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_5.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.297529982515	-0.134761736606	0.0797120449385	-0.0489692608121	0.132199783328	0.0629129889526	-0.261855324323	-0.0372661663609	-1.21763572578e-09
+PC.635	-0.269342929697	0.00823195555299	-0.170861487015	0.00676567224194	-0.272168045381	-0.166645022654	-0.0357138312685	-0.0231609609378	-1.21763572578e-09
+PC.356	0.252095110415	-0.154959217	-0.232952501924	0.124786056699	0.00548229006198	0.090696181001	-0.0498388124855	0.179875021982	-1.21763572578e-09
+PC.481	0.104576144436	-0.0469600338742	0.104676053002	-0.3570665303	0.00124619628031	-0.0663201948132	0.0700084408685	0.109327753224	-1.21763572578e-09
+PC.354	0.288756778746	0.0372125134467	-0.0415851924411	-0.080989515914	-0.11449405747	0.197972209955	-0.00331116212674	-0.188478831231	-1.21763572578e-09
+PC.593	0.19286283479	0.199134644522	0.320891768046	0.167446340091	-0.078905527258	-0.0569630851696	-0.0776207854943	0.0575138872036	-1.21763572578e-09
+PC.355	0.165482455054	-0.218474333635	0.00746606813222	0.103635714451	0.149352087413	-0.202367601965	0.0967571613296	-0.124349763697	-1.21763572578e-09
+PC.607	-0.0852439993525	0.384049930101	-0.185136088675	-0.0121834684412	0.189264509901	-0.0199052838949	0.036082355067	-0.00323613962588	-1.21763572578e-09
+PC.634	-0.351656411877	-0.0734737225067	0.117789335936	0.0965749919843	-0.0119772368758	0.160619808588	0.225491958433	0.0297751994426	-1.21763572578e-09
+
+
+eigvals	0.51444758364	0.286109243433	0.253677898271	0.20032493562	0.169189362502	0.153938151792	0.144775235712	0.101424794301	1.33437308462e-17
+% variation explained	28.2061073817	15.6867838431	13.9086396098	10.9834059388	9.2763062328	8.44011358528	7.93772966296	5.56091374554	7.31609433285e-16
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_6.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_6.txt
new file mode 100644
index 0000000..52b5237
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_6.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.306532758815	0.125055309543	-0.08615911958	0.00481923926582	0.12869850318	0.0884132334747	-0.234117775799	-0.0295337107367	-4.61554914669e-09
+PC.635	-0.2192577948	0.0255645834237	0.163426559207	-0.0175714515721	-0.33115744063	-0.0735011565247	-0.0452805641083	0.0100574762365	-4.61554914669e-09
+PC.356	0.225951027203	0.219515466953	0.204578542723	0.209761298578	0.119158634085	-0.135741439381	-0.00455873794414	0.0405033592303	-4.61554914669e-09
+PC.481	0.0908028876998	-0.00661109312438	-0.000453337527913	-0.326798277735	0.0856602599866	-0.0981237797222	0.00283054173001	0.146280860111	-4.61554914669e-09
+PC.354	0.265283313688	-0.0045922151862	0.0062090108145	-0.127098838264	-0.00612579727778	-0.0135362723215	0.00173199482336	-0.255029369363	-4.61554914669e-09
+PC.593	0.19574670089	-0.21163449603	-0.305157195503	0.149018307636	-0.076097660809	-0.100672457565	-0.0580904967716	0.0481201657006	-4.61554914669e-09
+PC.355	0.223455475873	0.120414505692	-0.0253460016063	0.0158819262218	-0.0848105622722	0.302907690662	0.0634777703455	0.0752218742009	-4.61554914669e-09
+PC.607	-0.135408755395	-0.392685590709	0.203178560568	0.0595266484496	0.115365266737	0.0804627102484	0.0438872261002	0.000156925236315	-4.61554914669e-09
+PC.634	-0.340040096344	0.124973529437	-0.160277019095	0.0324611474211	0.0493087969997	-0.0502085288699	0.230120041624	-0.0357775806159	-4.61554914669e-09
+
+
+eigvals	0.493922499204	0.293653382897	0.236756355703	0.194338851936	0.1765267285	0.15233972193	0.119178511611	0.0983059248854	1.9172964533e-16
+% variation explained	27.9839291371	16.6373782751	13.4137908102	11.0105627298	10.0013898316	8.6310382502	6.75223952937	5.56967143667	1.08627341679e-14
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_7.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_7.txt
new file mode 100644
index 0000000..eaf76d6
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_7.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.247724703631	-0.110907880622	0.148515184061	-0.0697969019998	-0.0519451757417	-0.28129818774	0.129606852106	0.0559122384073	-4.13390542395e-09
+PC.635	-0.281592024147	-0.0935985957717	-0.156580222739	0.0751448471758	-0.00335363029797	0.202521202889	0.218028267769	-0.0447266204757	-4.13390542395e-09
+PC.356	0.277341318558	-0.217172335579	-0.240864189131	-0.0828846105057	0.124058716899	-0.113350088725	-0.0456150092711	-0.127477368313	-4.13390542395e-09
+PC.481	0.0645446246272	0.123696182755	0.115718405274	0.360571807021	0.0681997111426	-0.0709886890683	-0.033380251629	-0.087239810899	-4.13390542395e-09
+PC.354	0.326462950777	-0.0934314670482	0.0143032594996	0.0554883383689	0.0553540224708	0.073102984185	0.0225407477261	0.257090445464	-4.13390542395e-09
+PC.593	0.207251961403	0.294193654604	0.159800145355	-0.208563452492	0.0892017906076	0.0639083024077	0.108255209858	-0.0714841189101	-4.13390542395e-09
+PC.355	0.163667410013	-0.101168902397	0.0976130732126	-0.0224543756361	-0.335618302326	0.0809784210014	-0.0791865505659	-0.0682660318133	-4.13390542395e-09
+PC.607	-0.16847562339	0.30201471578	-0.283404571053	-0.0250931101611	-0.0911829156	-0.0670065802057	-0.103379559938	0.0793736811619	-4.13390542395e-09
+PC.634	-0.34147591421	-0.103625371722	0.14489891552	-0.0824125417707	0.145285782846	0.112132635255	-0.216869706054	0.00681758537734	-4.13390542395e-09
+
+
+eigvals	0.543054102898	0.290991422552	0.254563261336	0.201903601877	0.175834268859	0.171080576396	0.143746820306	0.111200301257	1.53802566488e-16
+% variation explained	28.6969700961	15.3770537901	13.4520561748	10.6693266738	9.29172752472	9.04052498389	7.59610908326	5.87623167345	8.12749158443e-15
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_8.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_8.txt
new file mode 100644
index 0000000..fe9a5bd
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_8.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.297856558996	-0.118500137939	0.121887553385	0.00925175683482	-0.0629001611523	0.0235123286981	0.154183718038	0.226565441945	-6.9203227077e-09
+PC.635	-0.203966248031	0.00931969171391	-0.222791076921	-0.221486935693	0.101843830393	-0.144965640307	0.10888794931	-0.112159832411	-6.9203227077e-09
+PC.356	0.250075527075	-0.24138144942	-0.198173351288	0.12389753109	-0.236367962742	-0.0460108630286	0.0457784353988	-0.0488004347006	-6.9203227077e-09
+PC.481	0.0794651365171	-0.0377916144143	0.165333454574	0.251344494002	0.236442636891	-0.107288299401	0.0632066025291	-0.0754351738064	-6.9203227077e-09
+PC.354	0.254835019094	-0.00140502973402	-0.000650527428982	-0.116892529501	0.0679768548181	-0.154707453146	-0.187909727836	0.178633804997	-6.9203227077e-09
+PC.593	0.225226425994	0.284153114283	0.252385464356	-0.120623483819	-0.142948333647	0.0194387753121	0.0970253953757	-0.0718047255362	-6.9203227077e-09
+PC.355	0.12865666755	-0.191614940238	-0.00298287271838	-0.111739184653	0.137679465061	0.302598244605	-0.0238742517011	-0.0352177622037	-6.9203227077e-09
+PC.607	-0.0810236339757	0.352125113455	-0.254699322384	0.160902511543	0.0122565561358	0.115755383033	-0.0389182855819	0.0584163027965	-6.9203227077e-09
+PC.634	-0.355412335227	-0.0549047477064	0.139690678425	0.0253458401976	-0.113982885758	-0.008332475766	-0.218379835532	-0.12019762108	-6.9203227077e-09
+
+
+eigvals	0.464276362557	0.318290228223	0.279193241784	0.194898274087	0.18325656581	0.164542343712	0.136218302741	0.128149853582	-4.31017797408e-16
+% variation explained	24.8432207245	17.031567902	14.9395056259	10.428919567	9.80597695852	8.80458729546	7.28898051814	6.85724140855	2.3063569763e-14
\ No newline at end of file
diff --git a/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_9.txt b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_9.txt
new file mode 100644
index 0000000..66b8d7d
--- /dev/null
+++ b/tests/scripts_test_data/make_emperor/unweighted_unifrac_pc/pcoa_unweighted_unifrac_rarefaction_110_9.txt
@@ -0,0 +1,14 @@
+pc vector number	1	2	3	4	5	6	7	8	9
+PC.636	-0.276918773699	-0.109525428023	0.131430223502	-0.11422578396	-0.00807228907363	0.092749547669	0.247377186817	0.0989590042554	-3.46919738422e-09
+PC.635	-0.278181631362	-0.0264126317424	-0.107051242269	0.230883029394	-0.0423595235016	-0.253104608448	0.0377474156399	-0.00627296102413	-3.46919738422e-09
+PC.356	0.183414920533	-0.272502933338	-0.19359423824	-0.0219139076963	-0.155967607389	0.0537709485936	-0.107049426642	0.164325439223	-3.46919738422e-09
+PC.481	0.0521113998727	-0.00315475815851	0.00995534782892	-0.166521863666	0.32248357852	-0.129847874148	-0.0851611447031	0.077368765774	-3.46919738422e-09
+PC.354	0.313998489873	-0.0110051107082	0.0849509720571	0.285719007136	0.149460648588	0.137148324766	0.0612466442633	-0.0144264413729	-3.46919738422e-09
+PC.593	0.251766619129	0.281250626392	0.226532470349	-0.053731370011	-0.19060320034	-0.102968602035	-0.00941001027848	0.0706151211258	-3.46919738422e-09
+PC.355	0.19604841631	-0.158897863607	-0.0343705901915	-0.137803981217	-0.0531075052223	-0.0515530210484	0.0614119230428	-0.274442362529	-3.46919738422e-09
+PC.607	-0.101594937874	0.328567652216	-0.315877407021	-0.0481503686122	0.00758749286508	0.132203342143	0.016508139245	-0.0314390181072	-3.46919738422e-09
+PC.634	-0.340644502782	-0.0283195530314	0.198024463985	0.0257452386334	-0.0294215944475	0.121601942507	-0.222670727384	-0.0846875473443	-3.46919738422e-09
+
+
+eigvals	0.517202330691	0.30019150521	0.2650189	0.201057877124	0.192592716887	0.156750872809	0.13879823577	0.131494630884	1.08317974416e-16
+% variation explained	27.1767331967	15.7737580844	13.9255906441	10.5647170545	10.1199096985	8.23657666622	7.29324366474	6.90947099089	5.69163848736e-15
\ No newline at end of file
diff --git a/tests/test_biplots.py b/tests/test_biplots.py
new file mode 100755
index 0000000..59efae5
--- /dev/null
+++ b/tests/test_biplots.py
@@ -0,0 +1,243 @@
+#!/usr/bin/env python
+# File created on 16 Apr 2013
+from __future__ import division
+
+__author__ = "Yoshiki Vazquez Baeza"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+from unittest import TestCase, main
+
+from numpy import array
+from numpy.testing import assert_almost_equal
+
+from emperor.qiime_backports.parse import parse_classic_otu_table
+from emperor.util import EmperorUnsupportedComputation
+from emperor.biplots import extract_taxa_data, preprocess_otu_table
+
+
+class TopLevelTests(TestCase):
+    
+    def setUp(self):
+        self.biplot_coords = array([[-0.0520990488006, -0.0108550868341,
+            0.00118513950438, -0.0195647012451, -0.0437589801599,
+            0.000848245309189, -0.0122035463608, 0.0288287964617,
+            -4.84141986706e-09], [-0.00406501716894, -0.0300693128299,
+            -0.090316974427, 0.0559730983008, -0.0597265944801, 0.0456519002902,
+            -0.054142291901, 0.0668355273511, -4.84141986706e-09],[
+            0.0965087039672, -0.0164113801126, 0.0168583314836, 0.0103732287638,
+            -0.0225710882818, 0.0283857610935, 3.22664794516e-05,
+            0.0290430321474, -4.84141986706e-09], [0.126316230269,
+            0.0263889076329, 0.0330227792131, 0.0885396607535, -0.0372547601153,
+            0.0638622448577, -0.0224927101495, 0.0902626098256,
+            -4.84141986706e-09], [-0.0672105325806, 0.0105941468738,
+            -0.00527647509456, -0.0131631383518, 0.0315560857565,
+            -0.0199787562498, 0.00815726904809, -0.0175931838803,
+            -4.84141986706e-09], [0.118424097634, -0.00197525147278,
+            -0.0399274159763, 0.0211233039391, -0.124633799325, 0.0133749785777,
+            -0.0384382665152, -0.0584384149046, -4.84141986706e-09],
+            [0.228820399536, -0.130142097093, -0.287149447883, 0.0864498846421,
+            0.0442951919304, 0.20604260722, 0.0310003571386, 0.0719920436501,
+            -4.84141986706e-09], [-0.000909430629502, 0.0116559690557,
+            -0.049810186364, 0.0452278786773, -0.12933257558, -0.0214449951683,
+            -0.0693407019638, -0.062534332665, -4.84141986706e-09]])
+
+        self.otu_table = array([[0.02739726, 0.04697987, 0.02, 0.04697987, 0.01,
+            0.02027027, 0.01360544, 0.01342282, 0.02666667], [0.00684932,
+            0.02013423, 0.02, 0.00671141,  0., 0.00675676, 0., 0., 0.], [
+            0.14383562, 0.27516779, 0.65333333, 0.52348993, 0.38926174,
+            0.69594595, 0.28571429, 0.0738255, 0.19333333], [0., 0.02013423,
+            0.03333333, 0.01342282, 0., 0.0472973, 0., 0., 0.], [0.78767123,
+            0.45637584, 0.22, 0.39597315, 0.41610738, 0.20945946, 0.70068027,
+            0.89932886, 0.77333333], [0.,0.02013423, 0.01333333, 0.00671141,
+            0.03355705, 0.00675676, 0., 0., 0.],[0., 0., 0.01333333, 0., 0., 0.,
+            0., 0., 0.], [0.03424658, 0.16107383, 0.02666667, 0.00671141,
+            0.14765101, 0.01351351, 0., 0.01342282, 0.00666667]])
+
+        self.lineages = ['Root;k__Bacteria;Other',
+            'Root;k__Bacteria;p__Actinobacteria', 
+            'Root;k__Bacteria;p__Bacteroidetes',
+            'Root;k__Bacteria;p__Deferribacteres',
+            'Root;k__Bacteria;p__Firmicutes',
+            'Root;k__Bacteria;p__Proteobacteria','Root;k__Bacteria;p__TM7',
+            'Root;k__Bacteria;p__Tenericutes']
+
+        self.prevalence = array([0.04445514, 0.00972396, 0.6646394, 0.02081361,
+            1., 0.01385989, 0., 0.08185147])
+        self.otu_sample_ids = ['PC.636', 'PC.635', 'PC.356', 'PC.481', 'PC.354',
+            'PC.593', 'PC.355', 'PC.607', 'PC.634']
+        self.coords = COORDS
+        self.coords_header = ['PC.636', 'PC.635', 'PC.356', 'PC.481', 'PC.354',
+            'PC.593', 'PC.355', 'PC.607', 'PC.634']
+
+        # data used to test a case where an exception should be raised
+        self.otu_table_broken = array([[0.02739726, 0.04697987, 0.02,
+            0.04697987, 0.01, 0.02027027, 0.01360544, 0.01342282, 0.02666667]])
+        self.lineages_broken = ['Root;k__Bacteria']
+
+    def test_filter_taxa(self):
+        """Check the appropriate number of elements are extracted"""
+        # test the simple case where you want to retain 5 taxonomic groups
+        o_coords, o_table, o_lineages, o_prevalence = extract_taxa_data(
+            self.biplot_coords, self.otu_table, self.lineages,self.prevalence,3)
+
+        assert_almost_equal(o_coords, array([[-0.0672105325806, 0.0105941468738,
+            -0.00527647509456, -0.0131631383518, 0.0315560857565,
+            -0.0199787562498, 0.00815726904809, -0.0175931838803,
+            -4.84141986706e-09],[0.0965087039672, -0.0164113801126,
+            0.0168583314836, 0.0103732287638, -0.0225710882818, 0.0283857610935,
+            3.22664794516e-05, 0.0290430321474, -4.84141986706e-09], [
+            -0.000909430629502, 0.0116559690557, -0.049810186364,
+            0.0452278786773, -0.12933257558, -0.0214449951683,
+            -0.0693407019638, -0.062534332665, -4.84141986706e-09]]))
+        assert_almost_equal(o_table, array([[0.78767123, 0.45637584, 0.22,
+            0.39597315, 0.41610738, 0.20945946, 0.70068027, 0.89932886,
+            0.77333333], [0.14383562, 0.27516779, 0.65333333, 0.52348993,
+            0.38926174, 0.69594595, 0.28571429, 0.0738255, 0.19333333],
+            [0.03424658, 0.16107383, 0.02666667, 0.00671141, 0.14765101,
+            0.01351351, 0., 0.01342282, 0.00666667]]))
+        self.assertEquals(o_lineages, ['Root;k__Bacteria;p__Firmicutes',
+            'Root;k__Bacteria;p__Bacteroidetes',
+            'Root;k__Bacteria;p__Tenericutes'])
+        assert_almost_equal(o_prevalence,  array([ 1., 0.6646394, 0.08185147]))
+
+        # test the case where all the elements are requested
+        o_coords, o_table, o_lineages, o_prevalence = extract_taxa_data(
+            self.biplot_coords,self.otu_table,self.lineages,self.prevalence,-1)
+
+        assert_almost_equal(o_coords, array([[-6.72105326e-02, 1.05941469e-02,
+            -5.27647509e-03, -1.31631384e-02, 3.15560858e-02, -1.99787562e-02,
+            8.15726905e-03, -1.75931839e-02, -4.84141987e-09], [9.65087040e-02,
+            -1.64113801e-02, 1.68583315e-02, 1.03732288e-02, -2.25710883e-02,
+            2.83857611e-02, 3.22664795e-05, 2.90430321e-02, -4.84141987e-09],
+            [-9.09430630e-04, 1.16559691e-02, -4.98101864e-02, 4.52278787e-02,
+            -1.29332576e-01, -2.14449952e-02, -6.93407020e-02, -6.25343327e-02,
+            -4.84141987e-09], [-5.20990488e-02, -1.08550868e-02, 1.18513950e-03,
+            -1.95647012e-02, -4.37589802e-02, 8.48245309e-04, -1.22035464e-02,
+            2.88287965e-02, -4.84141987e-09], [1.26316230e-01, 2.63889076e-02,
+            3.30227792e-02, 8.85396608e-02, -3.72547601e-02, 6.38622449e-02,
+            -2.24927101e-02, 9.02626098e-02, -4.84141987e-09], [1.18424098e-01,
+            -1.97525147e-03, -3.99274160e-02, 2.11233039e-02, -1.24633799e-01,
+            1.33749786e-02, -3.84382665e-02, -5.84384149e-02, -4.84141987e-09],
+            [-4.06501717e-03, -3.00693128e-02, -9.03169744e-02, 5.59730983e-02,
+            -5.97265945e-02, 4.56519003e-02, -5.41422919e-02, 6.68355274e-02,
+            -4.84141987e-09], [2.28820400e-01, -1.30142097e-01, -2.87149448e-01,
+            8.64498846e-02, 4.42951919e-02, 2.06042607e-01, 3.10003571e-02,
+            7.19920437e-02, -4.84141987e-09]]))
+        assert_almost_equal(o_table, array([[ 0.78767123, 0.45637584, 0.22,
+            0.39597315, 0.41610738, 0.20945946, 0.70068027, 0.89932886,
+            0.77333333], [0.14383562, 0.27516779, 0.65333333, 0.52348993,
+            0.38926174, 0.69594595, 0.28571429, 0.0738255, 0.19333333],
+            [0.03424658, 0.16107383, 0.02666667, 0.00671141, 0.14765101,
+            0.01351351, 0., 0.01342282, 0.00666667], [0.02739726, 0.04697987,
+            0.02, 0.04697987, 0.01, 0.02027027, 0.01360544, 0.01342282,
+            0.02666667], [ 0., 0.02013423, 0.03333333, 0.01342282, 0.,
+            0.0472973, 0., 0., 0.], [ 0. , 0.02013423, 0.01333333, 0.00671141,
+            0.03355705, 0.00675676, 0., 0., 0.], [ 0.00684932, 0.02013423,
+            0.02, 0.00671141, 0., 0.00675676, 0., 0., 0.], [ 0., 0., 0.01333333,
+            0., 0., 0., 0., 0., 0.]]))
+        self.assertEquals(o_lineages, ['Root;k__Bacteria;p__Firmicutes',
+            'Root;k__Bacteria;p__Bacteroidetes',
+            'Root;k__Bacteria;p__Tenericutes', 'Root;k__Bacteria;Other',
+            'Root;k__Bacteria;p__Deferribacteres',
+            'Root;k__Bacteria;p__Proteobacteria',
+            'Root;k__Bacteria;p__Actinobacteria','Root;k__Bacteria;p__TM7'])
+        assert_almost_equal(o_prevalence, array([ 1., 0.6646394, 0.08185147,
+            0.04445514, 0.02081361, 0.01385989, 0.00972396, 0.]))
+
+    def test_preprocess_otu_table(self):
+        """Check the coords and otu table are processed correctly"""
+
+        # processing only the four most prevalent taxa
+        o_otu_coords, o_otu_table, o_otu_lineages, o_prevalence, lines =\
+            preprocess_otu_table(self.otu_sample_ids, self.otu_table,
+                self.lineages, self.coords, self.coords_header, 4)
+
+        assert_almost_equal(o_otu_coords, array([[ -6.71083200e-02,
+            1.05892642e-02, -5.26801821e-03, -1.31730322e-02, 3.15036935e-02,
+            -1.99712144e-02, 8.14445313e-03, -1.76632227e-02, -4.84141987e-09],
+            [ 9.65846961e-02, -1.64070839e-02, 1.68610695e-02, 1.03495979e-02,
+            -2.26223522e-02, 2.83763737e-02, 1.76116225e-05, 2.89253284e-02,
+            -4.84141987e-09], [-5.61881305e-04, 1.16341355e-02, -4.97196330e-02,
+            4.51141625e-02, -1.29353935e-01, -2.14114921e-02, -6.92988035e-02,
+            -6.27730937e-02, -4.84141987e-09], [-5.70985165e-02,
+            -1.09278921e-02, 8.49830390e-04, -1.91550282e-02, -4.22122952e-02,
+            7.75750297e-04, -1.18543093e-02, 3.31082777e-02, -4.84141987e-09]]))
+        assert_almost_equal(o_otu_table, array([[ 0.78767123, 0.45637584, 0.22,
+            0.39597315, 0.41610738, 0.20945946, 0.70068027, 0.89932886,
+            0.77333333], [0.14383562, 0.27516779, 0.65333333, 0.52348993,
+            0.38926174, 0.69594595, 0.28571429, 0.0738255, 0.19333333], [
+            0.03424658, 0.16107383, 0.02666667, 0.00671141, 0.14765101,
+            0.01351351, 0., 0.01342282, 0.00666667], [0.02739726, 0.04697987,
+            0.02, 0.04697987, 0.01, 0.02027027, 0.01360544, 0.01342282,
+            0.02666667]]))
+        self.assertEquals(o_otu_lineages, ['Root;k__Bacteria;p__Firmicutes',
+            'Root;k__Bacteria;p__Bacteroidetes',
+            'Root;k__Bacteria;p__Tenericutes', 'Root;k__Bacteria;Other'])
+        assert_almost_equal(o_prevalence, array([ 1., 0.66471926, 0.08193196,
+            0.04374296]))
+        self.assertEquals(lines, LINES)
+
+        # tests for correct outputs of empty inputs
+        o_otu_coords, o_otu_table, o_otu_lineages, o_prevalence, lines =\
+            preprocess_otu_table([],[], [], self.coords, self.coords_header, 4)
+        self.assertEquals(o_otu_coords, [])
+        self.assertEquals(o_otu_table, [])
+        self.assertEquals(o_otu_lineages, [])
+        self.assertEquals(o_prevalence, [])
+        self.assertEquals(lines, '')
+
+    def test_preprocess_otu_table_exceptions(self):
+        """Check the exceptions are raised appropriately"""
+        # should raise an exception because the inputs contain a single row
+        with self.assertRaises(EmperorUnsupportedComputation):
+            o_otu_coords, o_otu_table, o_otu_lineages, o_prevalence, lines =\
+                preprocess_otu_table(self.otu_sample_ids, self.otu_table_broken,
+                self.lineages_broken, self.coords, self.coords_header, 4)
+
+        # some inputs are completely wrong but should still fail because the
+        # contingency table has one row only, hence scores cannot be computed
+        with self.assertRaises(EmperorUnsupportedComputation):
+            o_otu_coords, o_otu_table, o_otu_lineages, o_prevalence, lines =\
+                preprocess_otu_table(self.otu_sample_ids, self.otu_table_broken,
+                [[]], self.coords, self.coords_header, 4)
+        with self.assertRaises(EmperorUnsupportedComputation):
+            o_otu_coords, o_otu_table, o_otu_lineages, o_prevalence, lines =\
+                preprocess_otu_table(self.otu_sample_ids, array([]),
+                self.lineages_broken, self.coords, self.coords_header, 4)
+
+
+OTU_TABLE = """Taxon\tPC.636\tPC.635\tPC.356\tPC.481\tPC.354\tPC.593\tPC.355\tPC.607\tPC.634
+Root;k__Bacteria;Other\t0.0202702702703\t0.0469798657718\t0.0266666666667\t0.027397260274\t0.0134228187919\t0.0134228187919\t0.0136054421769\t0.0469798657718\t0.02
+Root;k__Bacteria;p__Actinobacteria\t0.00675675675676\t0.00671140939597\t0.0\t0.00684931506849\t0.0\t0.0\t0.0\t0.0201342281879\t0.02
+Root;k__Bacteria;p__Bacteroidetes\t0.695945945946\t0.523489932886\t0.193333333333\t0.143835616438\t0.0738255033557\t0.389261744966\t0.285714285714\t0.275167785235\t0.653333333333
+Root;k__Bacteria;p__Deferribacteres\t0.0472972972973\t0.0134228187919\t0.0\t0.0\t0.0\t0.0\t0.0\t0.0201342281879\t0.0333333333333
+Root;k__Bacteria;p__Firmicutes\t0.209459459459\t0.395973154362\t0.773333333333\t0.787671232877\t0.89932885906\t0.41610738255\t0.700680272109\t0.456375838926\t0.22
+Root;k__Bacteria;p__Proteobacteria\t0.00675675675676\t0.00671140939597\t0.0\t0.0\t0.0\t0.0335570469799\t0.0\t0.0201342281879\t0.0133333333333
+Root;k__Bacteria;p__TM7\t0.0\t0.0\t0.0\t0.0\t0.0\t0.0\t0.0\t0.0\t0.0133333333333
+Root;k__Bacteria;p__Tenericutes\t0.0135135135135\t0.00671140939597\t0.00666666666667\t0.0342465753425\t0.0134228187919\t0.147651006711\t0.0\t0.161073825503\t0.0266666666667
+"""
+
+COORDS = array([[-0.276542163845, -0.144964375408, 0.0666467344429, -0.0677109454288, 0.176070269506, 0.072969390136, -0.229889463523, -0.0465989416581, -4.84141986706e-09],
+[-0.237661393984, 0.0460527772512, -0.138135814766, 0.159061025229, -0.247484698646, -0.115211468101, -0.112864033263, 0.0647940729676, -4.84141986706e-09],
+[0.228820399536, -0.130142097093, -0.287149447883, 0.0864498846421, 0.0442951919304, 0.20604260722, 0.0310003571386, 0.0719920436501, -4.84141986706e-09],
+[0.0422628480532, -0.0139681511889, 0.0635314615517, -0.346120552134, -0.127813807608, 0.0139350721063, 0.0300206887328, 0.140147849223, -4.84141986706e-09],
+[0.280399117569, -0.0060128286014, 0.0234854344148, -0.0468109474823, -0.146624450094, 0.00566979124596, -0.0354299634191, -0.255785794275, -4.84141986706e-09],
+[0.232872767451, 0.139788385269, 0.322871079774, 0.18334700682, 0.0204661596818, 0.0540589147147, -0.0366250872041, 0.0998235721267, -4.84141986706e-09],
+[0.170517581885, -0.194113268955, -0.0308965283066, 0.0198086158783, 0.155100062794, -0.279923941712, 0.0576092515759, 0.0242481862127, -4.84141986706e-09],
+[-0.0913299284215, 0.424147148265, -0.135627421345, -0.057519480907, 0.151363490722, -0.0253935675552, 0.0517306152066, -0.038738217609, -4.84141986706e-09],
+[-0.349339228244, -0.120787589539, 0.115274502117, 0.0694953933826, -0.0253722182853, 0.067853201946, 0.244447634756, -0.0598827706386, -4.84141986706e-09]])
+
+LINES = """#Taxon\tpc0\tpc1\tpc2\tpc3\tpc4\tpc5\tpc6\tpc7\tpc8
+Root;k__Bacteria;p__Firmicutes\t-0.0671083199539\t0.010589264186\t-0.00526801821358\t-0.0131730321859\t0.0315036934893\t-0.0199712144464\t0.00814445312587\t-0.0176632227289\t-4.84141986706e-09
+Root;k__Bacteria;p__Bacteroidetes\t0.0965846961434\t-0.0164070839162\t0.0168610695358\t0.010349597944\t-0.0226223521782\t0.0283763736987\t1.76116225444e-05\t0.0289253284365\t-4.84141986706e-09
+Root;k__Bacteria;p__Tenericutes\t-0.000561881304886\t0.0116341354761\t-0.0497196329856\t0.0451141625418\t-0.129353934943\t-0.0214114921223\t-0.0692988035087\t-0.0627730937042\t-4.84141986706e-09
+Root;k__Bacteria;Other\t-0.0570985164756\t-0.0109278921351\t0.00084983039019\t-0.0191550282339\t-0.0422122952074\t0.000775750296682\t-0.0118543093074\t0.0331082776958\t-4.84141986706e-09"""
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/test_filter.py b/tests/test_filter.py
new file mode 100755
index 0000000..c3179c7
--- /dev/null
+++ b/tests/test_filter.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+# File created on 12 May 2013
+from __future__ import division
+
+__author__ = "Yoshiki Vazquez Baeza"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+from numpy import array
+from emperor.filter import (filter_samples_from_coords,
+    keep_samples_from_pcoa_data)
+from unittest import TestCase, main
+from numpy.testing import assert_almost_equal
+
+class TopLevelTests(TestCase):
+    
+    def setUp(self):
+        self.jk_coords_header = [['1', '2', '3'], ['1', '2', '3'],
+            ['1', '2', '3'], ['1', '2', '3']]
+        self.jk_coords_data = [array([[1.2, 0.1, -1.2],[-2.5, -4.0, 4.5],
+            [0.11, 5.33,-0.23]]),array([[-1.4, 0.05, 1.3],[2.6, 4.1, -4.7],
+            [0.14, 2.00, -1.11]]),array([[-1.5, 0.05, 1.6],[2.4, 4.0, -4.8],
+            [1.0, -0.8, 0.01]]),array([[-1.5, 0.05, 1.6],[2.4, 4.0, -4.8],
+            [2, 0, 1.11111]])]
+
+        self.coords_header = ['PC.355', 'PC.635', 'PC.636', 'PC.354']
+        self.coords_data = COORDS_DATA
+
+    def test_filter_Samples_from_coords(self):
+        """Check it filters samples from coords data as requested"""
+        # check the function raises an exception on an empty set
+        self.assertRaises(ValueError, filter_samples_from_coords,
+            self.coords_header, self.coords_data, ['foo','bar', 'PC.666'])
+
+        # check it keeps the requested samples
+        out_headers, out_coords = filter_samples_from_coords(self.coords_header,
+            self.coords_data, ['PC.636', 'PC.355'])
+        self.assertEquals(out_headers, ['PC.355', 'PC.636'])
+        assert_almost_equal(out_coords, array([[-0.2, -0.1, 0.06, -0.06],
+            [0.1, -0.1, -0.2, 0.08]]))
+
+        # check it removes the requested samples
+        out_headers, out_coords = filter_samples_from_coords(self.coords_header,
+            self.coords_data, ['PC.636', 'PC.355'], negate=True)
+        self.assertEquals(out_headers, ['PC.635', 'PC.354'])
+        assert_almost_equal(out_coords, array([[-0.3, 0.04, -0.1, 0.15],
+            [0.04, -0.01, 0.06, -0.34]]))
+
+    def test_remove_samples_from_pcoa_data(self):
+        """ """
+        # check it keeps the requested samples
+        out_headers, out_coords = keep_samples_from_pcoa_data(
+            self.coords_header, self.coords_data, ['PC.636', 'PC.355'])
+        self.assertEquals(out_headers, ['PC.355', 'PC.636'])
+        assert_almost_equal(out_coords, array([[-0.2, -0.1, 0.06, -0.06],
+            [0.1, -0.1, -0.2, 0.08]]))
+
+        # check it keeps the requested samples when the input is jackknifed data
+        out_headers, out_coords = keep_samples_from_pcoa_data(
+            self.jk_coords_header, self.jk_coords_data, ['1', '3'])
+        self.assertEqual(out_headers, [['1', '3'], ['1', '3'], ['1', '3'], ['1',
+            '3']])
+        assert_almost_equal(out_coords,[array([[1.2, 0.1, -1.2],
+            [0.11, 5.33,-0.23]]),array([[-1.4, 0.05, 1.3],[0.14, 2.00, -1.11]]),
+            array([[-1.5, 0.05, 1.6],[1.0, -0.8, 0.01]]),array([
+            [-1.5, 0.05, 1.6],[2, 0, 1.11111]])])
+
+        # flip the order of the samples to keep
+        out_headers, out_coords = keep_samples_from_pcoa_data(
+            self.jk_coords_header, self.jk_coords_data, ['3', '1'])
+        self.assertEqual(out_headers, [['1', '3'], ['1', '3'], ['1', '3'], ['1',
+            '3']])
+        assert_almost_equal(out_coords,[array([[1.2, 0.1, -1.2],
+            [0.11, 5.33,-0.23]]),array([[-1.4, 0.05, 1.3],[0.14, 2.00, -1.11]]),
+            array([[-1.5, 0.05, 1.6],[1.0, -0.8, 0.01]]),array([
+            [-1.5, 0.05, 1.6],[2, 0, 1.11111]])])
+
+
+COORDS_DATA = array([
+    [-0.2, -0.1, 0.06, -0.06],
+    [-0.3, 0.04, -0.1, 0.15],
+    [0.1, -0.1, -0.2, 0.08],
+    [0.04, -0.01, 0.06, -0.34]])
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/test_format.py b/tests/test_format.py
new file mode 100755
index 0000000..5512a46
--- /dev/null
+++ b/tests/test_format.py
@@ -0,0 +1,1516 @@
+#!/usr/bin/env python
+# File created on 25 Jan 2013
+from __future__ import division
+
+__author__ = "Yoshiki Vazquez Baeza"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+
+from numpy import array
+from emperor.format import (format_pcoa_to_js, format_mapping_file_to_js,
+    format_taxa_to_js, format_vectors_to_js, format_emperor_html_footer_string,
+    EmperorLogicError, format_comparison_bars_to_js, format_emperor_autograph)
+from unittest import TestCase, main
+
+class TopLevelTests(TestCase):
+
+    def setUp(self):
+        self.pcoa_pct_var = array([2.66887049e+01, 1.62563704e+01,
+            1.37754129e+01, 1.12172158e+01, 1.00247750e+01, 8.22835130e+00,
+            7.55971174e+00, 6.24945796e+00, 1.17437419e-14])
+        self.pcoa_pct_var_really_low = array([2.66887049e+01, 1.62563704e+01,
+            0.1, 0.2, 0.19, 0.18, 0.17, 0.16, 0.15])
+        self.pcoa_headers = ['PC.355','PC.607','PC.634','PC.635','PC.593',
+            'PC.636','PC.481','PC.354','PC.356']
+        self.pcoa_coords = PCOA_DATA
+        self.pcoa_eigen_values = array([4.79412119e-01, 2.92014956e-01,
+            2.47449246e-01, 2.01496072e-01, 1.80076128e-01, 1.47806773e-01,
+            1.35795927e-01, 1.12259696e-01, 2.10954117e-16])
+
+        # data specific for testing the jackknifing
+        self.pcoa_jk_headers = ['PC.355','PC.607','PC.634','PC.635']
+        self.pcoa_jk_coords = array([[0.3, 0.5, 0.1, 0.3],[1.1, 1.1, 1.0, 0.8],
+            [0.1, 3.3, 5.5, 0.1], [1.0, 2.0, 1.0, 1.0]])
+        self.pcoa_jk_eigen_values = array([0.45, 0.32, 0.21, 0.02])
+        self.pcoa_jk_pct_var = array([44, 40, 15, 1])
+        self.pcoa_jk_coords_low = array([[0.2, 0.3, 0.1, 0.3],[1.1, 0.1, 0.0, 0.3],
+            [0.6, 3.1, 1.5, 0.1], [0.023, 1.0, 0.01, 1.0]])
+        self.pcoa_jk_coords_high = array([[0.6, 0.8, 0.9, 0.31],[1, 2.1, 0.0, 0.8],
+            [0.9, 3.7, 5.5, 0.1111], [0.01222, 2.0, 0.033, 2.0]])
+
+        self.mapping_file_data = MAPPING_FILE_DATA
+        self.mapping_file_headers = ['SampleID', 'BarcodeSequence',
+            'LinkerPrimerSequence', 'Treatment', 'DOB', 'Description']
+        self.good_columns = ['Treatment', 'LinkerPrimerSequence']
+
+        self.otu_coords = array([[2.80399118e-01, -6.01282860e-03,
+            2.34854344e-02, -4.68109475e-02, -1.46624450e-01, 5.66979125e-03,
+            -3.54299634e-02, -2.55785794e-01, -4.84141987e-09], [2.28820400e-01,
+            -1.30142097e-01, -2.87149448e-01, 8.64498846e-02, 4.42951919e-02,
+            2.06042607e-01, 3.10003571e-02, 7.19920437e-02, -4.84141987e-09],
+            [-9.13299284e-02, 4.24147148e-01, -1.35627421e-01, -5.75194809e-02,
+            1.51363491e-01, -2.53935676e-02, 5.17306152e-02, -3.87382176e-02,
+            -4.84141987e-09], [-2.76542164e-01, -1.44964375e-01, 6.66467344e-02,
+            -6.77109454e-02, 1.76070270e-01, 7.29693901e-02, -2.29889464e-01,
+            -4.65989417e-02,-4.84141987e-09]])
+        self.lineages = ['Root;k__Bacteria;p__Firmicutes',
+            'Root;k__Bacteria;p__Bacteroidetes',
+            'Root;k__Bacteria;p__Tenericutes', 'Root;k__Bacteria;Other']
+        self.prevalence = array([ 1., 0.66471926, 0.08193196, 0.04374296])
+
+        # comparison test
+        self.comparison_coords_data = array([[-0.0677, -2.036, 0.2726, 1.051,
+            -0.180, -0.698], [-1.782, -0.972, 0.1582, -1.091, 0.531, 0.292],
+            [-0.659, -0.2566, 0.514, -2.698, -0.393, 0.420], [-1.179, -0.968,
+            2.525, 0.53, -0.529, 0.632],[-0.896, -1.765, 0.274, -0.3235, 0.4009,
+            -0.03497], [-0.0923, 1.414, -0.622, 0.298, 0.5, -0.4580], [-0.972,
+            0.551, 1.144, 0.3147, -0.476, -0.4279], [1.438, -2.603, -1.39,
+            1.300, -0.1606, 1.260], [-0.356, 0.0875, 0.772, 0.539, -0.586,
+            -1.431], [1.512, -1.239, -0.0365, -0.682, -0.971, 0.356],
+            [1.17, 1.31, -1.407, 1.6, 0.60, 2.26], [2.618, 0.739, -0.01295,
+            -0.937, 3.079, -2.534], [0.2339, -0.880, -1.753, 0.177, 0.3517,
+            -0.743], [0.436, 2.12, -0.935, -0.476, -0.805, 0.4164], [-0.880,
+            1.069, 1.069, -0.596, -0.199, 0.306], [0.294, 0.2988, 0.04670,
+            -0.3865, 0.460, -0.431], [1.640, 0.2485, -0.354, 1.43, 1.226,
+            1.095], [0.821, -1.13, -1.794, -1.171, -1.27, -0.842]])
+        self.comparison_coords_headers = ['sampa_0', 'sampb_0', 'sampc_0',
+            'sampd_0', 'sampe_0', 'sampf_0', 'sampa_1', 'sampb_1', 'sampc_1',
+            'sampd_1', 'sampe_1', 'sampf_1', 'sampa_2', 'sampb_2', 'sampc_2',
+            'sampd_2', 'sampe_2', 'sampf_2']
+        self.comparison_coords_headers_zero = ['sampa0_0', 'sampb0_0', 'sampc0_0',
+            'sampd00_0', 'sampe00_0', 'sampf00_0', 'sampa0_1', 'sampb0_1', 'sampc0_1',
+            'sampd00_1', 'sampe00_1', 'sampf00_1', 'sampa0_2', 'sampb0_2', 'sampc0_2',
+            'sampd00_2', 'sampe00_2', 'sampf00_2']
+
+    def test_format_pcoa_to_js(self):
+        """Test correct formatting of the PCoA file"""
+        # test the case with only points and nothing else
+        out_js_pcoa_string = format_pcoa_to_js(self.pcoa_headers,
+            self.pcoa_coords, self.pcoa_eigen_values, self.pcoa_pct_var)
+        self.assertEquals(out_js_pcoa_string, PCOA_JS)
+
+        # test custom axes and the labels
+        out_js_pcoa_string = format_pcoa_to_js(self.pcoa_headers,
+            self.pcoa_coords, self.pcoa_eigen_values,
+            self.pcoa_pct_var, custom_axes=['Instant'])
+        self.assertEquals(out_js_pcoa_string, PCOA_JS_CUSTOM_AXES)
+
+        # test jackknifed pcoa plots
+        out_js_pcoa_string = format_pcoa_to_js(self.pcoa_jk_headers,
+            self.pcoa_jk_coords, self.pcoa_jk_eigen_values,
+            self.pcoa_jk_pct_var, coords_low=self.pcoa_jk_coords_low,
+            coords_high=self.pcoa_jk_coords_high)
+        self.assertEquals(out_js_pcoa_string, PCOA_JS_JACKKNIFED)
+
+        # check it raises an exception when the variation explained on the
+        # axes is not greater than 0.51 for at least three of them
+        self.assertRaises(EmperorLogicError, format_pcoa_to_js,
+            self.pcoa_headers, self.pcoa_coords, self.pcoa_eigen_values,
+            self.pcoa_pct_var_really_low)
+            
+        # test segments
+        out_js_pcoa_string = format_pcoa_to_js(self.pcoa_jk_headers,
+            self.pcoa_jk_coords, self.pcoa_jk_eigen_values,
+            self.pcoa_jk_pct_var, coords_low=self.pcoa_jk_coords_low,
+            coords_high=self.pcoa_jk_coords_high, number_of_segments=14)
+        self.assertEquals(out_js_pcoa_string, PCOA_JS_SEGMENTS)
+
+
+    def test_format_mapping_file_to_js(self):
+        """Tests correct formatting of the metadata mapping file"""
+        out_js_mapping_file_string = format_mapping_file_to_js(
+            self.mapping_file_data, self.mapping_file_headers, self.good_columns)
+        self.assertEquals(out_js_mapping_file_string, MAPPING_FILE_JS)
+
+    def test_format_taxa_to_js(self):
+        """Tests correct formatting of the taxa"""
+        out_js_taxa_string = format_taxa_to_js(self.otu_coords, self.lineages,
+            self.prevalence)
+        self.assertEquals(out_js_taxa_string, TAXA_JS_STRING)
+
+        # case with empty data
+        out_js_taxa_string = format_taxa_to_js([], [], [])
+        self.assertEquals(out_js_taxa_string, "\nvar g_taxaPositions = "
+            "new Array();\n\n")
+
+    def test_format_vectors_to_js(self):
+        """Tests correct formatting of the vectors from the coords"""
+
+        # test that only the variable declaration gets returned
+        out_js_vector_string = format_vectors_to_js(self.mapping_file_data,
+            self.mapping_file_headers, self.pcoa_coords, self.pcoa_headers,
+            None, None)
+        self.assertEquals(out_js_vector_string, '\nvar g_vectorPositions = new '
+            'Array();\n')
+
+        # vector string without sorting for the coordinates
+        out_js_vector_string = format_vectors_to_js(self.mapping_file_data,
+            self.mapping_file_headers, self.pcoa_coords, self.pcoa_headers,
+            'Treatment', None)
+        self.assertEquals(out_js_vector_string, VECTOR_JS_STRING_NO_SORTING)
+
+        # vector string sorting by the DOB category
+        out_js_vector_string = format_vectors_to_js(self.mapping_file_data,
+            self.mapping_file_headers, self.pcoa_coords, self.pcoa_headers,
+            'Treatment', 'DOB')
+        self.assertEquals(out_js_vector_string, VECTOR_JS_STRING_SORTING)
+
+    def test_format_comparison_bars_to_js(self):
+        """Check the correct strings are created for the two types of inputs"""
+
+        # empty string generation for comparison i. e. no clones
+        out_js_comparison_string = format_comparison_bars_to_js(
+            self.comparison_coords_data, self.comparison_coords_headers, 0,
+            True)
+        self.assertEquals(out_js_comparison_string, '\nvar '
+            'g_comparisonPositions = new Array();\nvar g_isSerialComparisonPlot'
+            ' = true;\n')
+
+        out_js_comparison_string = format_comparison_bars_to_js(
+            self.comparison_coords_data, self.comparison_coords_headers, 3,
+            True)
+        self.assertEquals(out_js_comparison_string, COMPARISON_JS_STRING)
+
+        # empty string generation for comparison i. e. no clones
+        out_js_comparison_string = format_comparison_bars_to_js(
+            self.comparison_coords_data, self.comparison_coords_headers, 0,
+            False)
+        self.assertEquals(out_js_comparison_string, '\nvar '
+            'g_comparisonPositions = new Array();\nvar g_isSerialComparisonPlot'
+            ' = false;\n')
+
+        out_js_comparison_string = format_comparison_bars_to_js(
+            self.comparison_coords_data, self.comparison_coords_headers, 3,
+            False)
+        self.assertEquals(out_js_comparison_string,
+            COMPARISON_JS_STRING_NON_SERIAL)
+            
+        out_js_comparison_string = format_comparison_bars_to_js(
+            self.comparison_coords_data, self.comparison_coords_headers_zero, 3,
+            False)
+        self.assertEquals(out_js_comparison_string, COMPARISON_COORDS_HEADERS_ZERO)         
+
+    def test_format_comparison_bars_to_js_exceptions(self):
+        """Check the correct exceptions are raised for incorrect inputs"""
+
+        # assertion for wrong length in headers
+        self.assertRaises(AssertionError, format_comparison_bars_to_js, [],
+            self.comparison_coords_data, 3)
+
+        # assertion for wrong length in coords data
+        self.assertRaises(AssertionError, format_comparison_bars_to_js,
+            self.comparison_coords_headers, self.comparison_coords_data[1::], 3)
+
+        # assertion for wrong number of clones and elements
+        self.assertRaises(AssertionError, format_comparison_bars_to_js,
+            self.comparison_coords_headers, self.comparison_coords_data, 11)
+
+    def test_format_emperor_html_footer_string(self):
+        """Test correct formatting of the footer string"""
+        # footer for a jackknifed pcoa plot without biplots
+        out_string = format_emperor_html_footer_string(False, True)
+        self.assertEqual(out_string, EXPECTED_FOOTER_A)
+
+        # footer for biplots without jackknifing
+        out_string = format_emperor_html_footer_string(True, False)
+        self.assertEqual(out_string, EXPECTED_FOOTER_B)
+
+        # no biplots nor jackknifing
+        out_string = format_emperor_html_footer_string(False, False)
+        self.assertEqual(out_string, EXPECTED_FOOTER_C)
+
+        #  no biplots no jackknifing but with vectors
+        out_string = format_emperor_html_footer_string(False, False, True)
+        self.assertEqual(out_string, EXPECTED_FOOTER_D)
+
+        # comparison plot
+        out_string = format_emperor_html_footer_string(False, False, False,True)
+        self.assertEqual(out_string, EXPECTED_FOOTER_E)
+
+
+    def test_format_emperor_autograph(self):
+        """Test signatures are created correctly for each of language"""
+
+        autograph = format_emperor_autograph('mapping_file.txt',
+            'pcoa_unweighted_unifrac.txt')
+
+        # check for comment open and comment close
+        self.assertTrue('<!--' in autograph)
+        self.assertTrue('-->' in autograph)
+        # check for fields since we cannot check for the specifics
+        self.assertTrue("*Summary of Emperor's Information*" in autograph)
+        self.assertTrue('Metadata:' in autograph)
+        self.assertTrue('Coordinates:' in autograph)
+        self.assertTrue('HostName:' in autograph)
+        self.assertTrue("Command:" in autograph)
+        self.assertTrue("Emperor Version: " in autograph)
+        self.assertTrue("QIIME Version: " in autograph)
+        self.assertTrue("Command executed on " in autograph)
+
+        autograph = format_emperor_autograph('mapping_file.txt',
+            'pcoa_unweighted_unifrac.txt','Python')
+        # check for comment open and comment close
+        self.assertTrue('"""' in autograph)
+        self.assertTrue('"""' in autograph)
+        # check for fields since we cannot check for the specifics
+        self.assertTrue("*Summary of Emperor's Information*" in autograph)
+        self.assertTrue('Metadata:' in autograph)
+        self.assertTrue('Coordinates:' in autograph)
+        self.assertTrue('HostName:' in autograph)
+        self.assertTrue("Command:" in autograph)
+        self.assertTrue("Emperor Version: " in autograph)
+        self.assertTrue("QIIME Version: " in autograph)
+        self.assertTrue("Command executed on " in autograph)
+
+        autograph = format_emperor_autograph('mapping_file.txt',
+            'pcoa_unweighted_unifrac.txt','C')
+        # check for comment open and comment close
+        self.assertTrue('/*' in autograph)
+        self.assertTrue('*/' in autograph)
+        # check for fields since we cannot check for the specifics
+        self.assertTrue("*Summary of Emperor's Information*" in autograph)
+        self.assertTrue('Metadata:' in autograph)
+        self.assertTrue('Coordinates:' in autograph)
+        self.assertTrue('HostName:' in autograph)
+        self.assertTrue("Command:" in autograph)
+        self.assertTrue("Emperor Version: " in autograph)
+        self.assertTrue("QIIME Version: " in autograph)
+        self.assertTrue("Command executed on " in autograph)
+
+        autograph = format_emperor_autograph('mapping_file.txt',
+            'pcoa_unweighted_unifrac.txt','Bash')
+        # check for comment open and comment close
+        self.assertTrue('<<COMMENT' in autograph)
+        self.assertTrue('COMMENT' in autograph)
+        # check for fields since we cannot check for the specifics
+        self.assertTrue("*Summary of Emperor's Information*" in autograph)
+        self.assertTrue('Metadata:' in autograph)
+        self.assertTrue('Coordinates:' in autograph)
+        self.assertTrue('HostName:' in autograph)
+        self.assertTrue("Command:" in autograph)
+        self.assertTrue("Emperor Version: " in autograph)
+        self.assertTrue("QIIME Version: " in autograph)
+        self.assertTrue("Command executed on " in autograph)
+
+        # haskell and cobol are ... not supported
+        self.assertRaises(AssertionError, format_emperor_autograph,
+            'mapping_file.txt', 'pcoa.txt', 'Haskell')
+        self.assertRaises(AssertionError, format_emperor_autograph,
+            'mapping_file.txt', 'pcoa.txt', 'Cobol')
+        
+
+PCOA_DATA = array([[ -1.09166142e-01, 8.77774496e-02, 1.15866606e-02, -6.26863896e-02, 2.31533068e-02, 8.76934639e-02, 1.37400927e-03, -1.35496063e-05, 1.29849404e-09],
+[6.88959784e-02, -1.66234067e-01, -9.98300962e-02, -2.90522450e-02, 5.05569953e-02, -2.95200038e-03, -3.25863204e-02, -2.17218431e-02, 1.29849404e-09],
+[2.04684540e-01, 1.28911236e-01, -2.93614192e-02, 1.07657904e-01, 1.78480761e-02, 7.97778676e-03, -2.92003235e-02, -1.23468947e-03, 1.29849404e-09],
+[1.26131510e-01, -2.66030272e-03, -1.41717093e-01, -9.71089687e-03, -6.94272590e-02, 3.67235068e-03, 4.29867599e-02, 6.44276242e-03, 1.29849404e-09],
+[9.68466168e-02, -1.59388265e-01, 1.35271607e-01, 5.12015857e-02, -2.02552984e-02, 3.07034843e-02, 1.55159338e-02, 1.42426937e-02, 1.29849404e-09],
+[2.81534642e-01, 7.10660196e-02, 9.71542020e-02, -8.06472757e-02, 7.04245456e-03, -4.53133767e-02, 6.55825124e-03, -1.26412251e-02, 1.29849404e-09],
+[-1.92382819e-01, 1.47832029e-02, -1.47871039e-02, 1.90888050e-02, 7.26409669e-02, -3.73008815e-02, 3.94304860e-02, 3.25351917e-02, 1.29849404e-09],
+[-2.93353176e-01, 1.83956004e-02, 3.29884266e-02, 3.15360631e-02, -2.86943531e-02, -1.94225139e-02, 8.06272805e-03, -5.58094095e-02, 1.29849404e-09],
+[-1.83191151e-01, 34912621e-03, 8.69481594e-03, -2.73875510e-02, -5.28648893e-02, -2.50583131e-02, -5.21415245e-02, 3.82000689e-02, 1.29849404e-09]])
+
+PCOA_JS = """
+var g_spherePositions = new Array();
+g_spherePositions['PC.355'] = { 'name': 'PC.355', 'color': 0, 'x': -0.109166, 'y': 0.087777, 'z': 0.011587, 'P1': -0.109166, 'P2': 0.087777, 'P3': 0.011587, 'P4': -0.062686, 'P5': 0.023153, 'P6': 0.087693, 'P7': 0.001374, 'P8': -0.000014 };
+g_spherePositions['PC.607'] = { 'name': 'PC.607', 'color': 0, 'x': 0.068896, 'y': -0.166234, 'z': -0.099830, 'P1': 0.068896, 'P2': -0.166234, 'P3': -0.099830, 'P4': -0.029052, 'P5': 0.050557, 'P6': -0.002952, 'P7': -0.032586, 'P8': -0.021722 };
+g_spherePositions['PC.634'] = { 'name': 'PC.634', 'color': 0, 'x': 0.204685, 'y': 0.128911, 'z': -0.029361, 'P1': 0.204685, 'P2': 0.128911, 'P3': -0.029361, 'P4': 0.107658, 'P5': 0.017848, 'P6': 0.007978, 'P7': -0.029200, 'P8': -0.001235 };
+g_spherePositions['PC.635'] = { 'name': 'PC.635', 'color': 0, 'x': 0.126132, 'y': -0.002660, 'z': -0.141717, 'P1': 0.126132, 'P2': -0.002660, 'P3': -0.141717, 'P4': -0.009711, 'P5': -0.069427, 'P6': 0.003672, 'P7': 0.042987, 'P8': 0.006443 };
+g_spherePositions['PC.593'] = { 'name': 'PC.593', 'color': 0, 'x': 0.096847, 'y': -0.159388, 'z': 0.135272, 'P1': 0.096847, 'P2': -0.159388, 'P3': 0.135272, 'P4': 0.051202, 'P5': -0.020255, 'P6': 0.030703, 'P7': 0.015516, 'P8': 0.014243 };
+g_spherePositions['PC.636'] = { 'name': 'PC.636', 'color': 0, 'x': 0.281535, 'y': 0.071066, 'z': 0.097154, 'P1': 0.281535, 'P2': 0.071066, 'P3': 0.097154, 'P4': -0.080647, 'P5': 0.007042, 'P6': -0.045313, 'P7': 0.006558, 'P8': -0.012641 };
+g_spherePositions['PC.481'] = { 'name': 'PC.481', 'color': 0, 'x': -0.192383, 'y': 0.014783, 'z': -0.014787, 'P1': -0.192383, 'P2': 0.014783, 'P3': -0.014787, 'P4': 0.019089, 'P5': 0.072641, 'P6': -0.037301, 'P7': 0.039430, 'P8': 0.032535 };
+g_spherePositions['PC.354'] = { 'name': 'PC.354', 'color': 0, 'x': -0.293353, 'y': 0.018396, 'z': 0.032988, 'P1': -0.293353, 'P2': 0.018396, 'P3': 0.032988, 'P4': 0.031536, 'P5': -0.028694, 'P6': -0.019423, 'P7': 0.008063, 'P8': -0.055809 };
+g_spherePositions['PC.356'] = { 'name': 'PC.356', 'color': 0, 'x': -0.183191, 'y': 34912.621000, 'z': 0.008695, 'P1': -0.183191, 'P2': 34912.621000, 'P3': 0.008695, 'P4': -0.027388, 'P5': -0.052865, 'P6': -0.025058, 'P7': -0.052142, 'P8': 0.038200 };
+
+var g_ellipsesDimensions = new Array();
+var g_segments = 8, g_rings = 8, g_radius = 0.006899;
+var g_xAxisLength = 0.574888;
+var g_yAxisLength = 34912.787234;
+var g_zAxisLength = 0.276989;
+var g_xMaximumValue = 0.281535;
+var g_yMaximumValue = 34912.621000;
+var g_zMaximumValue = 0.135272;
+var g_xMinimumValue = -0.293353;
+var g_yMinimumValue = -0.166234;
+var g_zMinimumValue = -0.141717;
+var g_maximum = 34912.621000;
+var g_pc1Label = "PC1 (27 %)";
+var g_pc2Label = "PC2 (16 %)";
+var g_pc3Label = "PC3 (14 %)";
+var g_number_of_custom_axes = 0;
+var g_fractionExplained = [0.266887, 0.162564, 0.137754, 0.112172, 0.100248, 0.082284, 0.075597, 0.062495];
+var g_fractionExplainedRounded = [27, 16, 14, 11, 10, 8, 8, 6];
+"""
+
+PCOA_JS_CUSTOM_AXES = """
+var g_spherePositions = new Array();
+g_spherePositions['PC.355'] = { 'name': 'PC.355', 'color': 0, 'x': -0.109166, 'y': 0.087777, 'z': 0.011587, 'P1': -0.109166, 'P2': 0.087777, 'P3': 0.011587, 'P4': -0.062686, 'P5': 0.023153, 'P6': 0.087693, 'P7': 0.001374, 'P8': -0.000014 };
+g_spherePositions['PC.607'] = { 'name': 'PC.607', 'color': 0, 'x': 0.068896, 'y': -0.166234, 'z': -0.099830, 'P1': 0.068896, 'P2': -0.166234, 'P3': -0.099830, 'P4': -0.029052, 'P5': 0.050557, 'P6': -0.002952, 'P7': -0.032586, 'P8': -0.021722 };
+g_spherePositions['PC.634'] = { 'name': 'PC.634', 'color': 0, 'x': 0.204685, 'y': 0.128911, 'z': -0.029361, 'P1': 0.204685, 'P2': 0.128911, 'P3': -0.029361, 'P4': 0.107658, 'P5': 0.017848, 'P6': 0.007978, 'P7': -0.029200, 'P8': -0.001235 };
+g_spherePositions['PC.635'] = { 'name': 'PC.635', 'color': 0, 'x': 0.126132, 'y': -0.002660, 'z': -0.141717, 'P1': 0.126132, 'P2': -0.002660, 'P3': -0.141717, 'P4': -0.009711, 'P5': -0.069427, 'P6': 0.003672, 'P7': 0.042987, 'P8': 0.006443 };
+g_spherePositions['PC.593'] = { 'name': 'PC.593', 'color': 0, 'x': 0.096847, 'y': -0.159388, 'z': 0.135272, 'P1': 0.096847, 'P2': -0.159388, 'P3': 0.135272, 'P4': 0.051202, 'P5': -0.020255, 'P6': 0.030703, 'P7': 0.015516, 'P8': 0.014243 };
+g_spherePositions['PC.636'] = { 'name': 'PC.636', 'color': 0, 'x': 0.281535, 'y': 0.071066, 'z': 0.097154, 'P1': 0.281535, 'P2': 0.071066, 'P3': 0.097154, 'P4': -0.080647, 'P5': 0.007042, 'P6': -0.045313, 'P7': 0.006558, 'P8': -0.012641 };
+g_spherePositions['PC.481'] = { 'name': 'PC.481', 'color': 0, 'x': -0.192383, 'y': 0.014783, 'z': -0.014787, 'P1': -0.192383, 'P2': 0.014783, 'P3': -0.014787, 'P4': 0.019089, 'P5': 0.072641, 'P6': -0.037301, 'P7': 0.039430, 'P8': 0.032535 };
+g_spherePositions['PC.354'] = { 'name': 'PC.354', 'color': 0, 'x': -0.293353, 'y': 0.018396, 'z': 0.032988, 'P1': -0.293353, 'P2': 0.018396, 'P3': 0.032988, 'P4': 0.031536, 'P5': -0.028694, 'P6': -0.019423, 'P7': 0.008063, 'P8': -0.055809 };
+g_spherePositions['PC.356'] = { 'name': 'PC.356', 'color': 0, 'x': -0.183191, 'y': 34912.621000, 'z': 0.008695, 'P1': -0.183191, 'P2': 34912.621000, 'P3': 0.008695, 'P4': -0.027388, 'P5': -0.052865, 'P6': -0.025058, 'P7': -0.052142, 'P8': 0.038200 };
+
+var g_ellipsesDimensions = new Array();
+var g_segments = 8, g_rings = 8, g_radius = 0.006899;
+var g_xAxisLength = 0.574888;
+var g_yAxisLength = 34912.787234;
+var g_zAxisLength = 0.276989;
+var g_xMaximumValue = 0.281535;
+var g_yMaximumValue = 34912.621000;
+var g_zMaximumValue = 0.135272;
+var g_xMinimumValue = -0.293353;
+var g_yMinimumValue = -0.166234;
+var g_zMinimumValue = -0.141717;
+var g_maximum = 34912.621000;
+var g_pc1Label = "Instant";
+var g_pc2Label = "PC1 (27 %)";
+var g_pc3Label = "PC2 (16 %)";
+var g_number_of_custom_axes = 1;
+var g_fractionExplained = [0.266887, 0.266887, 0.162564, 0.137754, 0.112172, 0.100248, 0.082284, 0.075597, 0.062495];
+var g_fractionExplainedRounded = [27, 27, 16, 14, 11, 10, 8, 8, 6];
+"""
+
+PCOA_JS_JACKKNIFED = """
+var g_spherePositions = new Array();
+g_spherePositions[\'PC.355\'] = { \'name\': \'PC.355\', \'color\': 0, \'x\': 0.300000, \'y\': 0.500000, \'z\': 0.100000, \'P1\': 0.300000, \'P2\': 0.500000, \'P3\': 0.100000, \'P4\': 0.300000 };
+g_spherePositions[\'PC.607\'] = { \'name\': \'PC.607\', \'color\': 0, \'x\': 1.100000, \'y\': 1.100000, \'z\': 1.000000, \'P1\': 1.100000, \'P2\': 1.100000, \'P3\': 1.000000, \'P4\': 0.800000 };
+g_spherePositions[\'PC.634\'] = { \'name\': \'PC.634\', \'color\': 0, \'x\': 0.100000, \'y\': 3.300000, \'z\': 5.500000, \'P1\': 0.100000, \'P2\': 3.300000, \'P3\': 5.500000, \'P4\': 0.100000 };
+g_spherePositions[\'PC.635\'] = { \'name\': \'PC.635\', \'color\': 0, \'x\': 1.000000, \'y\': 2.000000, \'z\': 1.000000, \'P1\': 1.000000, \'P2\': 2.000000, \'P3\': 1.000000, \'P4\': 1.000000 };
+
+var g_ellipsesDimensions = new Array();
+g_ellipsesDimensions[\'PC.355\'] = { \'name\': \'PC.355\', \'color\': 0, \'width\': 0.400000, \'height\': 0.500000, \'length\': 0.800000 , \'x\': 0.300000, \'y\': 0.500000, \'z\': 0.100000, \'P1\': 0.300000, \'P2\': 0.500000, \'P3\': 0.100000, \'P4\': 0.300000 }
+g_ellipsesDimensions[\'PC.607\'] = { \'name\': \'PC.607\', \'color\': 0, \'width\': 0.100000, \'height\': 2.000000, \'length\': 0.000000 , \'x\': 1.100000, \'y\': 1.100000, \'z\': 1.000000, \'P1\': 1.100000, \'P2\': 1.100000, \'P3\': 1.000000, \'P4\': 0.800000 }
+g_ellipsesDimensions[\'PC.634\'] = { \'name\': \'PC.634\', \'color\': 0, \'width\': 0.300000, \'height\': 0.600000, \'length\': 4.000000 , \'x\': 0.100000, \'y\': 3.300000, \'z\': 5.500000, \'P1\': 0.100000, \'P2\': 3.300000, \'P3\': 5.500000, \'P4\': 0.100000 }
+g_ellipsesDimensions[\'PC.635\'] = { \'name\': \'PC.635\', \'color\': 0, \'width\': 0.010780, \'height\': 1.000000, \'length\': 0.023000 , \'x\': 1.000000, \'y\': 2.000000, \'z\': 1.000000, \'P1\': 1.000000, \'P2\': 2.000000, \'P3\': 1.000000, \'P4\': 1.000000 }
+var g_segments = 8, g_rings = 8, g_radius = 0.012000;
+var g_xAxisLength = 1.200000;
+var g_yAxisLength = 3.800000;
+var g_zAxisLength = 5.600000;
+var g_xMaximumValue = 1.100000;
+var g_yMaximumValue = 3.300000;
+var g_zMaximumValue = 5.500000;
+var g_xMinimumValue = 0.100000;
+var g_yMinimumValue = 0.500000;
+var g_zMinimumValue = 0.100000;
+var g_maximum = 5.500000;
+var g_pc1Label = "PC1 (44 %)";
+var g_pc2Label = "PC2 (40 %)";
+var g_pc3Label = "PC3 (15 %)";
+var g_number_of_custom_axes = 0;
+var g_fractionExplained = [0.440000, 0.400000, 0.150000, 0.010000];
+var g_fractionExplainedRounded = [44, 40, 15, 1];
+"""
+
+PCOA_JS_SEGMENTS = """
+var g_spherePositions = new Array();
+g_spherePositions[\'PC.355\'] = { \'name\': \'PC.355\', \'color\': 0, \'x\': 0.300000, \'y\': 0.500000, \'z\': 0.100000, \'P1\': 0.300000, \'P2\': 0.500000, \'P3\': 0.100000, \'P4\': 0.300000 };
+g_spherePositions[\'PC.607\'] = { \'name\': \'PC.607\', \'color\': 0, \'x\': 1.100000, \'y\': 1.100000, \'z\': 1.000000, \'P1\': 1.100000, \'P2\': 1.100000, \'P3\': 1.000000, \'P4\': 0.800000 };
+g_spherePositions[\'PC.634\'] = { \'name\': \'PC.634\', \'color\': 0, \'x\': 0.100000, \'y\': 3.300000, \'z\': 5.500000, \'P1\': 0.100000, \'P2\': 3.300000, \'P3\': 5.500000, \'P4\': 0.100000 };
+g_spherePositions[\'PC.635\'] = { \'name\': \'PC.635\', \'color\': 0, \'x\': 1.000000, \'y\': 2.000000, \'z\': 1.000000, \'P1\': 1.000000, \'P2\': 2.000000, \'P3\': 1.000000, \'P4\': 1.000000 };
+
+var g_ellipsesDimensions = new Array();
+g_ellipsesDimensions[\'PC.355\'] = { \'name\': \'PC.355\', \'color\': 0, \'width\': 0.400000, \'height\': 0.500000, \'length\': 0.800000 , \'x\': 0.300000, \'y\': 0.500000, \'z\': 0.100000, \'P1\': 0.300000, \'P2\': 0.500000, \'P3\': 0.100000, \'P4\': 0.300000 }
+g_ellipsesDimensions[\'PC.607\'] = { \'name\': \'PC.607\', \'color\': 0, \'width\': 0.100000, \'height\': 2.000000, \'length\': 0.000000 , \'x\': 1.100000, \'y\': 1.100000, \'z\': 1.000000, \'P1\': 1.100000, \'P2\': 1.100000, \'P3\': 1.000000, \'P4\': 0.800000 }
+g_ellipsesDimensions[\'PC.634\'] = { \'name\': \'PC.634\', \'color\': 0, \'width\': 0.300000, \'height\': 0.600000, \'length\': 4.000000 , \'x\': 0.100000, \'y\': 3.300000, \'z\': 5.500000, \'P1\': 0.100000, \'P2\': 3.300000, \'P3\': 5.500000, \'P4\': 0.100000 }
+g_ellipsesDimensions[\'PC.635\'] = { \'name\': \'PC.635\', \'color\': 0, \'width\': 0.010780, \'height\': 1.000000, \'length\': 0.023000 , \'x\': 1.000000, \'y\': 2.000000, \'z\': 1.000000, \'P1\': 1.000000, \'P2\': 2.000000, \'P3\': 1.000000, \'P4\': 1.000000 }
+var g_segments = 14, g_rings = 14, g_radius = 0.012000;
+var g_xAxisLength = 1.200000;
+var g_yAxisLength = 3.800000;
+var g_zAxisLength = 5.600000;
+var g_xMaximumValue = 1.100000;
+var g_yMaximumValue = 3.300000;
+var g_zMaximumValue = 5.500000;
+var g_xMinimumValue = 0.100000;
+var g_yMinimumValue = 0.500000;
+var g_zMinimumValue = 0.100000;
+var g_maximum = 5.500000;
+var g_pc1Label = "PC1 (44 %)";
+var g_pc2Label = "PC2 (40 %)";
+var g_pc3Label = "PC3 (15 %)";
+var g_number_of_custom_axes = 0;
+var g_fractionExplained = [0.440000, 0.400000, 0.150000, 0.010000];
+var g_fractionExplainedRounded = [44, 40, 15, 1];
+"""
+
+MAPPING_FILE_DATA = [\
+    ['PC.354','AGCACGAGCCTA','YATGCTGCCTCCCGTAGGAGT','Control','20061218','Control_mouse_I.D._354'],
+    ['PC.355','AACTCGTCGATG','YATGCTGCCTCCCGTAGGAGT','Control','20061218','Control_mouse_I.D._355'],
+    ['PC.356','ACAGACCACTCA','YATGCTGCCTCCCGTAGGAGT','Control','20061126','Control_mouse_I.D._356'],
+    ['PC.481','ACCAGCGACTAG','YATGCTGCCTCCCGTAGGAGT','Control','20070314','Control_mouse_I.D._481'],
+    ['PC.593','AGCAGCACTTGT','YATGCTGCCTCCCGTAGGAGT','Control','20071210','Control_mouse_I.D._593'],
+    ['PC.607','AACTGTGCGTAC','YATGCTGCCTCCCGTAGGAGT','Fast','20071112','Fasting_mouse_I.D._607'],
+    ['PC.634','ACAGAGTCGGCT','YATGCTGCCTCCCGTAGGAGT','Fast','20080116','Fasting_mouse_I.D._634'],
+    ['PC.635','ACCGCAGAGTCA','YATGCTGCCTCCCGTAGGAGT','Fast','20080116','Fasting_mouse_I.D._635'],
+    ['PC.636','ACGGTGAGTGTC','YATGCTGCCTCCCGTAGGAGT','Fast','20080116','Fasting_mouse_I.D._636']]
+
+
+MAPPING_FILE_JS = """var g_mappingFileHeaders = ['BarcodeSequence','LinkerPrimerSequence','Treatment','DOB','Description'];\nvar g_mappingFileData = { 'PC.636': ['ACGGTGAGTGTC','YATGCTGCCTCCCGTAGGAGT','Fast','20080116','Fasting_mouse_I.D._636'],'PC.355': ['AACTCGTCGATG','YATGCTGCCTCCCGTAGGAGT','Control','20061218','Control_mouse_I.D._355'],'PC.607': ['AACTGTGCGTAC','YATGCTGCCTCCCGTAGGAGT','Fast','20071112','Fasting_mouse_I.D._607'],'PC.634': ['ACAGAGTCGGCT','YATGCTGCCTCCCGTAGGAGT','Fast' [...]
+
+TAXA_JS_STRING = """
+var g_taxaPositions = new Array();
+g_taxaPositions['0'] = { 'lineage': 'Root;k__Bacteria;p__Firmicutes', 'x': 0.280399, 'y': -0.006013, 'z': 0.023485, 'radius': 5.000000};
+g_taxaPositions['1'] = { 'lineage': 'Root;k__Bacteria;p__Bacteroidetes', 'x': 0.228820, 'y': -0.130142, 'z': -0.287149, 'radius': 3.491237};
+g_taxaPositions['2'] = { 'lineage': 'Root;k__Bacteria;p__Tenericutes', 'x': -0.091330, 'y': 0.424147, 'z': -0.135627, 'radius': 0.868694};
+g_taxaPositions['3'] = { 'lineage': 'Root;k__Bacteria;Other', 'x': -0.276542, 'y': -0.144964, 'z': 0.066647, 'radius': 0.696843};
+
+"""
+
+VECTOR_JS_STRING_NO_SORTING = """
+var g_vectorPositions = new Array();
+g_vectorPositions['Control'] = new Array();
+g_vectorPositions['Control']['PC.354'] = [-0.293353176, 0.0183956004, 0.0329884266];
+g_vectorPositions['Control']['PC.355'] = [-0.109166142, 0.0877774496, 0.0115866606];
+g_vectorPositions['Control']['PC.356'] = [-0.183191151, 34912.621, 0.00869481594];
+g_vectorPositions['Control']['PC.481'] = [-0.192382819, 0.0147832029, -0.0147871039];
+g_vectorPositions['Control']['PC.593'] = [0.0968466168, -0.159388265, 0.135271607];
+g_vectorPositions['Fast'] = new Array();
+g_vectorPositions['Fast']['PC.607'] = [0.0688959784, -0.166234067, -0.0998300962];
+g_vectorPositions['Fast']['PC.634'] = [0.20468454, 0.128911236, -0.0293614192];
+g_vectorPositions['Fast']['PC.635'] = [0.12613151, -0.00266030272, -0.141717093];
+g_vectorPositions['Fast']['PC.636'] = [0.281534642, 0.0710660196, 0.097154202];
+"""
+
+VECTOR_JS_STRING_SORTING = """
+var g_vectorPositions = new Array();
+g_vectorPositions['Control'] = new Array();
+g_vectorPositions['Control']['PC.356'] = [-0.183191151, 34912.621, 0.00869481594];
+g_vectorPositions['Control']['PC.354'] = [-0.293353176, 0.0183956004, 0.0329884266];
+g_vectorPositions['Control']['PC.355'] = [-0.109166142, 0.0877774496, 0.0115866606];
+g_vectorPositions['Control']['PC.481'] = [-0.192382819, 0.0147832029, -0.0147871039];
+g_vectorPositions['Control']['PC.593'] = [0.0968466168, -0.159388265, 0.135271607];
+g_vectorPositions['Fast'] = new Array();
+g_vectorPositions['Fast']['PC.607'] = [0.0688959784, -0.166234067, -0.0998300962];
+g_vectorPositions['Fast']['PC.634'] = [0.20468454, 0.128911236, -0.0293614192];
+g_vectorPositions['Fast']['PC.635'] = [0.12613151, -0.00266030272, -0.141717093];
+g_vectorPositions['Fast']['PC.636'] = [0.281534642, 0.0710660196, 0.097154202];
+"""
+
+COMPARISON_JS_STRING = """
+var g_comparisonPositions = new Array();
+var g_isSerialComparisonPlot = true;
+g_comparisonPositions['sampa'] = [[-0.0677, -2.036, 0.2726], [-0.972, 0.551, 1.144], [0.2339, -0.88, -1.753]];
+g_comparisonPositions['sampb'] = [[-1.782, -0.972, 0.1582], [1.438, -2.603, -1.39], [0.436, 2.12, -0.935]];
+g_comparisonPositions['sampc'] = [[-0.659, -0.2566, 0.514], [-0.356, 0.0875, 0.772], [-0.88, 1.069, 1.069]];
+g_comparisonPositions['sampd'] = [[-1.179, -0.968, 2.525], [1.512, -1.239, -0.0365], [0.294, 0.2988, 0.0467]];
+g_comparisonPositions['sampe'] = [[-0.896, -1.765, 0.274], [1.17, 1.31, -1.407], [1.64, 0.2485, -0.354]];
+g_comparisonPositions['sampf'] = [[-0.0923, 1.414, -0.622], [2.618, 0.739, -0.01295], [0.821, -1.13, -1.794]];
+"""
+
+COMPARISON_JS_STRING_NON_SERIAL = """
+var g_comparisonPositions = new Array();
+var g_isSerialComparisonPlot = false;
+g_comparisonPositions['sampa'] = [[-0.0677, -2.036, 0.2726], [-0.972, 0.551, 1.144], [0.2339, -0.88, -1.753]];
+g_comparisonPositions['sampb'] = [[-1.782, -0.972, 0.1582], [1.438, -2.603, -1.39], [0.436, 2.12, -0.935]];
+g_comparisonPositions['sampc'] = [[-0.659, -0.2566, 0.514], [-0.356, 0.0875, 0.772], [-0.88, 1.069, 1.069]];
+g_comparisonPositions['sampd'] = [[-1.179, -0.968, 2.525], [1.512, -1.239, -0.0365], [0.294, 0.2988, 0.0467]];
+g_comparisonPositions['sampe'] = [[-0.896, -1.765, 0.274], [1.17, 1.31, -1.407], [1.64, 0.2485, -0.354]];
+g_comparisonPositions['sampf'] = [[-0.0923, 1.414, -0.622], [2.618, 0.739, -0.01295], [0.821, -1.13, -1.794]];
+"""
+
+COMPARISON_COORDS_HEADERS_ZERO = """
+var g_comparisonPositions = new Array();
+var g_isSerialComparisonPlot = false;
+g_comparisonPositions['sampa0'] = [[-0.0677, -2.036, 0.2726], [-0.972, 0.551, 1.144], [0.2339, -0.88, -1.753]];
+g_comparisonPositions['sampb0'] = [[-1.782, -0.972, 0.1582], [1.438, -2.603, -1.39], [0.436, 2.12, -0.935]];
+g_comparisonPositions['sampc0'] = [[-0.659, -0.2566, 0.514], [-0.356, 0.0875, 0.772], [-0.88, 1.069, 1.069]];
+g_comparisonPositions['sampd00'] = [[-1.179, -0.968, 2.525], [1.512, -1.239, -0.0365], [0.294, 0.2988, 0.0467]];
+g_comparisonPositions['sampe00'] = [[-0.896, -1.765, 0.274], [1.17, 1.31, -1.407], [1.64, 0.2485, -0.354]];
+g_comparisonPositions['sampf00'] = [[-0.0923, 1.414, -0.622], [2.618, 0.739, -0.01295], [0.821, -1.13, -1.794]];
+"""
+
+EXPECTED_FOOTER_A =\
+"""document.getElementById("logo").style.display = 'none';
+document.getElementById("logotable").style.display = 'none';
+
+ </script>
+</head>
+
+<body>
+
+<div id="overlay">
+    <div>
+    <img src="emperor_required_resources/img/emperor.png" alt="Emperor" id="smalllogo"/>
+        <h1>WebGL is not enabled!</h1>
+        <p>Emperor's visualization framework is WebGL based, it seems that your system doesn't have this resource available. Here is what you can do:</p>
+        <p id="explanation"><strong>Chrome:</strong> Type "chrome://flags/" into the address bar, then search for "Disable WebGL". Disable this option if you haven't already. <em>Note:</em> If you follow these steps and still don't see an image, go to "chrome://flags/" and then search for "Override software rendering list" and enable this option.</p>
+        <p id="explanation"><strong>Safari:</strong> Open Safari's menu and select Preferences. Click on the advanced tab, and then check "Show Developer" menu. Then open the "Developer" menu and select "Enable WebGL".</p>
+        <p id="explanation"><strong>Firefox:</strong> Go to Options through Firefox > Options or Tools > Options. Go to Advanced, then General. Check "Use hardware acceleration when available" and restart Firefox.</p>
+        <p id="explanation"><strong>Other browsers:</strong> The only browsers that support WebGL are Chrome, Safari, and Firefox. Please switch to these browsers when using Emperor.</p>
+        <p id="explanation"><em>Note:</em> Once you went through these changes, reload the page and it should work!</p>
+        <p id="source">Sources: Instructions for <a href="https://www.biodigitalhuman.com/home/enabling-webgl.html">Chrome and Safari</a>, and <a href="http://www.infewbytes.com/?p=144">Firefox</a></p>
+    </div>
+</div>
+
+<div id="plotToggle">
+    <form>
+      <div id="plottype">
+        <input id="pcoa" type="radio" id="pcoa" name="plottype" checked="checked" /><label for="pcoa">PCoA</label>
+        <input id="parallel" type="radio" id="parallel" name="plottype" /><label for="parallel">Parallel</label>
+      </div>
+    </form>
+</div>
+<div id="pcoaPlotWrapper" class="plotWrapper">
+    <label id="pointCount" class="ontop">
+    </label>
+
+    <div id="finder" class="arrow-right">
+    </div>
+
+    <div id="labels" class="unselectable">
+    </div>
+
+    <div id="taxalabels" class="unselectable">
+    </div>
+
+    <div id="axislabels" class="axislabels">
+    </div>
+
+    <div id="main_plot">
+    </div>
+</div>
+
+<div id="parallelPlotWrapper" class="plotWrapper">
+</div>
+
+<div class="separator" ondblclick="separatorDoubleClick()"></div>
+
+<div id="menu">
+    <div id="menutabs">
+        <ul>
+            <li><a href="#keytab">Key</a></li>
+            <li><a href="#colorby">Colors</a></li>
+            <li><a href="#showby">Visibility</a></li>
+            <li><a href="#scalingby">Scaling</a></li>
+            <li><a href="#labelby">Labels</a></li>
+            <li><a href="#axes">Axes</a></li>
+            <li><a href="#options">Options</a></li>
+        </ul>
+        <div id="keytab">
+            <form name="keyFilter">
+            <label>Filter  </label><input name="filterBox" id="searchBox" type="text" onkeyup="filterKey()"></input>
+            </form>
+            <div id="key">
+            </div>
+        </div>
+        <div id="colorby">
+            <br>
+            <input type="checkbox" onchange="toggleContinuousAndDiscreteColors(this)" id="discreteorcontinuouscolors" name="discreteorcontinuouscolors">  Use gradient colors</input>
+            <br><br>
+            <select id="colorbycombo" onchange="colorByMenuChanged()" size="3">
+            </select>
+            <div class="list" id="colorbylist">
+            </div>
+        </div>
+        <div id="showby" align="center">
+            <table width="100%">
+                <tr>
+                    <td align="center">
+                        <select id="showbycombo" onchange="showByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="showbylist" style="height:100%;width:100%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereopacity" class="text">Global Sphere Opacity</label>
+                        <label id="sphereopacity" class="slidervalue"></label>
+                        <div id="sopacityslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="scalingby" align="center">
+            <table width="100%">
+                <tr>
+                    <td align="center">
+                        <select id="scalingbycombo" onchange="scalingByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="scalingbylist" style="height:100%;width:100%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereradius" class="text">Global Sphere Scale</label>
+                        <label id="sphereradius" class="slidervalue"></label>
+                        <div id="sradiusslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="labelby">
+        <div id="labelsTop">
+            <form name="plotoptions">
+            <input type="checkbox" onClick="toggleLabels()">Samples Label Visibility</input>
+            </form>
+            <br>
+            <label for="labelopacity" class="text">Label Opacity</label>
+            <label id="labelopacity" class="slidervalue"></label>
+            <div id="lopacityslider" class="slider-range-max"></div>
+            <div id="labelColorHolder clearfix">
+            <table>
+                <tr><td><div id="labelColor" class="colorbox"></div></td><td><label>Master Label Color</label></td></tr>
+            </table></div>
+        </div>
+            <br>
+            <select id="labelcombo" onchange="labelMenuChanged()">
+            </select>
+            <div class="list" id="labellist">
+            </div>
+        </div>
+        <div id="axes">
+            <div id="pcoaaxes">
+                <div class="list" id="axeslist">
+                </div>
+            </div>
+        </div>
+        <div id="options">
+            <table>
+                <tr><td><div id="axeslabelscolor" class="colorbox" name="axeslabelscolor"></div></td><td title="Axes Labels Color">Axes Labels Color</td></tr>
+                <tr><td><div id="axescolor" class="colorbox" name="axescolor"></div></td><td title="Axes Color Title">Axes Color</td></tr>
+                <tr><td><div id="rendererbackgroundcolor" class="colorbox" name="rendererbackgroundcolor"></div></td><td title="Background Color Title">Background Color</td></tr>
+            </table>
+            <div id="pcoaviewoptions" class="">
+            <br>
+            <label for="ellipseopacity" class="text">Ellipse Opacity</label>
+            <label id="ellipseopacity" class="slidervalue"></label>
+            <div id="eopacityslider" class="slider-range-max"></div>
+                <form name="settingsoptionscolor">
+                </form>
+                <div id="pcoaoptions" class="">
+                    <form name="settingsoptions">
+                        <input type="checkbox" onchange="toggleScaleCoordinates(this)" id="scale_checkbox" name="scale_checkbox">Scale coords by percent explained</input>
+                    </form>
+                </div>
+                <br><input id="reset" class="button" type="submit" value="Recenter Camera" style="" onClick="resetCamera()">
+                <br><br>
+                <hr class='section-break'>
+                <br>Filename <small>(only letters, numbers, ., - and _)</small>:
+                <br><input name="saveas_name" id="saveas_name" value="screenshot" type="text"/>
+                <br><input id="saveas_legends" class="checkbox" type="checkbox" style=""> Create legend
+                <input id="saveas" class="button" type="submit" value="Save as SVG" style="" onClick="saveSVG()"/>
+                <br><br>For a PNG, simply press 'ctrl+p'.
+                <div id="paralleloptions" class="">
+                </div>
+            </div>
+            <br>
+        </div>
+    </div>  
+</div>
+</body>
+
+</html>
+"""
+
+EXPECTED_FOOTER_B =\
+"""document.getElementById("logo").style.display = 'none';
+document.getElementById("logotable").style.display = 'none';
+
+ </script>
+</head>
+
+<body>
+
+<div id="overlay">
+    <div>
+    <img src="emperor_required_resources/img/emperor.png" alt="Emperor" id="smalllogo"/>
+        <h1>WebGL is not enabled!</h1>
+        <p>Emperor's visualization framework is WebGL based, it seems that your system doesn't have this resource available. Here is what you can do:</p>
+        <p id="explanation"><strong>Chrome:</strong> Type "chrome://flags/" into the address bar, then search for "Disable WebGL". Disable this option if you haven't already. <em>Note:</em> If you follow these steps and still don't see an image, go to "chrome://flags/" and then search for "Override software rendering list" and enable this option.</p>
+        <p id="explanation"><strong>Safari:</strong> Open Safari's menu and select Preferences. Click on the advanced tab, and then check "Show Developer" menu. Then open the "Developer" menu and select "Enable WebGL".</p>
+        <p id="explanation"><strong>Firefox:</strong> Go to Options through Firefox > Options or Tools > Options. Go to Advanced, then General. Check "Use hardware acceleration when available" and restart Firefox.</p>
+        <p id="explanation"><strong>Other browsers:</strong> The only browsers that support WebGL are Chrome, Safari, and Firefox. Please switch to these browsers when using Emperor.</p>
+        <p id="explanation"><em>Note:</em> Once you went through these changes, reload the page and it should work!</p>
+        <p id="source">Sources: Instructions for <a href="https://www.biodigitalhuman.com/home/enabling-webgl.html">Chrome and Safari</a>, and <a href="http://www.infewbytes.com/?p=144">Firefox</a></p>
+    </div>
+</div>
+
+<div id="plotToggle">
+    <form>
+      <div id="plottype">
+        <input id="pcoa" type="radio" id="pcoa" name="plottype" checked="checked" /><label for="pcoa">PCoA</label>
+        <input id="parallel" type="radio" id="parallel" name="plottype" /><label for="parallel">Parallel</label>
+      </div>
+    </form>
+</div>
+<div id="pcoaPlotWrapper" class="plotWrapper">
+    <label id="pointCount" class="ontop">
+    </label>
+
+    <div id="finder" class="arrow-right">
+    </div>
+
+    <div id="labels" class="unselectable">
+    </div>
+
+    <div id="taxalabels" class="unselectable">
+    </div>
+
+    <div id="axislabels" class="axislabels">
+    </div>
+
+    <div id="main_plot">
+    </div>
+</div>
+
+<div id="parallelPlotWrapper" class="plotWrapper">
+</div>
+
+<div class="separator" ondblclick="separatorDoubleClick()"></div>
+
+<div id="menu">
+    <div id="menutabs">
+        <ul>
+            <li><a href="#keytab">Key</a></li>
+            <li><a href="#colorby">Colors</a></li>
+            <li><a href="#showby">Visibility</a></li>
+            <li><a href="#scalingby">Scaling</a></li>
+            <li><a href="#labelby">Labels</a></li>
+            <li><a href="#axes">Axes</a></li>
+            <li><a href="#options">Options</a></li>
+        </ul>
+        <div id="keytab">
+            <form name="keyFilter">
+            <label>Filter  </label><input name="filterBox" id="searchBox" type="text" onkeyup="filterKey()"></input>
+            </form>
+            <div id="key">
+            </div>
+        </div>
+        <div id="colorby">
+            <br>
+            <br>
+            <table>
+                <tr><td><div id="taxaspherescolor" class="colorbox" name="taxaspherescolor"></div></td><td title="taxacolor">Taxa Spheres Color</td></tr>
+            </table>
+            <br>
+            <input type="checkbox" onchange="toggleContinuousAndDiscreteColors(this)" id="discreteorcontinuouscolors" name="discreteorcontinuouscolors">  Use gradient colors</input>
+            <br><br>
+            <select id="colorbycombo" onchange="colorByMenuChanged()" size="3">
+            </select>
+            <div class="list" id="colorbylist">
+            </div>
+        </div>
+        <div id="showby" align="center">
+            <br>
+            <form name="biplotsvisibility">
+            <input type="checkbox" onClick="toggleBiplotVisibility()" checked>Biplots Visibility</input>
+            </form>
+            <br>
+            <table width="100%">
+                <tr>
+                    <td align="center">
+                        <select id="showbycombo" onchange="showByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="showbylist" style="height:100%;width:100%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereopacity" class="text">Global Sphere Opacity</label>
+                        <label id="sphereopacity" class="slidervalue"></label>
+                        <div id="sopacityslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="scalingby" align="center">
+            <table width="100%">
+                <tr>
+                    <td align="center">
+                        <select id="scalingbycombo" onchange="scalingByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="scalingbylist" style="height:100%;width:100%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereradius" class="text">Global Sphere Scale</label>
+                        <label id="sphereradius" class="slidervalue"></label>
+                        <div id="sradiusslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="labelby">
+        <div id="labelsTop">
+            <form name="plotoptions">
+            <input type="checkbox" onClick="toggleLabels()">Samples Label Visibility</input>
+            </form>
+            <form name="biplotoptions">
+            <input type="checkbox" onClick="toggleTaxaLabels()">Biplots Label Visibility</input>
+            </form>
+            <br>
+            <label for="labelopacity" class="text">Label Opacity</label>
+            <label id="labelopacity" class="slidervalue"></label>
+            <div id="lopacityslider" class="slider-range-max"></div>
+            <div id="labelColorHolder clearfix">
+            <table>
+                <tr><td><div id="labelColor" class="colorbox"></div></td><td><label>Master Label Color</label></td></tr>
+            <tr><td><div id="taxalabelcolor" class="colorbox"></div></td><td><label>Taxa Label Color</label></td></tr>
+
+            </table></div>
+        </div>
+            <br>
+            <select id="labelcombo" onchange="labelMenuChanged()">
+            </select>
+            <div class="list" id="labellist">
+            </div>
+        </div>
+        <div id="axes">
+            <div id="pcoaaxes">
+                <div class="list" id="axeslist">
+                </div>
+            </div>
+        </div>
+        <div id="options">
+            <table>
+                <tr><td><div id="axeslabelscolor" class="colorbox" name="axeslabelscolor"></div></td><td title="Axes Labels Color">Axes Labels Color</td></tr>
+                <tr><td><div id="axescolor" class="colorbox" name="axescolor"></div></td><td title="Axes Color Title">Axes Color</td></tr>
+                <tr><td><div id="rendererbackgroundcolor" class="colorbox" name="rendererbackgroundcolor"></div></td><td title="Background Color Title">Background Color</td></tr>
+            </table>
+            <div id="pcoaviewoptions" class="">
+                <form name="settingsoptionscolor">
+                </form>
+                <div id="pcoaoptions" class="">
+                    <form name="settingsoptions">
+                        <input type="checkbox" onchange="toggleScaleCoordinates(this)" id="scale_checkbox" name="scale_checkbox">Scale coords by percent explained</input>
+                    </form>
+                </div>
+                <br><input id="reset" class="button" type="submit" value="Recenter Camera" style="" onClick="resetCamera()">
+                <br><br>
+                <hr class='section-break'>
+                <br>Filename <small>(only letters, numbers, ., - and _)</small>:
+                <br><input name="saveas_name" id="saveas_name" value="screenshot" type="text"/>
+                <br><input id="saveas_legends" class="checkbox" type="checkbox" style=""> Create legend
+                <input id="saveas" class="button" type="submit" value="Save as SVG" style="" onClick="saveSVG()"/>
+                <br><br>For a PNG, simply press 'ctrl+p'.
+                <div id="paralleloptions" class="">
+                </div>
+            </div>
+            <br>
+        </div>
+    </div>  
+</div>
+</body>
+
+</html>
+"""
+
+EXPECTED_FOOTER_C =\
+"""document.getElementById("logo").style.display = 'none';
+document.getElementById("logotable").style.display = 'none';
+
+ </script>
+</head>
+
+<body>
+
+<div id="overlay">
+    <div>
+    <img src="emperor_required_resources/img/emperor.png" alt="Emperor" id="smalllogo"/>
+        <h1>WebGL is not enabled!</h1>
+        <p>Emperor's visualization framework is WebGL based, it seems that your system doesn't have this resource available. Here is what you can do:</p>
+        <p id="explanation"><strong>Chrome:</strong> Type "chrome://flags/" into the address bar, then search for "Disable WebGL". Disable this option if you haven't already. <em>Note:</em> If you follow these steps and still don't see an image, go to "chrome://flags/" and then search for "Override software rendering list" and enable this option.</p>
+        <p id="explanation"><strong>Safari:</strong> Open Safari's menu and select Preferences. Click on the advanced tab, and then check "Show Developer" menu. Then open the "Developer" menu and select "Enable WebGL".</p>
+        <p id="explanation"><strong>Firefox:</strong> Go to Options through Firefox > Options or Tools > Options. Go to Advanced, then General. Check "Use hardware acceleration when available" and restart Firefox.</p>
+        <p id="explanation"><strong>Other browsers:</strong> The only browsers that support WebGL are Chrome, Safari, and Firefox. Please switch to these browsers when using Emperor.</p>
+        <p id="explanation"><em>Note:</em> Once you went through these changes, reload the page and it should work!</p>
+        <p id="source">Sources: Instructions for <a href="https://www.biodigitalhuman.com/home/enabling-webgl.html">Chrome and Safari</a>, and <a href="http://www.infewbytes.com/?p=144">Firefox</a></p>
+    </div>
+</div>
+
+<div id="plotToggle">
+    <form>
+      <div id="plottype">
+        <input id="pcoa" type="radio" id="pcoa" name="plottype" checked="checked" /><label for="pcoa">PCoA</label>
+        <input id="parallel" type="radio" id="parallel" name="plottype" /><label for="parallel">Parallel</label>
+      </div>
+    </form>
+</div>
+<div id="pcoaPlotWrapper" class="plotWrapper">
+    <label id="pointCount" class="ontop">
+    </label>
+
+    <div id="finder" class="arrow-right">
+    </div>
+
+    <div id="labels" class="unselectable">
+    </div>
+
+    <div id="taxalabels" class="unselectable">
+    </div>
+
+    <div id="axislabels" class="axislabels">
+    </div>
+
+    <div id="main_plot">
+    </div>
+</div>
+
+<div id="parallelPlotWrapper" class="plotWrapper">
+</div>
+
+<div class="separator" ondblclick="separatorDoubleClick()"></div>
+
+<div id="menu">
+    <div id="menutabs">
+        <ul>
+            <li><a href="#keytab">Key</a></li>
+            <li><a href="#colorby">Colors</a></li>
+            <li><a href="#showby">Visibility</a></li>
+            <li><a href="#scalingby">Scaling</a></li>
+            <li><a href="#labelby">Labels</a></li>
+            <li><a href="#axes">Axes</a></li>
+            <li><a href="#options">Options</a></li>
+        </ul>
+        <div id="keytab">
+            <form name="keyFilter">
+            <label>Filter  </label><input name="filterBox" id="searchBox" type="text" onkeyup="filterKey()"></input>
+            </form>
+            <div id="key">
+            </div>
+        </div>
+        <div id="colorby">
+            <br>
+            <input type="checkbox" onchange="toggleContinuousAndDiscreteColors(this)" id="discreteorcontinuouscolors" name="discreteorcontinuouscolors">  Use gradient colors</input>
+            <br><br>
+            <select id="colorbycombo" onchange="colorByMenuChanged()" size="3">
+            </select>
+            <div class="list" id="colorbylist">
+            </div>
+        </div>
+        <div id="showby" align="center">
+            <table width="100%">
+                <tr>
+                    <td align="center">
+                        <select id="showbycombo" onchange="showByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="showbylist" style="height:100%;width:100%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereopacity" class="text">Global Sphere Opacity</label>
+                        <label id="sphereopacity" class="slidervalue"></label>
+                        <div id="sopacityslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="scalingby" align="center">
+            <table width="100%">
+                <tr>
+                    <td align="center">
+                        <select id="scalingbycombo" onchange="scalingByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="scalingbylist" style="height:100%;width:100%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereradius" class="text">Global Sphere Scale</label>
+                        <label id="sphereradius" class="slidervalue"></label>
+                        <div id="sradiusslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="labelby">
+        <div id="labelsTop">
+            <form name="plotoptions">
+            <input type="checkbox" onClick="toggleLabels()">Samples Label Visibility</input>
+            </form>
+            <br>
+            <label for="labelopacity" class="text">Label Opacity</label>
+            <label id="labelopacity" class="slidervalue"></label>
+            <div id="lopacityslider" class="slider-range-max"></div>
+            <div id="labelColorHolder clearfix">
+            <table>
+                <tr><td><div id="labelColor" class="colorbox"></div></td><td><label>Master Label Color</label></td></tr>
+            </table></div>
+        </div>
+            <br>
+            <select id="labelcombo" onchange="labelMenuChanged()">
+            </select>
+            <div class="list" id="labellist">
+            </div>
+        </div>
+        <div id="axes">
+            <div id="pcoaaxes">
+                <div class="list" id="axeslist">
+                </div>
+            </div>
+        </div>
+        <div id="options">
+            <table>
+                <tr><td><div id="axeslabelscolor" class="colorbox" name="axeslabelscolor"></div></td><td title="Axes Labels Color">Axes Labels Color</td></tr>
+                <tr><td><div id="axescolor" class="colorbox" name="axescolor"></div></td><td title="Axes Color Title">Axes Color</td></tr>
+                <tr><td><div id="rendererbackgroundcolor" class="colorbox" name="rendererbackgroundcolor"></div></td><td title="Background Color Title">Background Color</td></tr>
+            </table>
+            <div id="pcoaviewoptions" class="">
+                <form name="settingsoptionscolor">
+                </form>
+                <div id="pcoaoptions" class="">
+                    <form name="settingsoptions">
+                        <input type="checkbox" onchange="toggleScaleCoordinates(this)" id="scale_checkbox" name="scale_checkbox">Scale coords by percent explained</input>
+                    </form>
+                </div>
+                <br><input id="reset" class="button" type="submit" value="Recenter Camera" style="" onClick="resetCamera()">
+                <br><br>
+                <hr class='section-break'>
+                <br>Filename <small>(only letters, numbers, ., - and _)</small>:
+                <br><input name="saveas_name" id="saveas_name" value="screenshot" type="text"/>
+                <br><input id="saveas_legends" class="checkbox" type="checkbox" style=""> Create legend
+                <input id="saveas" class="button" type="submit" value="Save as SVG" style="" onClick="saveSVG()"/>
+                <br><br>For a PNG, simply press 'ctrl+p'.
+                <div id="paralleloptions" class="">
+                </div>
+            </div>
+            <br>
+        </div>
+    </div>  
+</div>
+</body>
+
+</html>
+"""
+
+EXPECTED_FOOTER_D = """document.getElementById("logo").style.display = 'none';
+document.getElementById("logotable").style.display = 'none';
+
+ </script>
+</head>
+
+<body>
+
+<div id="overlay">
+    <div>
+    <img src="emperor_required_resources/img/emperor.png" alt="Emperor" id="smalllogo"/>
+        <h1>WebGL is not enabled!</h1>
+        <p>Emperor's visualization framework is WebGL based, it seems that your system doesn't have this resource available. Here is what you can do:</p>
+        <p id="explanation"><strong>Chrome:</strong> Type "chrome://flags/" into the address bar, then search for "Disable WebGL". Disable this option if you haven't already. <em>Note:</em> If you follow these steps and still don't see an image, go to "chrome://flags/" and then search for "Override software rendering list" and enable this option.</p>
+        <p id="explanation"><strong>Safari:</strong> Open Safari's menu and select Preferences. Click on the advanced tab, and then check "Show Developer" menu. Then open the "Developer" menu and select "Enable WebGL".</p>
+        <p id="explanation"><strong>Firefox:</strong> Go to Options through Firefox > Options or Tools > Options. Go to Advanced, then General. Check "Use hardware acceleration when available" and restart Firefox.</p>
+        <p id="explanation"><strong>Other browsers:</strong> The only browsers that support WebGL are Chrome, Safari, and Firefox. Please switch to these browsers when using Emperor.</p>
+        <p id="explanation"><em>Note:</em> Once you went through these changes, reload the page and it should work!</p>
+        <p id="source">Sources: Instructions for <a href="https://www.biodigitalhuman.com/home/enabling-webgl.html">Chrome and Safari</a>, and <a href="http://www.infewbytes.com/?p=144">Firefox</a></p>
+    </div>
+</div>
+
+<div id="plotToggle">
+    <form>
+      <div id="plottype">
+        <input id="pcoa" type="radio" id="pcoa" name="plottype" checked="checked" /><label for="pcoa">PCoA</label>
+        <input id="parallel" type="radio" id="parallel" name="plottype" /><label for="parallel">Parallel</label>
+      </div>
+    </form>
+</div>
+<div id="pcoaPlotWrapper" class="plotWrapper">
+    <label id="pointCount" class="ontop">
+    </label>
+
+    <div id="finder" class="arrow-right">
+    </div>
+
+    <div id="labels" class="unselectable">
+    </div>
+
+    <div id="taxalabels" class="unselectable">
+    </div>
+
+    <div id="axislabels" class="axislabels">
+    </div>
+
+    <div id="main_plot">
+    </div>
+</div>
+
+<div id="parallelPlotWrapper" class="plotWrapper">
+</div>
+
+<div class="separator" ondblclick="separatorDoubleClick()"></div>
+
+<div id="menu">
+    <div id="menutabs">
+        <ul>
+            <li><a href="#keytab">Key</a></li>
+            <li><a href="#colorby">Colors</a></li>
+            <li><a href="#showby">Visibility</a></li>
+            <li><a href="#scalingby">Scaling</a></li>
+            <li><a href="#labelby">Labels</a></li>
+            <li><a href="#axes">Axes</a></li>
+            <li><a href="#options">Options</a></li>
+        </ul>
+        <div id="keytab">
+            <form name="keyFilter">
+            <label>Filter  </label><input name="filterBox" id="searchBox" type="text" onkeyup="filterKey()"></input>
+            </form>
+            <div id="key">
+            </div>
+        </div>
+        <div id="colorby">
+            <br>
+            <input type="checkbox" onchange="toggleContinuousAndDiscreteColors(this)" id="discreteorcontinuouscolors" name="discreteorcontinuouscolors">  Use gradient colors</input>
+            <br><br>
+            <select id="colorbycombo" onchange="colorByMenuChanged()" size="3">
+            </select>
+            <div class="list" id="colorbylist">
+            </div>
+        </div>
+        <div id="showby" align="center">
+            <table width="100%">
+                <tr>
+                    <td align="center">
+                        <select id="showbycombo" onchange="showByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="showbylist" style="height:100%;width:100%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereopacity" class="text">Global Sphere Opacity</label>
+                        <label id="sphereopacity" class="slidervalue"></label>
+                        <div id="sopacityslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="scalingby" align="center">
+            <table width="100%">
+                <tr>
+                    <td align="center">
+                        <select id="scalingbycombo" onchange="scalingByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="scalingbylist" style="height:100%;width:100%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereradius" class="text">Global Sphere Scale</label>
+                        <label id="sphereradius" class="slidervalue"></label>
+                        <div id="sradiusslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="labelby">
+        <div id="labelsTop">
+            <form name="plotoptions">
+            <input type="checkbox" onClick="toggleLabels()">Samples Label Visibility</input>
+            </form>
+            <br>
+            <label for="labelopacity" class="text">Label Opacity</label>
+            <label id="labelopacity" class="slidervalue"></label>
+            <div id="lopacityslider" class="slider-range-max"></div>
+            <div id="labelColorHolder clearfix">
+            <table>
+                <tr><td><div id="labelColor" class="colorbox"></div></td><td><label>Master Label Color</label></td></tr>
+            </table></div>
+        </div>
+            <br>
+            <select id="labelcombo" onchange="labelMenuChanged()">
+            </select>
+            <div class="list" id="labellist">
+            </div>
+        </div>
+        <div id="axes">
+            <div id="pcoaaxes">
+                <div class="list" id="axeslist">
+                </div>
+            </div>
+        </div>
+        <div id="options">
+            <table>
+                <tr><td><div id="axeslabelscolor" class="colorbox" name="axeslabelscolor"></div></td><td title="Axes Labels Color">Axes Labels Color</td></tr>
+                <tr><td><div id="axescolor" class="colorbox" name="axescolor"></div></td><td title="Axes Color Title">Axes Color</td></tr>
+                <tr><td><div id="rendererbackgroundcolor" class="colorbox" name="rendererbackgroundcolor"></div></td><td title="Background Color Title">Background Color</td></tr>
+            </table>
+            <div id="pcoaviewoptions" class="">
+            <br>
+            <label for="vectorsopacity" class="text">Vectors Opacity</label>
+            <label id="vectorsopacity" class="slidervalue"></label>
+            <div id="vopacityslider" class="slider-range-max"></div>
+                <form name="settingsoptionscolor">
+                </form>
+                <div id="pcoaoptions" class="">
+                    <form name="settingsoptions">
+                        <input type="checkbox" onchange="toggleScaleCoordinates(this)" id="scale_checkbox" name="scale_checkbox">Scale coords by percent explained</input>
+                    </form>
+                </div>
+                <br><input id="reset" class="button" type="submit" value="Recenter Camera" style="" onClick="resetCamera()">
+                <br><br>
+                <hr class='section-break'>
+                <br>Filename <small>(only letters, numbers, ., - and _)</small>:
+                <br><input name="saveas_name" id="saveas_name" value="screenshot" type="text"/>
+                <br><input id="saveas_legends" class="checkbox" type="checkbox" style=""> Create legend
+                <input id="saveas" class="button" type="submit" value="Save as SVG" style="" onClick="saveSVG()"/>
+                <br><br>For a PNG, simply press 'ctrl+p'.
+                <div id="paralleloptions" class="">
+                </div>
+            </div>
+            <br>
+        </div>
+    </div>  
+</div>
+</body>
+
+</html>
+"""
+
+EXPECTED_FOOTER_E = """document.getElementById("logo").style.display = 'none';
+document.getElementById("logotable").style.display = 'none';
+
+ </script>
+</head>
+
+<body>
+
+<div id="overlay">
+    <div>
+    <img src="emperor_required_resources/img/emperor.png" alt="Emperor" id="smalllogo"/>
+        <h1>WebGL is not enabled!</h1>
+        <p>Emperor's visualization framework is WebGL based, it seems that your system doesn't have this resource available. Here is what you can do:</p>
+        <p id="explanation"><strong>Chrome:</strong> Type "chrome://flags/" into the address bar, then search for "Disable WebGL". Disable this option if you haven't already. <em>Note:</em> If you follow these steps and still don't see an image, go to "chrome://flags/" and then search for "Override software rendering list" and enable this option.</p>
+        <p id="explanation"><strong>Safari:</strong> Open Safari's menu and select Preferences. Click on the advanced tab, and then check "Show Developer" menu. Then open the "Developer" menu and select "Enable WebGL".</p>
+        <p id="explanation"><strong>Firefox:</strong> Go to Options through Firefox > Options or Tools > Options. Go to Advanced, then General. Check "Use hardware acceleration when available" and restart Firefox.</p>
+        <p id="explanation"><strong>Other browsers:</strong> The only browsers that support WebGL are Chrome, Safari, and Firefox. Please switch to these browsers when using Emperor.</p>
+        <p id="explanation"><em>Note:</em> Once you went through these changes, reload the page and it should work!</p>
+        <p id="source">Sources: Instructions for <a href="https://www.biodigitalhuman.com/home/enabling-webgl.html">Chrome and Safari</a>, and <a href="http://www.infewbytes.com/?p=144">Firefox</a></p>
+    </div>
+</div>
+
+<div id="plotToggle">
+    <form>
+      <div id="plottype">
+        <input id="pcoa" type="radio" id="pcoa" name="plottype" checked="checked" /><label for="pcoa">PCoA</label>
+        <input id="parallel" type="radio" id="parallel" name="plottype" /><label for="parallel">Parallel</label>
+      </div>
+    </form>
+</div>
+<div id="pcoaPlotWrapper" class="plotWrapper">
+    <label id="pointCount" class="ontop">
+    </label>
+
+    <div id="finder" class="arrow-right">
+    </div>
+
+    <div id="labels" class="unselectable">
+    </div>
+
+    <div id="taxalabels" class="unselectable">
+    </div>
+
+    <div id="axislabels" class="axislabels">
+    </div>
+
+    <div id="main_plot">
+    </div>
+</div>
+
+<div id="parallelPlotWrapper" class="plotWrapper">
+</div>
+
+<div class="separator" ondblclick="separatorDoubleClick()"></div>
+
+<div id="menu">
+    <div id="menutabs">
+        <ul>
+            <li><a href="#keytab">Key</a></li>
+            <li><a href="#colorby">Colors</a></li>
+            <li><a href="#showby">Visibility</a></li>
+            <li><a href="#scalingby">Scaling</a></li>
+            <li><a href="#labelby">Labels</a></li>
+            <li><a href="#axes">Axes</a></li>
+            <li><a href="#options">Options</a></li>
+        </ul>
+        <div id="keytab">
+            <form name="keyFilter">
+            <label>Filter  </label><input name="filterBox" id="searchBox" type="text" onkeyup="filterKey()"></input>
+            </form>
+            <div id="key">
+            </div>
+        </div>
+        <div id="colorby">
+            <br>
+            <input type="checkbox" onchange="toggleContinuousAndDiscreteColors(this)" id="discreteorcontinuouscolors" name="discreteorcontinuouscolors">  Use gradient colors</input>
+            <br><br>
+            <select id="colorbycombo" onchange="colorByMenuChanged()" size="3">
+            </select>
+            <div class="list" id="colorbylist">
+            </div>
+        </div>
+        <div id="showby" align="center">
+            <table width="100%">
+                <tr>
+                    <td align="center">
+                        <select id="showbycombo" onchange="showByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="showbylist" style="height:100%;width:100%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereopacity" class="text">Global Sphere Opacity</label>
+                        <label id="sphereopacity" class="slidervalue"></label>
+                        <div id="sopacityslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="scalingby" align="center">
+            <table width="100%">
+                <tr>
+                    <td align="center">
+                        <select id="scalingbycombo" onchange="scalingByMenuChanged()">
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <div class="list" id="scalingbylist" style="height:100%;width:100%">
+                        </div>
+                    </td>
+                </tr>
+                <tr>
+                    <td style="padding-left: 12px; padding-right:12px;">
+                        <br>
+                        <label for="sphereradius" class="text">Global Sphere Scale</label>
+                        <label id="sphereradius" class="slidervalue"></label>
+                        <div id="sradiusslider" class="slider-range-max"></div>
+                    </td>
+                </tr>
+            </table>
+        </div>
+        <div id="labelby">
+        <div id="labelsTop">
+            <form name="plotoptions">
+            <input type="checkbox" onClick="toggleLabels()">Samples Label Visibility</input>
+            </form>
+            <br>
+            <label for="labelopacity" class="text">Label Opacity</label>
+            <label id="labelopacity" class="slidervalue"></label>
+            <div id="lopacityslider" class="slider-range-max"></div>
+            <div id="labelColorHolder clearfix">
+            <table>
+                <tr><td><div id="labelColor" class="colorbox"></div></td><td><label>Master Label Color</label></td></tr>
+            </table></div>
+        </div>
+            <br>
+            <select id="labelcombo" onchange="labelMenuChanged()">
+            </select>
+            <div class="list" id="labellist">
+            </div>
+        </div>
+        <div id="axes">
+            <div id="pcoaaxes">
+                <div class="list" id="axeslist">
+                </div>
+            </div>
+        </div>
+        <div id="options">
+            <table>
+                <tr><td><div id="axeslabelscolor" class="colorbox" name="axeslabelscolor"></div></td><td title="Axes Labels Color">Axes Labels Color</td></tr>
+                <tr><td><div id="axescolor" class="colorbox" name="axescolor"></div></td><td title="Axes Color Title">Axes Color</td></tr>
+                <tr><td><div id="rendererbackgroundcolor" class="colorbox" name="rendererbackgroundcolor"></div></td><td title="Background Color Title">Background Color</td></tr>
+            <tr><td><div id="edgecolorselector_a" class="colorbox" name="edgecolorselector_a"></div></td><td title="edgecolor_a">Edge Color Selector A</td></tr>
+            <tr><td><div id="edgecolorselector_b" class="colorbox" name="edgecolorselector_b"></div></td><td title="edgecolor_b">Edge Color Selector B</td></tr>
+
+            </table>
+            <div id="pcoaviewoptions" class="">
+            <br>
+            <form name="edgesvisibility">
+            <input type="checkbox" onClick="toggleEdgesVisibility()" checked>Edges Visibility</input>
+            </form>
+            <br>
+                <form name="settingsoptionscolor">
+                </form>
+                <div id="pcoaoptions" class="">
+                    <form name="settingsoptions">
+                        <input type="checkbox" onchange="toggleScaleCoordinates(this)" id="scale_checkbox" name="scale_checkbox">Scale coords by percent explained</input>
+                    </form>
+                </div>
+                <br><input id="reset" class="button" type="submit" value="Recenter Camera" style="" onClick="resetCamera()">
+                <br><br>
+                <hr class='section-break'>
+                <br>Filename <small>(only letters, numbers, ., - and _)</small>:
+                <br><input name="saveas_name" id="saveas_name" value="screenshot" type="text"/>
+                <br><input id="saveas_legends" class="checkbox" type="checkbox" style=""> Create legend
+                <input id="saveas" class="button" type="submit" value="Save as SVG" style="" onClick="saveSVG()"/>
+                <br><br>For a PNG, simply press 'ctrl+p'.
+                <div id="paralleloptions" class="">
+                </div>
+            </div>
+            <br>
+        </div>
+    </div>  
+</div>
+</body>
+
+</html>
+"""
+
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/tests/test_pycogent_backports/test_procrustes.py b/tests/test_pycogent_backports/test_procrustes.py
new file mode 100755
index 0000000..cbc99e8
--- /dev/null
+++ b/tests/test_pycogent_backports/test_procrustes.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+
+from unittest import main, TestCase
+from numpy import (array, sqrt, dot, trace, transpose, pi, cos, sin, dot, trace,
+    append)
+from numpy.testing import assert_almost_equal
+from emperor.pycogent_backports.procrustes import (procrustes, get_disparity,
+    center, normalize)
+
+
+__author__ = "Justin Kuczynski"
+__copyright__ = "Copyright 2007-2012, The Cogent Project"
+__credits__ = ["Justin Kuczynski"]
+__license__ = "BSD"
+__version__ = "1.5.3-dev"
+__maintainer__ = "Justin Kuczynski"
+__email__ = "justinak at gmail.com"
+__status__ = "Production"
+
+class procrustesTests(TestCase):
+    """test the procrustes module, using floating point numpy arrays
+    """
+
+    def setUp(self):
+        """creates inputs"""
+        # an L
+        self.data1 = array([[1, 3],
+                    [1, 2],
+                    [1, 1],
+                    [2, 1]], 'd')
+            
+        # a larger, shifted, mirrored L
+        self.data2 = array([[4, -2],
+                    [4, -4],
+                    [4, -6],
+                    [2, -6]], 'd') 
+                    
+        # an L shifted up 1, right 1, and with point 4 shifted an extra .5
+        # to the right
+        # pointwise distance disparity with data1: 3*(2) + (1 + 1.5^2)
+        self.data3 = array([[2, 4],
+                    [2, 3],
+                    [2, 2],
+                    [3, 2.5]], 'd')
+                    
+        # data4, data5 are standardized (trace(A*A') = 1).
+        # procrustes should return an identical copy if they are used
+        # as the first matrix argument.
+        shiftangle = pi/8
+        self.data4 = array([[1,0],
+                            [0,1],
+                            [-1,0],
+                            [0,-1]],'d')/sqrt(4)
+        self.data5 = array([[cos(shiftangle), sin(shiftangle)],
+                            [cos(pi/2-shiftangle), sin(pi/2-shiftangle)],
+                            [-cos(shiftangle), -sin(shiftangle)],
+                            [-cos(pi/2-shiftangle), -sin(pi/2-shiftangle)]],
+                            'd')/sqrt(4)
+                        
+                        
+    def test_procrustes(self):
+        """tests procrustes' ability to match two matrices.
+        
+        the second matrix is a rotated, shifted, scaled, and mirrored version
+        of the first, in two dimensions only
+        """
+        # can shift, mirror, and scale an 'L'?
+        a,b,disparity = procrustes(self.data1, self.data2)
+        assert_almost_equal(b, a)
+        assert_almost_equal(disparity,0.)
+        
+        # if first mtx is standardized, leaves first mtx unchanged?
+        m4, m5, disp45 = procrustes(self.data4, self.data5)
+        assert_almost_equal(m4, self.data4)
+
+        # at worst, data3 is an 'L' with one point off by .5
+        m1, m3, disp13 = procrustes(self.data1, self.data3)
+        self.assertTrue(disp13 < .5**2)
+        
+    def test_procrustes2(self):
+        """procrustes disparity should not depend on order of matrices"""
+        m1, m3, disp13 = procrustes(self.data1, self.data3)
+        m3_2, m1_2, disp31 = procrustes(self.data3, self.data1)
+        assert_almost_equal(disp13, disp31)
+        
+        # try with 3d, 8 pts per
+        rand1 = array([[ 2.61955202,  0.30522265,  0.55515826],
+        [ 0.41124708, -0.03966978, -0.31854548],
+        [ 0.91910318,  1.39451809, -0.15295084],
+        [ 2.00452023,  0.50150048,  0.29485268],
+        [ 0.09453595,  0.67528885,  0.03283872],
+        [ 0.07015232,  2.18892599, -1.67266852],
+        [ 0.65029688,  1.60551637,  0.80013549],
+        [-0.6607528 ,  0.53644208,  0.17033891]])
+        
+        rand3 = array([[ 0.0809969 ,  0.09731461, -0.173442  ],
+        [-1.84888465, -0.92589646, -1.29335743],
+        [ 0.67031855, -1.35957463,  0.41938621],
+        [ 0.73967209, -0.20230757,  0.52418027],
+        [ 0.17752796,  0.09065607,  0.29827466],
+        [ 0.47999368, -0.88455717, -0.57547934],
+        [-0.11486344, -0.12608506, -0.3395779 ],
+        [-0.86106154, -0.28687488,  0.9644429 ]])
+        res1, res3, disp13 = procrustes(rand1,rand3)
+        res3_2, res1_2, disp31 = procrustes(rand3, rand1)
+        assert_almost_equal(disp13, disp31)
+        
+    def test_get_disparity(self):
+        """tests get_disparity"""
+        disp = get_disparity(self.data1, self.data3)
+        disp2 = get_disparity(self.data3, self.data1)
+        assert_almost_equal(disp, disp2)
+        assert_almost_equal(disp, (3.*2. + (1. + 1.5**2)))
+        
+        d1 = append(self.data1, self.data1, 0)
+        d3 = append(self.data3, self.data3, 0)
+        
+        disp3 = get_disparity(d1,d3)
+        disp4 = get_disparity(d3,d1)
+        assert_almost_equal(disp3, disp4)
+        # 2x points in same configuration should give 2x disparity
+        assert_almost_equal(disp3, 2.*disp)
+        
+    def test_center(self):
+        centered_mtx = center(self.data1)
+        column_means = centered_mtx.mean(0)
+        for col_mean in column_means:
+            assert_almost_equal(col_mean, 0.)
+            
+    def test_normalize(self):
+        norm_mtx = normalize(self.data1)
+        assert_almost_equal(trace(dot(norm_mtx,transpose(norm_mtx))), 1.)
+        
+    # match_points isn't yet tested, as it's almost a private function
+    # and test_procrustes() tests it implicitly.
+        
+#run if called from command line
+if __name__ == '__main__':
+       main()
diff --git a/tests/test_qiime_backports/test_biplots.py b/tests/test_qiime_backports/test_biplots.py
new file mode 100755
index 0000000..5afc319
--- /dev/null
+++ b/tests/test_qiime_backports/test_biplots.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# File created on 1 Apr 2010
+from __future__ import division
+
+__author__ = "Justin Kuczynski"
+__copyright__ = "Copyright 2011, The QIIME Project"
+__credits__ = ["Justin Kuczynski"]
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Justin Kuczynski"
+__email__ = "justinak at gmail.com"
+__status__ = "Development"
+
+import emperor.qiime_backports.biplots as bp
+import numpy as np
+
+from os import system
+from unittest import TestCase, main
+from numpy.testing import assert_almost_equal, assert_array_almost_equal
+
+class BiplotTests(TestCase):
+    
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def test_get_taxa_coords(self):
+        otu_table = np.array([  [2,0,0,1],
+                                [1,1,1,1],
+                                [0,2,2,1]],float)
+        sample_names = list('WXYZ')
+        otu_names = list('abc')
+    
+        res = bp.get_taxa_coords(otu_table, [.4,.2,.1,.9])
+        otu_coords= range(3)
+        otu_coords[0] = .4*2/3 + .9*1/3
+        otu_coords[1] = .4*1/4 + .2*1/4 + .1*1/4 + .9*1/4
+        otu_coords[2] = .4*0/5 + .2*2/5 + .1*2/5 + .9*1/5
+        assert_almost_equal(res, otu_coords)
+    
+    def test_get_taxa_prevalence(self):
+        otu_table = np.array([  [2,0,0,1],
+                                [1,1,1,1],
+                                [0,0,0,0]],float)
+        sample_weights = [3,1,1,2]
+        res = bp.get_taxa_prevalence(otu_table)
+        # print res
+        # self.assertFloatEqual(res, np.array([(2/3) + 1/2, 1/3+1+1+1/2, 0])/4) 
+        assert_almost_equal(res, np.array([(2/3) + 1/2, 1/3+1+1+1/2, 0])/4\
+            * 4/(2.5+1/3))                    
+        otu_table = np.array([  [2,0,0,1],
+                                [1,1,1,1],
+                                [0,2,2,1]],float)
+        res = bp.get_taxa_prevalence(otu_table)
+        # print res
+        # self.assertFloatEqual(res, np.array([3,4,5])/12) # if no normalize
+        assert_almost_equal(res, [0,.5,1])
+
+
+    def test_make_biplot_scores_output(self):
+        """make_biplot_scores_output correctly formats biplot scores"""
+        taxa = {}
+        taxa['lineages'] = list('ABC')
+        taxa['coord'] = np.array([  [2.1,0.2,0.2,1.4],
+                                 [1.1,1.2,1.3,1.5],
+                                 [-.3,-2,2.5,1.9]],float)
+        res = bp.make_biplot_scores_output(taxa)
+        exp = ['#Taxon\tpc0\tpc1\tpc2\tpc3',
+               'A\t2.1\t0.2\t0.2\t1.4',
+               'B\t1.1\t1.2\t1.3\t1.5',
+               'C\t-0.3\t-2.0\t2.5\t1.9',
+              ]
+        self.assertEqual(res, exp)
+    
+taxa_mage_no_scale = [\
+'@group {Taxa (n=3)} collapsible', \
+'@balllist color=white radius=10.0 alpha=0.7 dimension=3 master={taxa_points} nobutton', \
+'{A} 1.0 4.0 7.0', \
+'@labellist color=white radius=10.0 alpha=0.7 dimension=3 master={taxa_labels} nobutton', \
+'{A} 1.0 4.0 7.0', \
+'@balllist color=white radius=15.0 alpha=0.7 dimension=3 master={taxa_points} nobutton', \
+'{B} 2.0 5.0 8.0', \
+'@labellist color=white radius=15.0 alpha=0.7 dimension=3 master={taxa_labels} nobutton', \
+'{B} 2.0 5.0 8.0', \
+'@balllist color=white radius=20.0 alpha=0.7 dimension=3 master={taxa_points} nobutton', \
+'{C} 3.0 6.0 9.0', \
+'@labellist color=white radius=20.0 alpha=0.7 dimension=3 master={taxa_labels} nobutton', \
+'{C} 3.0 6.0 9.0']
+
+taxa_mage_scale = [\
+'@group {Taxa (n=3)} collapsible', \
+'@balllist color=white radius=10.0 alpha=0.7 dimension=3 master={taxa_points} nobutton', \
+'{A} 1.0 0.4 0.07', \
+'@labellist color=white radius=10.0 alpha=0.7 dimension=3 master={taxa_labels} nobutton', \
+'{A} 1.0 0.4 0.07', \
+'@balllist color=white radius=15.0 alpha=0.7 dimension=3 master={taxa_points} nobutton', \
+'{B} 2.0 0.5 0.08', \
+'@labellist color=white radius=15.0 alpha=0.7 dimension=3 master={taxa_labels} nobutton', \
+'{B} 2.0 0.5 0.08', \
+'@balllist color=white radius=20.0 alpha=0.7 dimension=3 master={taxa_points} nobutton', \
+'{C} 3.0 0.6 0.09', \
+'@labellist color=white radius=20.0 alpha=0.7 dimension=3 master={taxa_labels} nobutton', \
+'{C} 3.0 0.6 0.09']
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/test_qiime_backports/test_filter.py b/tests/test_qiime_backports/test_filter.py
new file mode 100755
index 0000000..1074ff4
--- /dev/null
+++ b/tests/test_qiime_backports/test_filter.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# File created on 18 May 2010
+from __future__ import division
+
+__author__ = "Greg Caporaso"
+__copyright__ = "Copyright 2011, The QIIME Project"
+__credits__ = ["Greg Caporaso", "Jai Ram Rideout", "Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+__status__ = "Development"
+ 
+from StringIO import StringIO
+from unittest import TestCase, main
+
+from emperor.qiime_backports.parse import (parse_mapping_file,
+    parse_metadata_state_descriptions)
+from emperor.qiime_backports.filter import (get_sample_ids, filter_mapping_file,
+    filter_mapping_file_by_metadata_states,
+    sample_ids_from_metadata_description, filter_mapping_file_from_mapping_f)
+
+class FilterTests(TestCase):
+    
+    def setUp(self):
+        self.map_str1 = map_str1
+        self.map_str2 = map_str2.split('\n')
+        self.map_data, self.map_headers, self.map_comments =\
+         parse_mapping_file(StringIO(self.map_str1))
+        self.tutorial_mapping_f = StringIO(tutorial_mapping_f)
+
+        # For sample_ids_from_category_state_coverage() tests.
+        self.exp_empty = (set([]), 0, set([]))
+        self.exp_all = (set(['PC.354', 'PC.355', 'PC.356', 'PC.481', 'PC.593',
+                             'PC.607', 'PC.634', 'PC.635', 'PC.636']), 6,
+                        set(['Control', 'Fast']))
+
+    def test_filter_mapping_file(self):
+        """filter_mapping_file should filter map file according to sample ids"""
+        self.assertEqual(filter_mapping_file(self.map_data, self.map_headers,\
+         ['a','b','c','d','e','f']), (self.map_headers, self.map_data))
+        self.assertEqual(filter_mapping_file(self.map_data, self.map_headers, ['a']),
+            (['SampleID','Description'],['a\tx'.split('\t')]))
+
+    def test_filter_mapping_file_from_mapping_f(self):
+        """ filter_mapping_file_from_mapping_f functions as expected """
+        actual = filter_mapping_file_from_mapping_f(self.tutorial_mapping_f,["PC.354","PC.355"])
+        expected = """#SampleID	BarcodeSequence	LinkerPrimerSequence	Treatment	DOB	Description
+PC.354	AGCACGAGCCTA	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._354
+PC.355	AACTCGTCGATG	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._355"""
+        self.assertEqual(actual,expected)
+
+    def test_filter_mapping_file_from_mapping_f_negate(self):
+        """ filter_mapping_file_from_mapping_f functions as expected when negate is True """
+        actual = filter_mapping_file_from_mapping_f(self.tutorial_mapping_f,
+         ["PC.356", "PC.481", "PC.593", "PC.607", "PC.634", "PC.635", "PC.636"],
+         negate=True)
+        expected = """#SampleID	BarcodeSequence	LinkerPrimerSequence	Treatment	DOB	Description
+PC.354	AGCACGAGCCTA	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._354
+PC.355	AACTCGTCGATG	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._355"""
+        self.assertEqual(actual,expected)
+
+    def test_filter_mapping_file_by_metadata_states(self):
+        """ filter_mapping_file_by_metadata_states functions as expected """
+        actual = filter_mapping_file_by_metadata_states(self.tutorial_mapping_f,"Treatment:Control")
+        expected = """#SampleID	BarcodeSequence	LinkerPrimerSequence	Treatment	DOB	Description
+PC.354	AGCACGAGCCTA	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._354
+PC.355	AACTCGTCGATG	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._355
+PC.356	ACAGACCACTCA	YATGCTGCCTCCCGTAGGAGT	Control	20061126	Control_mouse_I.D._356
+PC.481	ACCAGCGACTAG	YATGCTGCCTCCCGTAGGAGT	Control	20070314	Control_mouse_I.D._481
+PC.593	AGCAGCACTTGT	YATGCTGCCTCCCGTAGGAGT	Control	20071210	Control_mouse_I.D._593"""
+        self.assertEqual(actual,expected)
+
+
+    def test_sample_ids_from_metadata_description(self):
+        """Testing sample_ids_from_metadata_description fails on an empty set"""
+        self.assertRaises(ValueError, sample_ids_from_metadata_description,
+            self.tutorial_mapping_f, "Treatment:Foo")
+        self.tutorial_mapping_f.seek(0)
+        self.assertRaises(ValueError, sample_ids_from_metadata_description,
+            self.tutorial_mapping_f, "DOB:!20061218,!20070314,!20071112,"
+            "!20080116")
+
+    def test_get_sample_ids(self):
+        """get_sample_ids should return sample ids matching criteria."""
+        self.assertEqual(get_sample_ids(self.map_data, self.map_headers,\
+            parse_metadata_state_descriptions('Study:Twin')), [])
+        self.assertEqual(get_sample_ids(self.map_data, self.map_headers,\
+            parse_metadata_state_descriptions('Study:Dog')), ['a','b'])
+        self.assertEqual(get_sample_ids(self.map_data, self.map_headers,\
+            parse_metadata_state_descriptions('Study:*,!Dog')), ['c','d','e'])
+        self.assertEqual(get_sample_ids(self.map_data, self.map_headers,\
+            parse_metadata_state_descriptions('Study:*,!Dog;BodySite:Stool')), ['e'])
+        self.assertEqual(get_sample_ids(self.map_data, self.map_headers,\
+            parse_metadata_state_descriptions('BodySite:Stool')), ['a','b','e'])
+
+map_str1 = """#SampleID\tStudy\tBodySite\tDescription
+a\tDog\tStool\tx
+b\tDog\tStool\ty
+c\tHand\tPalm\tz
+d\tWholeBody\tPalm\ta
+e\tWholeBody\tStool\tb"""
+
+map_str2 = """#SampleID\tIndividual\tTime\tBodySite\tDescription
+a\tI1\t2\tPalm\tx
+b\tI2\t3\tStool\ty
+c\tI1\t1\tStool\tz
+d\tI3\t3\tStool\ta
+e\tI3\t1\tPalm\tb
+f\tI1\t3\tPalm\tc
+g\tI1\t2\tStool\td"""
+
+tutorial_mapping_f = """#SampleID	BarcodeSequence	LinkerPrimerSequence	Treatment	DOB	Description
+#Example mapping file for the QIIME analysis package.  These 9 samples are from a study of the effects of exercise and diet on mouse cardiac physiology (Crawford, et al, PNAS, 2009).
+PC.354	AGCACGAGCCTA	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._354
+PC.355	AACTCGTCGATG	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._355
+PC.356	ACAGACCACTCA	YATGCTGCCTCCCGTAGGAGT	Control	20061126	Control_mouse_I.D._356
+PC.481	ACCAGCGACTAG	YATGCTGCCTCCCGTAGGAGT	Control	20070314	Control_mouse_I.D._481
+PC.593	AGCAGCACTTGT	YATGCTGCCTCCCGTAGGAGT	Control	20071210	Control_mouse_I.D._593
+PC.607	AACTGTGCGTAC	YATGCTGCCTCCCGTAGGAGT	Fast	20071112	Fasting_mouse_I.D._607
+PC.634	ACAGAGTCGGCT	YATGCTGCCTCCCGTAGGAGT	Fast	20080116	Fasting_mouse_I.D._634
+PC.635	ACCGCAGAGTCA	YATGCTGCCTCCCGTAGGAGT	Fast	20080116	Fasting_mouse_I.D._635
+PC.636	ACGGTGAGTGTC	YATGCTGCCTCCCGTAGGAGT	Fast	20080116	Fasting_mouse_I.D._636"""
+
+expected_mapping_f1 = """#SampleID	BarcodeSequence	LinkerPrimerSequence	Treatment	DOB	Description
+PC.354	AGCACGAGCCTA	YATGCTGCCTCCCGTAGGAGT	Control	20061218	Control_mouse_I.D._354
+PC.636	ACGGTGAGTGTC	YATGCTGCCTCCCGTAGGAGT	Fast	20080116	Fasting_mouse_I.D._636"""
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/test_qiime_backports/test_format.py b/tests/test_qiime_backports/test_format.py
new file mode 100755
index 0000000..c5b3a20
--- /dev/null
+++ b/tests/test_qiime_backports/test_format.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+#unit tests for format.py
+from __future__ import division
+
+__author__ = "Rob Knight"
+__copyright__ = "Copyright 2011, The QIIME Project" #consider project name
+__credits__ = ["Rob Knight", "Daniel McDonald", "Jai Ram Rideout"]
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+__status__ = "Development"
+
+from unittest import main, TestCase
+from emperor.qiime_backports.format import format_mapping_file
+
+class TopLevelTests(TestCase):
+    """Tests of top-level module functions."""
+    
+    def setUp(self):
+      pass
+
+    def test_format_mapping_file(self):
+        """ format_mapping file should match expected result"""
+        headers = ['SampleID','col1','col0','Description']
+        samples =\
+         [['bsample','v1_3','v0_3','d1'],['asample','aval','another','d2']]
+        comments = ['this goes after headers','this too']
+        self.assertEqual(format_mapping_file(headers,samples,comments),
+         example_mapping_file)
+        # need file or stringIO for roundtrip test
+        # roundtrip = parse_mapping_file(format_mapping_file(headers,samples,comments))
+        # self.assertEqual(roundtrip, [headers,samples,comments])
+
+example_mapping_file = """#SampleID\tcol1\tcol0\tDescription
+#this goes after headers
+#this too
+bsample\tv1_3\tv0_3\td1
+asample\taval\tanother\td2"""
+
+
+#run unit tests if run from command-line
+if __name__ == '__main__':
+    main()
diff --git a/tests/test_qiime_backports/test_make_3d_plots.py b/tests/test_qiime_backports/test_make_3d_plots.py
new file mode 100755
index 0000000..2a967c6
--- /dev/null
+++ b/tests/test_qiime_backports/test_make_3d_plots.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+#file test_make_3d_plots.py
+
+__author__ = "Dan Knights"
+__copyright__ = "Copyright 2011, The QIIME Project" #consider project name
+__credits__ = ["Dan Knights"]
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Development"
+
+from numpy import array, nan
+from unittest import TestCase, main
+from numpy.testing import assert_almost_equal
+from emperor.qiime_backports.make_3d_plots import (get_custom_coords,
+    remove_nans, scale_custom_coords)
+
+class TopLevelTests(TestCase):
+    """Tests of top-level functions"""
+
+    def setUp(self):
+        """define some top-level data"""
+        self.coord_header=["Sample1","Sample2","Sample3"]
+        self.coords=array([[-0.219044992,0.079674486,0.09233683],[-0.042258081,\
+                        0.000204041,0.024837603],[0.080504323,-0.212014503,\
+                        -0.088353435]])
+        self.pct_var=array([25.00,30.00,35.00])
+        self.mapping=[["Sample-ID","Day","Type"],["Sample1","Day1","Soil"],\
+                    ["Sample2","Day1","Soil"],["Sample3","Day1","Soil"]]
+        self.mapping2=[["Sample-ID","Day","Type","Height","Weight"],\
+                        ["Sample1","Day1","Soil","10","60"],\
+                        ["Sample2","Day1","Soil","20","55"],\
+                        ["Sample3","Day1","Soil","30","50"]]
+
+        self.prefs_vectors={}
+        self.prefs_vectors['Sample']={}   
+        self.prefs_vectors['Sample']['column']="Type"
+        self.coords2 = array([[0, -0.219044992,0.079674486,0.09233683], \
+                                [1, -0.042258081, 0.000204041, 0.024837603],\
+                                [2, 0.080504323, -0.212014503, -0.088353435],\
+                                [3, 0.012345551, -0.124512513, -01142356135]])
+        self.custom_axes = ['Height']
+        self.add_vectors = {'vectors_algorithm': 'trajectory', 'vectors_axes': 3,\
+                                'vectors': ['Height'], 'vectors_path': 'vectors_test',\
+                                'eigvals': array([ 0, 2.44923871, 1.11678013, 1.01533255]),\
+                                'vectors_output': {},\
+                                'weight_by_vector' : False,\
+                                'window_size': 1}
+        self.filename_vectors = 'vectors_test'
+        self.file_path_vectors = 'vectors_test_dir'
+
+    def test_get_custom_coords(self):
+        """get_custom_coords: Gets custom axis coords from the mapping file."""
+        exp = 1
+        custom_axes = ['Height','Weight']
+        coords = [self.coord_header, self.coords]
+        get_custom_coords(custom_axes, self.mapping2, coords)
+        exp = array([[10,60,-0.219044992,0.079674486,0.09233683],
+                           [20,55,-0.042258081, 0.000204041,0.024837603],
+                           [30,50,0.080504323,-0.212014503,-0.088353435]])
+        assert_almost_equal(coords[1],exp)
+
+    def test_scale_custom_coords(self):
+        """scale_custom_coords: \
+        Scales custom coordinates to match min/max of PC1"""
+        custom_axes = ['Height','Weight']
+        coord_data = array([[10,60,-0.219044992,0.079674486,0.09233683],
+                            [20,55,-0.042258081, 0.000204041,0.024837603],
+                            [30,50,0.080504323,-0.212014503,-0.088353435]])
+        coords = [self.coord_header, coord_data]
+        scale_custom_coords(custom_axes,coords)
+        # calculate results
+        mn = coord_data[2,].min()
+        mx = coord_data[2,].max()
+        h = array([10.0,20.0,30.0])
+        h = (h-min(h))/(max(h)-min(h))
+        h = h * (mx-mn) + mn
+        w = array([60.0,55.0,50.0])
+        w = (w-min(w))/(max(w)-min(w))
+        w = w * (mx-mn) + mn
+        exp = array([[h[0],w[0],-0.219044992,0.079674486,0.09233683],
+                            [h[1],w[1],-0.042258081, 0.000204041,0.024837603],
+                            [h[2],w[2],0.080504323,-0.212014503,-0.088353435]])
+        assert_almost_equal(coords[1],exp)
+
+    def test_remove_nans(self):
+        """remove_nans: Deletes any samples with NANs in their coordinates"""
+        coord_data = array([[10,60,-0.219044992,0.079674486,0.09233683],
+                            [20,55,-0.042258081, nan,0.024837603],
+                            [30,50,0.080504323,-0.212014503,-0.088353435]])
+        coords = [self.coord_header, coord_data]
+        remove_nans(coords)
+
+        exp_header = ["Sample1","Sample3"]
+        exp_coords = array([[10,60,-0.219044992,0.079674486,0.09233683],
+                            [30,50,0.080504323,-0.212014503,-0.088353435]])
+        self.assertEqual(coords[0],exp_header)
+        assert_almost_equal(coords[1],exp_coords)
+
+
+#run tests if called from command line
+if __name__ == "__main__":
+    main()
diff --git a/tests/test_qiime_backports/test_parse.py b/tests/test_qiime_backports/test_parse.py
new file mode 100755
index 0000000..5e07d5f
--- /dev/null
+++ b/tests/test_qiime_backports/test_parse.py
@@ -0,0 +1,390 @@
+#!/usr/bin/env python
+#file test_parse.py
+
+__author__ = "Rob Knight"
+__copyright__ = "Copyright 2011, The QIIME Project"
+__credits__ = ["Rob Knight", "Justin Kuczynski", "Greg Caporaso",
+               "Cathy Lozupone", "Jai Ram Rideout"] #remember to add yourself
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+__status__ = "Development"
+
+from unittest import TestCase, main
+
+from StringIO import StringIO
+
+from numpy import array
+from numpy.testing import assert_almost_equal
+
+from emperor.qiime_backports.parse import (parse_mapping_file,
+    parse_metadata_state_descriptions, parse_coords, parse_classic_otu_table,
+    mapping_file_to_dict, parse_mapping_file_to_dict, QiimeParseError)
+
+class TopLevelTests(TestCase):
+
+    def setUp(self):
+        self.otu_table1 = otu_table1
+        self.otu_table_without_leading_comment = \
+            otu_table_without_leading_comment
+        self.otu_table1_floats=otu_table1_floats
+        self.legacy_otu_table1 = legacy_otu_table1
+        self.expected_lineages1 = expected_lineages1
+
+    def test_parse_mapping_file(self):
+        """parse_mapping_file functions as expected"""
+        s1 = ['#sample\ta\tb', '#comment line to skip',\
+              'x \t y \t z ', ' ', '#more skip', 'i\tj\tk']
+        exp = ([['x','y','z'],['i','j','k']],\
+               ['sample','a','b'],\
+               ['comment line to skip','more skip'])
+        obs = parse_mapping_file(s1)
+        self.assertEqual(obs, exp)
+
+        # We don't currently support this, but we should soon...
+        # # check that first non-comment, non-blank line is used as 
+        # # header
+        # s1 = ['sample\ta\tb', '#comment line to skip',\
+        #       'x \t y \t z ', ' ', '#more skip', 'i\tj\tk']
+        # exp = ([['x','y','z'],['i','j','k']],\
+        #        ['sample','a','b'],\
+        #        ['comment line to skip','more skip'])
+        # obs = parse_mapping_file(s1)
+        # self.assertEqual(obs, exp)
+
+        #check that we strip double quotes by default
+        s2 = ['#sample\ta\tb', '#comment line to skip',\
+              '"x "\t" y "\t z ', ' ', '"#more skip"', 'i\t"j"\tk']
+        obs = parse_mapping_file(s2)
+        self.assertEqual(obs, exp)
+
+    def test_mapping_file_to_dict(self):
+        """parse_mapping_file functions as expected"""
+        s1 = ['#sample\ta\tb', '#comment line to skip',\
+              'x \t y \t z ', ' ', '#more skip', 'i\tj\tk']
+        exp = ([['x','y','z'],['i','j','k']],\
+               ['sample','a','b'],\
+               ['comment line to skip','more skip'])
+        mapres = parse_mapping_file(s1) # map_data, header, comments
+        mapdict = mapping_file_to_dict(*mapres[:2])
+        expdict = {'x':{'a':'y','b':'z'}, 'i':{'a':'j','b':'k'}}
+        self.assertEqual(mapdict, expdict)
+
+    def test_parse_mapping_file_to_dict(self):
+        """parse_mapping_file functions as expected"""
+        s1 = ['#sample\ta\tb', '#comment line to skip',\
+              'x \t y \t z ', ' ', '#more skip', 'i\tj\tk']
+        exp = ([['x','y','z'],['i','j','k']],\
+               ['sample','a','b'],\
+               ['comment line to skip','more skip'])
+        mapdict, comments = parse_mapping_file_to_dict(s1)
+        expdict = {'x':{'a':'y','b':'z'}, 'i':{'a':'j','b':'k'}}
+        self.assertEqual(mapdict, expdict)
+        self.assertEqual(comments, ['comment line to skip','more skip'])
+
+    def test_parse_metadata_state_descriptions(self):
+        """parse_metadata_state_descriptions should return correct states from string."""
+        s = ''
+        self.assertEqual(parse_metadata_state_descriptions(s), {})
+        s = 'Study:Twin,Hand,Dog;BodySite:Palm,Stool'
+        self.assertEqual(parse_metadata_state_descriptions(s), {'Study':set([
+            'Twin','Hand','Dog']),'BodySite':set(['Palm','Stool'])})
+
+        # category names with colons i. e. ontology-derived
+        s = 'Study:Twin,Hand,Dog;site:UBERON:feces,UBERON:ear canal;'+\
+            'env_feature:ENVO:farm soil,ENVO:national park'
+        self.assertEqual(parse_metadata_state_descriptions(s), {'Study':
+            set(['Twin', 'Hand', 'Dog']), 'site':set(['UBERON:feces',
+            'UBERON:ear canal']), 'env_feature':set(['ENVO:farm soil',
+            'ENVO:national park'])})
+
+        s = "Treatment:A,B,C;env_matter:ENVO:nitsol,ENVO:farm soil;env_biom:"+\
+            "ENVO:Tropical dry (including Monsoon forests) and woodlands,"+\
+            "ENVO:Forest: including woodlands;country:GAZ:Persnickety Islands"+\
+            ",St. Kitt's and Nevis"
+        self.assertEqual(parse_metadata_state_descriptions(s), {"country":
+            set(["GAZ:Persnickety Islands", "St. Kitt's and Nevis"]),
+            "env_biom":set(["ENVO:Tropical dry (including Monsoon forests) "+\
+            "and woodlands", "ENVO:Forest: including woodlands"]), "env_matter":
+            set(["ENVO:nitsol","ENVO:farm soil"]), 'Treatment':set(["A", "B",
+            "C"])})
+
+
+    def test_parse_coords(self):
+        """parse_coords should handle coords file"""
+        coords = """pc vector number\t1\t2\t3
+A\t0.11\t0.09\t0.23
+B\t0.03\t0.07\t-0.26
+C\t0.12\t0.06\t-0.32
+
+
+eigvals\t4.94\t1.79\t1.50
+% variation explained\t14.3\t5.2\t4.3
+
+
+""".splitlines()
+        obs = parse_coords(coords)
+        exp = (['A','B','C'], 
+            array([[.11,.09,.23],[.03,.07,-.26],[.12,.06,-.32]]),
+            array([4.94,1.79,1.50]),
+            array([14.3,5.2,4.3]))
+        # test the header and the values apart from each other
+        self.assertEqual(obs[0], exp[0])
+        assert_almost_equal(obs[1], exp[1])
+
+    def test_parse_coords_exceptions(self):
+        """Check exceptions are raised accordingly with missing information"""
+
+        # missing eigenvalues line
+        with self.assertRaises(QiimeParseError):
+            out = parse_coords(COORDS_NO_EIGENVALS.splitlines())
+        # missing percentages explained line
+        with self.assertRaises(QiimeParseError):
+            out = parse_coords(COORDS_NO_PCNTS.splitlines())
+        # missing vector number line
+        with self.assertRaises(QiimeParseError):
+            out = parse_coords(COORDS_NO_VECTORS.splitlines())
+
+        # a whole different file (taxa summary)
+        with self.assertRaises(QiimeParseError):
+            out = parse_coords(taxa_summary1.splitlines())
+
+    def test_parse_classic_otu_table_legacy(self):
+        """parse_classic_otu_table functions as expected with legacy OTU table
+        """
+        data = self.legacy_otu_table1
+        data_f = (data.split('\n'))
+        obs = parse_classic_otu_table(data_f)
+        exp = (['Fing','Key','NA'],
+               ['0','1','2','3','4'],
+               array([[19111,44536,42],[1216,3500,6],[1803,1184,2],
+                      [1722,4903,17], [589,2074,34]]),
+               self.expected_lineages1)
+
+        # divide the comparisons into their four elements
+        self.assertEqual(obs[0], exp[0])
+        self.assertEqual(obs[1], exp[1])
+        assert_almost_equal(obs[2], exp[2])
+        self.assertEqual(obs[3], exp[3])
+        
+    def test_parse_classic_otu_table(self):
+        """parse_classic_otu_table functions as expected with new-style OTU table
+        """
+        data = self.otu_table1
+        data_f = (data.split('\n'))
+        obs = parse_classic_otu_table(data_f)
+        exp = (['Fing','Key','NA'],
+               ['0','1','2','3','4'],
+               array([[19111,44536,42],[1216,3500,6],[1803,1184,2],
+                      [1722,4903,17], [589,2074,34]]),
+               self.expected_lineages1)
+
+        # divide the comparisons into their four elements
+        self.assertEqual(obs[0], exp[0])
+        self.assertEqual(obs[1], exp[1])
+        assert_almost_equal(obs[2], exp[2])
+        self.assertEqual(obs[3], exp[3])
+
+        # test that the modified parse_classic performs correctly on OTU tables
+        # without leading comments
+        data = self.otu_table_without_leading_comment
+        data_f = (data.split('\n'))
+        obs = parse_classic_otu_table(data_f)
+        sams = ['let-7i','miR-7','miR-17n','miR-18a','miR-19a','miR-22',
+            'miR-25','miR-26a']
+        otus = ['A2M', 'AAAS', 'AACS', 'AADACL1']
+        vals = array([\
+            [-0.2,  0.03680505,  0.205,  0.23,  0.66,  0.08,  -0.373,  0.26],
+            [-0.09,  -0.25,  0.274,  0.15,  0.12,  0.29,  0.029,  -0.1148452],
+            [0.33,  0.19,  0.27,  0.28,  0.19,  0.25,  0.089,  0.14],
+            [0.49,  -0.92,  -0.723,  -0.23,  0.08,  0.49,  -0.386,  -0.64]])
+        exp = (sams, otus, vals, []) # no lineages
+        # because float comps in arrays always errors
+        self.assertEqual(obs[0], exp[0])
+        self.assertEqual(obs[1], exp[1])
+        self.assertEqual(obs[3], exp[3])
+        self.assertTrue(all((obs[2]==exp[2]).tolist()))
+
+    def test_parse_classic_otu_table_floats_in_table(self):
+        """parse_classic_otu_table functions using an OTU table containing floats
+           but cast as int....this will automatically cast into floats"""
+           
+        data = self.otu_table1_floats
+        data_f = (data.split('\n'))
+        obs = parse_classic_otu_table(data_f)
+        exp = (['Fing','Key','NA'],
+               ['0','1','2','3','4'],
+               array([[19111.0,44536.0,42.0],[1216.0,3500.0,6.0],
+                      [1803.0,1184.0,2.0],[1722.1,4903.2,17.0],
+                      [589.6,2074.4,34.5]]),
+               self.expected_lineages1)
+        # because float comps in arrays always errors
+        self.assertEqual(obs[0], exp[0])
+        self.assertEqual(obs[1], exp[1])
+        self.assertEqual(obs[3], exp[3])
+        self.assertTrue(all((obs[2]==exp[2]).tolist()))
+
+    def test_parse_classic_otu_table_float_counts(self):
+        """parse_classic_otu_table should return correct result from small table"""
+        data = """#Full OTU Counts
+#OTU ID	Fing	Key	NA	Consensus Lineage
+0	19111	44536	42	Bacteria; Actinobacteria; Actinobacteridae; Propionibacterineae; Propionibacterium
+1	1216	3500	6	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Lactobacillales; Lactobacillales; Streptococcaceae; Streptococcus
+2	1803	1184	2	Bacteria; Actinobacteria; Actinobacteridae; Gordoniaceae; Corynebacteriaceae
+3	1722	4903	17	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Staphylococcaceae
+4	589	2074	34	Bacteria; Cyanobacteria; Chloroplasts; vectors"""
+        data_f = (data.split('\n'))
+        obs = parse_classic_otu_table(data_f,count_map_f=float)
+        exp = (['Fing','Key','NA'],
+               ['0','1','2','3','4'],
+               array([[19111.,44536.,42.],[1216.,3500.,6.],[1803.,1184.,2.],\
+                    [1722.,4903.,17.], [589,2074.,34.]]),
+               [['Bacteria','Actinobacteria','Actinobacteridae','Propionibacterineae','Propionibacterium'],
+                ['Bacteria','Firmicutes','Alicyclobacillaceae','Bacilli','Lactobacillales','Lactobacillales','Streptococcaceae','Streptococcus'],
+                ['Bacteria','Actinobacteria','Actinobacteridae','Gordoniaceae','Corynebacteriaceae'],
+                ['Bacteria','Firmicutes','Alicyclobacillaceae','Bacilli','Staphylococcaceae'],
+                ['Bacteria','Cyanobacteria','Chloroplasts','vectors']])
+
+        # because float comps in arrays always errors
+        self.assertEqual(obs[0], exp[0])
+        self.assertEqual(obs[1], exp[1])
+        self.assertEqual(obs[3], exp[3])
+        self.assertTrue(all((obs[2]==exp[2]).tolist()))
+
+    def test_parse_classic_otu_table_file(self):
+        """parse_classic_otu_table should return correct result on fileio format object"""
+        data = """#Full OTU Counts
+#OTU ID	Fing	Key	NA	Consensus Lineage
+0	19111	44536	42	Bacteria; Actinobacteria; Actinobacteridae; Propionibacterineae; Propionibacterium
+1	1216	3500	6	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Lactobacillales; Lactobacillales; Streptococcaceae; Streptococcus
+2	1803	1184	2	Bacteria; Actinobacteria; Actinobacteridae; Gordoniaceae; Corynebacteriaceae
+3	1722	4903	17	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Staphylococcaceae
+4	589	2074	34	Bacteria; Cyanobacteria; Chloroplasts; vectors"""
+        data_f = StringIO(data)
+        obs = parse_classic_otu_table(data_f)
+        exp = (['Fing','Key','NA'],
+               ['0','1','2','3','4'],
+               array([[19111,44536,42],[1216,3500,6],[1803,1184,2],\
+                    [1722,4903,17], [589,2074,34]]),
+               [['Bacteria','Actinobacteria','Actinobacteridae','Propionibacterineae','Propionibacterium'],
+                ['Bacteria','Firmicutes','Alicyclobacillaceae','Bacilli','Lactobacillales','Lactobacillales','Streptococcaceae','Streptococcus'],
+                ['Bacteria','Actinobacteria','Actinobacteridae','Gordoniaceae','Corynebacteriaceae'],
+                ['Bacteria','Firmicutes','Alicyclobacillaceae','Bacilli','Staphylococcaceae'],
+                ['Bacteria','Cyanobacteria','Chloroplasts','vectors']])
+        # because float comps in arrays always errors
+        self.assertEqual(obs[0], exp[0])
+        self.assertEqual(obs[1], exp[1])
+        self.assertEqual(obs[3], exp[3])
+        self.assertTrue(all((obs[2]==exp[2]).tolist()))
+        
+    def test_parse_classic_otu_table_consensus_lineage(self):
+        """parse_classic_otu_table should accept 'consensusLineage'"""
+        data = """#Full OTU Counts
+#OTU ID	Fing	Key	NA	consensusLineage
+0	19111	44536	42	Bacteria; Actinobacteria; Actinobacteridae; Propionibacterineae; Propionibacterium
+1	1216	3500	6	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Lactobacillales; Lactobacillales; Streptococcaceae; Streptococcus
+2	1803	1184	2	Bacteria; Actinobacteria; Actinobacteridae; Gordoniaceae; Corynebacteriaceae
+3	1722	4903	17	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Staphylococcaceae
+4	589	2074	34	Bacteria; Cyanobacteria; Chloroplasts; vectors"""
+        data_f = StringIO(data)
+        obs = parse_classic_otu_table(data_f)
+        exp = (['Fing','Key','NA'],
+               ['0','1','2','3','4'],
+               array([[19111,44536,42],[1216,3500,6],[1803,1184,2],\
+                    [1722,4903,17], [589,2074,34]]),
+               [['Bacteria','Actinobacteria','Actinobacteridae','Propionibacterineae','Propionibacterium'],
+                ['Bacteria','Firmicutes','Alicyclobacillaceae','Bacilli','Lactobacillales','Lactobacillales','Streptococcaceae','Streptococcus'],
+                ['Bacteria','Actinobacteria','Actinobacteridae','Gordoniaceae','Corynebacteriaceae'],
+                ['Bacteria','Firmicutes','Alicyclobacillaceae','Bacilli','Staphylococcaceae'],
+                ['Bacteria','Cyanobacteria','Chloroplasts','vectors']])
+        # because float comps in arrays always errors
+        self.assertEqual(obs[0], exp[0])
+        self.assertEqual(obs[1], exp[1])
+        self.assertEqual(obs[3], exp[3])
+        self.assertTrue(all((obs[2]==exp[2]).tolist()))
+
+legacy_otu_table1 = """# some comment goes here
+#OTU ID	Fing	Key	NA	Consensus Lineage
+0	19111	44536	42	Bacteria; Actinobacteria; Actinobacteridae; Propionibacterineae; Propionibacterium
+
+1	1216	3500	6	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Lactobacillales; Lactobacillales; Streptococcaceae; Streptococcus
+2	1803	1184	2	Bacteria; Actinobacteria; Actinobacteridae; Gordoniaceae; Corynebacteriaceae
+3	1722	4903	17	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Staphylococcaceae
+4	589	2074	34	Bacteria; Cyanobacteria; Chloroplasts; vectors
+"""
+
+otu_table1 = """# Some comment
+
+
+
+
+OTU ID	Fing	Key	NA	Consensus Lineage
+0	19111	44536	42	Bacteria; Actinobacteria; Actinobacteridae; Propionibacterineae; Propionibacterium
+# some other comment
+1	1216	3500	6	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Lactobacillales; Lactobacillales; Streptococcaceae; Streptococcus
+2	1803	1184	2	Bacteria; Actinobacteria; Actinobacteridae; Gordoniaceae; Corynebacteriaceae
+# comments
+#    everywhere!
+3	1722	4903	17	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Staphylococcaceae
+4	589	2074	34	Bacteria; Cyanobacteria; Chloroplasts; vectors
+"""
+
+otu_table1_floats = """# Some comment
+
+
+
+
+OTU ID	Fing	Key	NA	Consensus Lineage
+0	19111.0	44536.0	42.0	Bacteria; Actinobacteria; Actinobacteridae; Propionibacterineae; Propionibacterium
+# some other comment
+1	1216.0	3500.0	6.0	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Lactobacillales; Lactobacillales; Streptococcaceae; Streptococcus
+2	1803.0	1184.0	2.0	Bacteria; Actinobacteria; Actinobacteridae; Gordoniaceae; Corynebacteriaceae
+# comments
+#    everywhere!
+3	1722.1	4903.2	17	Bacteria; Firmicutes; Alicyclobacillaceae; Bacilli; Staphylococcaceae
+4	589.6	2074.4	34.5	Bacteria; Cyanobacteria; Chloroplasts; vectors
+"""
+
+
+otu_table_without_leading_comment = '#OTU ID\tlet-7i\tmiR-7\tmiR-17n\tmiR-18a\tmiR-19a\tmiR-22\tmiR-25\tmiR-26a\nA2M\t-0.2\t0.03680505\t0.205\t0.23\t0.66\t0.08\t-0.373\t0.26\nAAAS\t-0.09\t-0.25\t0.274\t0.15\t0.12\t0.29\t0.029\t-0.114845199\nAACS\t0.33\t0.19\t0.27\t0.28\t0.19\t0.25\t0.089\t0.14\nAADACL1\t0.49\t-0.92\t-0.723\t-0.23\t0.08\t0.49\t-0.386\t-0.64'
+
+expected_lineages1 = [['Bacteria','Actinobacteria','Actinobacteridae','Propionibacterineae','Propionibacterium'],
+['Bacteria','Firmicutes','Alicyclobacillaceae','Bacilli','Lactobacillales','Lactobacillales','Streptococcaceae','Streptococcus'],
+['Bacteria','Actinobacteria','Actinobacteridae','Gordoniaceae','Corynebacteriaceae'],
+['Bacteria','Firmicutes','Alicyclobacillaceae','Bacilli','Staphylococcaceae'],
+['Bacteria','Cyanobacteria','Chloroplasts','vectors']]
+
+
+COORDS_NO_VECTORS = """A\t0.11\t0.09\t0.23
+B\t0.03\t0.07\t-0.26
+C\t0.12\t0.06\t-0.32
+eigvals\t4.94\t1.79\t1.50
+% variation explained\t14.3\t5.2\t4.3"""
+
+COORDS_NO_EIGENVALS = """pc vector number\t1\t2\t3
+A\t0.11\t0.09\t0.23
+B\t0.03\t0.07\t-0.26
+C\t0.12\t0.06\t-0.32
+foo\t4.94\t1.79\t1.50
+% variation explained\t14.3\t5.2\t4.3"""
+
+COORDS_NO_PCNTS = """pc vector number\t1\t2\t3
+A\t0.11\t0.09\t0.23
+B\t0.03\t0.07\t-0.26
+C\t0.12\t0.06\t-0.32
+eigvals\t4.94\t1.79\t1.50"""
+
+taxa_summary1 = """#Full OTU Counts
+Taxon\tEven1\tEven2\tEven3
+Bacteria;Actinobacteria;Actinobacteria(class);Actinobacteridae\t0.0880247251673\t0.0721968465746\t0.081371761759
+Bacteria;Bacteroidetes/Chlorobigroup;Bacteroidetes;Bacteroidia\t0.192137761955\t0.191095101593\t0.188504131885
+Bacteria;Firmicutes;Bacilli;Lactobacillales\t0.0264895739603\t0.0259942669171\t0.0318460745596
+# some comment
+Bacteria;Firmicutes;Clostridia;Clostridiales\t0.491800007824\t0.526186212556\t0.49911159984
+Bacteria;Firmicutes;Erysipelotrichi;Erysipelotrichales\t0.0311411916592\t0.0184083913576\t0.0282325481054
+Bacteria;Proteobacteria;Gammaproteobacteria;Enterobacteriales\t0.166137214246\t0.163087129528\t0.168923372865
+No blast hit;Other\t0.00426952518811\t0.00303205147361\t0.0020105109874"""
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/test_qiime_backports/test_util.py b/tests/test_qiime_backports/test_util.py
new file mode 100755
index 0000000..3c67963
--- /dev/null
+++ b/tests/test_qiime_backports/test_util.py
@@ -0,0 +1,543 @@
+#!/usr/bin/env python
+from __future__ import division
+
+__author__ = "Greg Caporaso"
+__copyright__ = "Copyright 2011, The QIIME Project"
+#remember to add yourself if you make changes
+__credits__ = ["Rob Knight", "Daniel McDonald", "Greg Caporaso", 
+               "Justin Kuczynski", "Catherine Lozupone",
+               "Jai Ram Rideout", "Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "1.7.0-dev"
+__maintainer__ = "Greg Caporaso"
+__email__ = "gregcaporaso at gmail.com"
+__status__ = "Development"
+
+
+from unittest import TestCase, main
+
+from numpy.testing import assert_almost_equal
+from numpy import array, isnan, asarray, arange
+
+from emperor.pycogent_backports.procrustes import procrustes
+from emperor.qiime_backports.parse import (parse_mapping_file_to_dict,
+    QiimeParseError)
+from emperor.qiime_backports.util import (MetadataMap, is_valid_git_sha1,
+    is_valid_git_refname, summarize_pcoas, _flip_vectors,
+    _compute_jn_pcoa_avg_ranges, matrix_IQR, idealfourths, IQR)
+
+
+class TopLevelTests(TestCase):
+    def setup(self):
+        pass
+
+
+    def test_flip_vectors(self):
+        """_flip_vectors makes a new PCA matrix with correct signs"""
+        m_matrix = array([[1.0, 0.0, 1.0], [2.0, 4.0, 4.0]])
+        jn_matrix = array([[1.2, 0.1, -1.2], [2.5, 4.0, -4.5]])
+        new_matrix = _flip_vectors(jn_matrix, m_matrix)
+        assert_almost_equal(new_matrix, array([[1.2, 0.1, 1.2], [2.5, 4.0, 4.5]]))
+
+    def test_compute_jn_pcoa_avg_ranges(self):
+        """_compute_jn_pcoa_avg_ranges works
+        """
+        jn_flipped_matrices = [array([[2.0,4.0, -4.5],[-1.2,-0.1,1.2]]),\
+                array([[3.0,4.0, -4.5],[-1.2,-0.1,1.2]]),\
+                array([[4.0,4.0, -4.5],[-1.2,-0.1,1.2]]),\
+                array([[5.0,4.0, -4.5],[-1.2,-0.1,1.2]]),\
+                array([[6.0,4.0, -4.5],[-1.2,-0.1,1.2]]),\
+                array([[7.0,4.0, -4.5],[-1.2,-0.1,1.2]]),\
+                array([[1.0,4.0, -4.5],[-1.2,-0.1,1.2]])]
+        avg_matrix, low_matrix, high_matrix = _compute_jn_pcoa_avg_ranges(\
+                jn_flipped_matrices, 'ideal_fourths')
+        assert_almost_equal(avg_matrix[(0,0)], 4.0)
+        assert_almost_equal(avg_matrix[(0,2)], -4.5)
+        assert_almost_equal(low_matrix[(0,0)], 2.16666667)
+        assert_almost_equal(high_matrix[(0,0)], 5.83333333)
+
+        avg_matrix, low_matrix, high_matrix = _compute_jn_pcoa_avg_ranges(\
+                jn_flipped_matrices, 'sdev')
+        x = array([m[0,0] for m in jn_flipped_matrices])
+        self.assertEqual(x.mean(),avg_matrix[0,0])
+        self.assertEqual(-x.std(ddof=1)/2,low_matrix[0,0])
+        self.assertEqual(x.std(ddof=1)/2,high_matrix[0,0])
+        
+    def test_summarize_pcoas(self):
+        """summarize_pcoas works
+        """
+        master_pcoa = [['1', '2', '3'], \
+            array([[-1.0, 0.0, 1.0], [2.0, 4.0, -4.0]]), \
+            array([.76, .24])]
+        jn1 = [['1', '2', '3'], \
+            array([[1.2, 0.1, -1.2],[-2.5, -4.0, 4.5]]), \
+            array([0.80, .20])]
+        jn2 = [['1', '2', '3'], \
+            array([[-1.4, 0.05, 1.3],[2.6, 4.1, -4.7]]), \
+            array([0.76, .24])]
+        jn3 = [['1', '2', '3'], \
+            array([[-1.5, 0.05, 1.6],[2.4, 4.0, -4.8]]), \
+            array([0.84, .16])]
+        jn4 = [['1', '2', '3'], \
+            array([[-1.5, 0.05, 1.6],[2.4, 4.0, -4.8]]), \
+            array([0.84, .16])]
+        support_pcoas = [jn1, jn2, jn3, jn4]
+        #test with the ideal_fourths option
+        matrix_average, matrix_low, matrix_high, eigval_average, m_names = \
+            summarize_pcoas(master_pcoa, support_pcoas, 'ideal_fourths',
+                            apply_procrustes=False)
+        self.assertEqual(m_names, ['1', '2', '3'])
+        assert_almost_equal(matrix_average[(0,0)], -1.4)
+        assert_almost_equal(matrix_average[(0,1)], 0.0125)
+        assert_almost_equal(matrix_low[(0,0)], -1.5)
+        assert_almost_equal(matrix_high[(0,0)], -1.28333333)
+        assert_almost_equal(matrix_low[(0,1)], -0.0375)
+        assert_almost_equal(matrix_high[(0,1)], 0.05)
+        assert_almost_equal(eigval_average[0], 0.81)
+        assert_almost_equal(eigval_average[1], 0.19)
+        #test with the IQR option
+        matrix_average, matrix_low, matrix_high, eigval_average, m_names = \
+            summarize_pcoas(master_pcoa, support_pcoas, method='IQR',
+                            apply_procrustes=False)
+        assert_almost_equal(matrix_low[(0,0)], -1.5)
+        assert_almost_equal(matrix_high[(0,0)], -1.3)
+
+        #test with procrustes option followed by sdev
+        m, m1, msq = procrustes(master_pcoa[1],jn1[1])
+        m, m2, msq = procrustes(master_pcoa[1],jn2[1])
+        m, m3, msq = procrustes(master_pcoa[1],jn3[1])
+        m, m4, msq = procrustes(master_pcoa[1],jn4[1])
+        matrix_average, matrix_low, matrix_high, eigval_average, m_names = \
+            summarize_pcoas(master_pcoa, support_pcoas, method='sdev',
+                            apply_procrustes=True)
+
+        x = array([m1[0,0],m2[0,0],m3[0,0],m4[0,0]])
+        self.assertEqual(x.mean(),matrix_average[0,0])
+        self.assertEqual(-x.std(ddof=1)/2,matrix_low[0,0])
+        self.assertEqual(x.std(ddof=1)/2,matrix_high[0,0])
+
+    def test_IQR(self):
+        "IQR returns the interquartile range for list x"
+        #works for odd with odd split
+        x = [2,3,4,5,6,7,1]
+        minv, maxv = IQR(x)
+        self.assertEqual(minv, 2)
+        self.assertEqual(maxv, 6)
+        #works for even with odd split
+        x = [1,2,3,4,5,6]
+        minv, maxv = IQR(x)
+        self.assertEqual(minv, 2)
+        self.assertEqual(maxv, 5)
+        #works for even with even split
+        x = [1,2,3,4,5,6,7,8]
+        minv, maxv = IQR(x)
+        self.assertEqual(minv, 2.5)
+        self.assertEqual(maxv, 6.5)
+        #works with array
+        #works for odd with odd split
+        x = array([2,3,4,5,6,7,1])
+        minv, maxv = IQR(x)
+        self.assertEqual(minv, 2)
+        self.assertEqual(maxv, 6)
+        #works for even with odd split
+        x = array([1,2,3,4,5,6])
+        minv, maxv = IQR(x)
+        self.assertEqual(minv, 2)
+        self.assertEqual(maxv, 5)
+        #works for even with even split
+        x = array([1,2,3,4,5,6,7,8])
+        minv, maxv = IQR(x)
+        self.assertEqual(minv, 2.5)
+        self.assertEqual(maxv, 6.5)
+        
+    def test_matrix_IQR(self):
+        """matrix_IQR calcs the IQR for each column in an array correctly
+        """
+        x = array([[1,2,3],[4,5,6],[7,8,9], [10,11,12]])
+        min_vals, max_vals = matrix_IQR(x)
+        assert_almost_equal(min_vals, array([2.5,3.5,4.5]))
+        assert_almost_equal(max_vals, array([8.5,9.5,10.5]))
+
+    def test_idealfourths(self):
+        """idealfourths: tests the ideal-fourths function which was imported from scipy
+        at the following location (http://projects.scipy.org/scipy/browser/trunk/scipy/stats/tests/test_mmorestats.py?rev=4154)
+        """
+        test = arange(100)
+        self.assertEqual(idealfourths(test),
+                            [24.416666666666668, 74.583333333333343])
+        test_2D = test.repeat(3).reshape(-1,3)
+        
+        # used to be assertAlmostEqualRel but assert_almost_equal from numpy
+        # seems to be working just fine
+        assert_almost_equal(asarray(idealfourths(test_2D, axis=0)),\
+                    array([[24.41666667, 24.41666667, 24.41666667], \
+                                 [74.58333333, 74.58333333, 74.58333333]]))
+        
+        assert_almost_equal(idealfourths(test_2D, axis=1),
+                            test.repeat(2).reshape(-1,2))
+        test = [0,0]
+        _result = idealfourths(test)
+        assert_almost_equal(isnan(_result).all(),True)
+
+
+class MetadataMapTests(TestCase):
+    """Tests for the MetadataMap class."""
+
+    def setUp(self):
+        """Create MetadataMap objects that will be used in the tests."""
+        # Create a map using the overview tutorial mapping file.
+        self.overview_map_str = [
+                "#SampleID\tBarcodeSequence\tTreatment\tDOB\tDescription",
+                "PC.354\tAGCACGAGCCTA\tControl\t20061218\t354",
+                "PC.355\tAACTCGTCGATG\tControl\t20061218\t355",
+                "PC.356\tACAGACCACTCA\tControl\t20061126\t356",
+                "PC.481\tACCAGCGACTAG\tControl\t20070314\t481",
+                "PC.593\tAGCAGCACTTGT\tControl\t20071210\t593",
+                "PC.607\tAACTGTGCGTAC\tFast\t20071112\t607",
+                "PC.634\tACAGAGTCGGCT\tFast\t20080116\t634",
+                "PC.635\tACCGCAGAGTCA\tFast\t20080116\t635",
+                "PC.636\tACGGTGAGTGTC\tFast\t20080116\t636"]
+        self.overview_map = MetadataMap(
+            *parse_mapping_file_to_dict(self.overview_map_str))
+
+        # Create the same overview tutorial map, but this time with some
+        # comments.
+        self.comment = "# Some comments about this mapping file"
+        self.map_with_comments_str = self.overview_map_str[:]
+        self.map_with_comments_str.insert(1, self.comment)
+        self.map_with_comments = MetadataMap(*parse_mapping_file_to_dict(
+            self.map_with_comments_str))
+
+        # Create a MetadataMap object that has no metadata (i.e. no sample IDs,
+        # so no metadata about samples).
+        self.empty_map = MetadataMap({}, [])
+
+        # Create a MetadataMap object that has samples (i.e. sample IDs) but
+        # not associated metadata (i.e. no columns other than SampleID).
+        self.no_metadata_str = ["#SampleID",
+                                "PC.354",
+                                "PC.355",
+                                "PC.356",
+                                "PC.481",
+                                "PC.593",
+                                "PC.607",
+                                "PC.634",
+                                "PC.635",
+                                "PC.636"]
+        self.no_metadata = MetadataMap(*parse_mapping_file_to_dict(
+            self.no_metadata_str))
+
+        # Create a MetadataMap object that has a category with only one value
+        # throughout the entire column.
+        self.single_value_str = ["#SampleID\tFoo",
+                                "PC.354\tfoo",
+                                "PC.355\tfoo",
+                                "PC.356\tfoo",
+                                "PC.481\tfoo",
+                                "PC.593\tfoo",
+                                "PC.607\tfoo",
+                                "PC.634\tfoo",
+                                "PC.635\tfoo",
+                                "PC.636\tfoo"]
+        self.single_value = MetadataMap(*parse_mapping_file_to_dict(
+            self.single_value_str))
+
+    def test_parseMetadataMap(self):
+        """Test parsing a mapping file into a MetadataMap instance."""
+        obs = MetadataMap.parseMetadataMap(self.overview_map_str)
+        self.assertEqual(obs, self.overview_map)
+
+    def test_parseMetadataMap_empty(self):
+        """Test parsing empty mapping file contents."""
+        self.assertRaises(QiimeParseError, MetadataMap.parseMetadataMap, [])
+
+    def test_eq(self):
+        """Test whether two MetadataMaps are equal."""
+        self.assertTrue(self.empty_map == MetadataMap({}, []))
+        self.assertTrue(self.overview_map == MetadataMap(
+            self.overview_map._metadata, self.overview_map.Comments))
+
+    def test_ne(self):
+        """Test whether two MetadataMaps are not equal."""
+        self.assertTrue(self.empty_map != MetadataMap({}, ["foo"]))
+        self.assertTrue(self.overview_map != MetadataMap(
+            self.overview_map._metadata, ["foo"]))
+        self.assertTrue(self.overview_map != MetadataMap({},
+            self.overview_map.Comments))
+        self.assertTrue(self.overview_map != self.empty_map)
+        self.assertTrue(self.overview_map != self.map_with_comments)
+        self.assertTrue(self.overview_map != self.no_metadata)
+
+    def test_getSampleMetadata(self):
+        """Test metadata by sample ID accessor with valid sample IDs."""
+        exp = {'BarcodeSequence': 'AGCACGAGCCTA', 'Treatment': 'Control',
+                'DOB': '20061218', 'Description': '354'}
+        obs = self.overview_map.getSampleMetadata('PC.354')
+        self.assertEqual(obs, exp)
+
+        exp = {'BarcodeSequence': 'ACCAGCGACTAG', 'Treatment': 'Control',
+                'DOB': '20070314', 'Description': '481'}
+        obs = self.map_with_comments.getSampleMetadata('PC.481')
+        self.assertEqual(obs, exp)
+
+        exp = {'BarcodeSequence': 'ACGGTGAGTGTC', 'Treatment': 'Fast',
+                'DOB': '20080116', 'Description': '636'}
+        obs = self.map_with_comments.getSampleMetadata('PC.636')
+        self.assertEqual(obs, exp)
+
+        exp = {}
+        obs = self.no_metadata.getSampleMetadata('PC.636')
+        self.assertEqual(obs, exp)
+
+    def test_getSampleMetadata_bad_sample_id(self):
+        """Test metadata by sample ID accessor with invalid sample IDs."""
+        # Nonexistent sample ID.
+        self.assertRaises(KeyError, self.overview_map.getSampleMetadata,
+            'PC.000')
+        self.assertRaises(KeyError, self.no_metadata.getSampleMetadata,
+            'PC.000')
+        # Integer sample ID.
+        self.assertRaises(KeyError, self.overview_map.getSampleMetadata, 42)
+        # Sample ID of type None.
+        self.assertRaises(KeyError, self.overview_map.getSampleMetadata, None)
+
+        # Sample ID on empty map.
+        self.assertRaises(KeyError, self.empty_map.getSampleMetadata, 's1')
+        # Integer sample ID on empty map.
+        self.assertRaises(KeyError, self.empty_map.getSampleMetadata, 1)
+        # Sample ID of None on empty map.
+        self.assertRaises(KeyError, self.empty_map.getSampleMetadata, None)
+
+    def test_getCategoryValue(self):
+        """Test category value by sample ID/category name accessor."""
+        exp = "Fast"
+        obs = self.overview_map.getCategoryValue('PC.634', 'Treatment')
+        self.assertEqual(obs, exp)
+
+        exp = "20070314"
+        obs = self.overview_map.getCategoryValue('PC.481', 'DOB')
+        self.assertEqual(obs, exp)
+
+        exp = "ACGGTGAGTGTC"
+        obs = self.map_with_comments.getCategoryValue(
+                'PC.636', 'BarcodeSequence')
+        self.assertEqual(obs, exp)
+
+    def test_getCategoryValues(self):
+        """Test category value list by sample ID/category name accessor."""
+        smpl_ids = ['PC.354', 'PC.355', 'PC.356', 'PC.481', 'PC.593', 'PC.607',
+                    'PC.634', 'PC.635', 'PC.636']
+
+        exp = ['Control','Control','Control','Control','Control','Fast'
+                    ,'Fast','Fast','Fast']
+        obs = self.overview_map.getCategoryValues(smpl_ids, 'Treatment')
+        self.assertEqual(obs, exp)
+
+    def test_isNumericCategory(self):
+        """Test checking if a category is numeric."""
+        obs = self.overview_map.isNumericCategory('Treatment')
+        self.assertEqual(obs, False)
+
+        obs = self.overview_map.isNumericCategory('DOB')
+        self.assertEqual(obs, True)
+
+    def test_hasUniqueCategoryValues(self):
+        """Test checking if a category has unique values."""
+        obs = self.overview_map.hasUniqueCategoryValues('Treatment')
+        self.assertEqual(obs, False)
+
+        obs = self.overview_map.hasUniqueCategoryValues('DOB')
+        self.assertEqual(obs, False)
+
+        obs = self.overview_map.hasUniqueCategoryValues('Description')
+        self.assertEqual(obs, True)
+
+    def test_hasSingleCategoryValue(self):
+        """Test checking if a category has only a single value."""
+        obs = self.overview_map.hasSingleCategoryValue('Treatment')
+        self.assertEqual(obs, False)
+
+        obs = self.single_value.hasSingleCategoryValue('Foo')
+        self.assertEqual(obs, True)
+
+    def test_getCategoryValue_bad_sample_id(self):
+        """Test category value by sample ID accessor with bad sample IDs."""
+        # Nonexistent sample ID.
+        self.assertRaises(KeyError, self.overview_map.getCategoryValue,
+            'PC.000', 'Treatment')
+        self.assertRaises(KeyError, self.no_metadata.getCategoryValue,
+            'PC.000', 'Treatment')
+        # Integer sample ID.
+        self.assertRaises(KeyError, self.overview_map.getCategoryValue, 42,
+            'DOB')
+        # Sample ID of type None.
+        self.assertRaises(KeyError, self.overview_map.getCategoryValue, None,
+            'Treatment')
+
+        # Sample ID on empty map.
+        self.assertRaises(KeyError, self.empty_map.getCategoryValue, 's1',
+            'foo')
+        # Integer sample ID on empty map.
+        self.assertRaises(KeyError, self.empty_map.getCategoryValue, 1,
+            'bar')
+        # Sample ID of None on empty map.
+        self.assertRaises(KeyError, self.empty_map.getCategoryValue, None,
+            'baz')
+
+    def test_getCategoryValue_bad_category(self):
+        """Test category value by sample ID accessor with bad categories."""
+        # Nonexistent category.
+        self.assertRaises(KeyError, self.overview_map.getCategoryValue,
+            'PC.354', 'foo')
+        # Integer category.
+        self.assertRaises(KeyError, self.overview_map.getCategoryValue,
+            'PC.354', 42)
+        # Category of type None.
+        self.assertRaises(KeyError, self.overview_map.getCategoryValue,
+            'PC.354', None)
+
+        # Category on map with no metadata, but that has sample IDs.
+        self.assertRaises(KeyError, self.no_metadata.getCategoryValue,
+            'PC.354', 'Treatment')
+        # Integer category on map with no metadata.
+        self.assertRaises(KeyError, self.no_metadata.getCategoryValue,
+            'PC.354', 34)
+        # Category of type None on map with no metadata.
+        self.assertRaises(KeyError, self.no_metadata.getCategoryValue,
+            'PC.354', None)
+
+    def test_SampleIds(self):
+        """Test sample IDs accessor."""
+        exp = ["PC.354", "PC.355", "PC.356", "PC.481", "PC.593", "PC.607",
+               "PC.634", "PC.635", "PC.636"]
+        obs = self.overview_map.SampleIds
+        self.assertEqual(obs, exp)
+
+        obs = self.no_metadata.SampleIds
+        self.assertEqual(obs, exp)
+
+        obs = self.empty_map.SampleIds
+        self.assertEqual(obs, [])
+
+    def test_CategoryNames(self):
+        """Test category names accessor."""
+        exp = ["BarcodeSequence", "DOB", "Description", "Treatment"]
+        obs = self.overview_map.CategoryNames
+        self.assertEqual(obs, exp)
+
+        obs = self.no_metadata.CategoryNames
+        self.assertEqual(obs, [])
+
+        obs = self.empty_map.CategoryNames
+        self.assertEqual(obs, [])
+
+    def test_filterSamples(self):
+        """Test filtering out samples from metadata map."""
+        exp = ['PC.356', 'PC.593']
+        self.overview_map.filterSamples(['PC.593', 'PC.356'])
+        obs = self.overview_map.SampleIds
+        self.assertEqual(obs, exp)
+
+        self.overview_map.filterSamples([])
+        self.assertEqual(self.overview_map.SampleIds, [])
+
+    def test_filterSamples_strict(self):
+        """Test strict checking of sample prescence when filtering."""
+        with self.assertRaises(ValueError):
+            self.overview_map.filterSamples(['PC.356', 'abc123'])
+
+        with self.assertRaises(ValueError):
+            self.empty_map.filterSamples(['foo'])
+
+    def test_filterSamples_no_strict(self):
+        """Test missing samples does not raise error."""
+        self.overview_map.filterSamples(['PC.356', 'abc123'], strict=False)
+        self.assertEqual(self.overview_map.SampleIds, ['PC.356'])
+
+        self.empty_map.filterSamples(['foo'], strict=False)
+        self.assertEqual(self.empty_map.SampleIds, [])
+
+
+    def test_is_valid_git_refname(self):
+        """Test correct validation of refnames"""
+        # valid branchnames
+        self.assertTrue(is_valid_git_refname('master'))
+        self.assertTrue(is_valid_git_refname('debuggatron_2000'))
+        self.assertTrue(is_valid_git_refname('refname/bar'))
+        self.assertTrue(is_valid_git_refname('ref.nameslu/_eggs_/spam'))
+        self.assertTrue(is_valid_git_refname('valid{0}char'.format(
+            unichr(40))))
+        self.assertTrue(is_valid_git_refname('master at head'))
+        self.assertTrue(is_valid_git_refname('bar{thing}foo'))
+
+        # case happening with git < 1.6.6
+        self.assertFalse(is_valid_git_refname(
+            '--abbrev-ref\nbaa350d7b7063d585ca293fc16ef15e0765dc9ee'))
+
+        # different invalid refnames, for a description of each group see the
+        # man page of git check-ref-format
+        self.assertFalse(is_valid_git_refname('bar/.spam/eggs'))
+        self.assertFalse(is_valid_git_refname('bar.lock/spam/eggs'))
+        self.assertFalse(is_valid_git_refname('bar.lock'))
+        self.assertFalse(is_valid_git_refname('.foobar'))
+
+        self.assertFalse(is_valid_git_refname('ref..name'))
+
+        self.assertFalse(is_valid_git_refname(u'invalid{0}char'.format(
+            unichr(177))))
+        self.assertFalse(is_valid_git_refname('invalid{0}char'.format(
+            unichr(39))))
+        self.assertFalse(is_valid_git_refname('ref~name/bar'))
+        self.assertFalse(is_valid_git_refname('refname spam'))
+        self.assertFalse(is_valid_git_refname('bar/foo/eggs~spam'))
+        self.assertFalse(is_valid_git_refname('bar:_spam_'))
+        self.assertFalse(is_valid_git_refname('eggtastic^2'))
+
+        self.assertFalse(is_valid_git_refname('areyourandy?'))
+        self.assertFalse(is_valid_git_refname('bar/*/spam'))
+        self.assertFalse(is_valid_git_refname('bar[spam]/eggs'))
+
+        self.assertFalse(is_valid_git_refname('/barfooeggs'))
+        self.assertFalse(is_valid_git_refname('barfooeggs/'))
+        self.assertFalse(is_valid_git_refname('bar/foo//////eggs'))
+
+        self.assertFalse(is_valid_git_refname('dotEnding.'))
+
+        self.assertFalse(is_valid_git_refname('@{branch'))
+
+        self.assertFalse(is_valid_git_refname('contains\\slash'))
+
+        self.assertFalse(is_valid_git_refname('$newbranch'))
+
+    def test_is_valid_git_sha1(self):
+        """ """
+
+        # valid sha1 strings
+        self.assertTrue(is_valid_git_sha1(
+            '65a9ba2ef4b126fb5b054ea6b89b457463db4ec6'))
+        self.assertTrue(is_valid_git_sha1(
+            'a29a9911e41253405494c43889925a6d79ca26db'))
+        self.assertTrue(is_valid_git_sha1(
+            'e099cd5fdea89eba929d6051fbd26cc9e7a0c961'))
+        self.assertTrue(is_valid_git_sha1(
+            '44235d322c3386bd5ce872d9d7ea2e10d27c86cb'))
+        self.assertTrue(is_valid_git_sha1(
+            '7d2fc23E04540EE92c742948cca9ed5bc54d08d1'))
+        self.assertTrue(is_valid_git_sha1(
+            'fb5dc0285a8b11f199c4f3a7547a2da38138373f'))
+        self.assertTrue(is_valid_git_sha1(
+            '0b2abAEb195ba7ebc5cfdb53213a66fbaddefdb8'))
+
+        # invalid length
+        self.assertFalse(is_valid_git_sha1('cca9ed5bc54d08d1'))
+        self.assertFalse(is_valid_git_sha1(''))
+
+        # invalid characters
+        self.assertFalse(is_valid_git_sha1(
+            'fb5dy0f85a8b11f199c4f3a75474a2das8138373'))
+        self.assertFalse(is_valid_git_sha1(
+            '0x5dcc816fbc1c2e8eX087d7d2ed8d2950a7c16b'))
+
+#run unit tests if run from command-line
+if __name__ == '__main__':
+    main()
diff --git a/tests/test_sort.py b/tests/test_sort.py
new file mode 100755
index 0000000..242532f
--- /dev/null
+++ b/tests/test_sort.py
@@ -0,0 +1,235 @@
+#!/usr/bin/env python
+# File created on 20 Apr 2013
+from __future__ import division
+
+__author__ = "Yoshiki Vazquez Baeza"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+from unittest import TestCase, main
+
+from numpy import array
+from numpy.testing import assert_almost_equal
+
+from emperor.sort import (sort_taxa_table_by_pcoa_coords,
+    sort_comparison_filenames)
+
+class TopLevelTests(TestCase):
+    def setUp(self):
+        self.otu_headers = ['PC.636', 'PC.635', 'PC.356', 'PC.481', 'PC.354',
+                'PC.593', 'PC.355', 'PC.607', 'PC.634']
+
+        self.otu_table = array([[0.02739726, 0.04697987, 0.02, 0.04697987, 0.01,
+            0.02027027, 0.01360544, 0.01342282, 0.02666667], [0.00684932,
+            0.02013423, 0.02, 0.00671141,  0., 0.00675676, 0., 0., 0.], [
+            0.14383562, 0.27516779, 0.65333333, 0.52348993, 0.38926174,
+            0.69594595, 0.28571429, 0.0738255, 0.19333333], [0., 0.02013423,
+            0.03333333, 0.01342282, 0., 0.0472973, 0., 0., 0.], [0.78767123,
+            0.45637584, 0.22, 0.39597315, 0.41610738, 0.20945946, 0.70068027,
+            0.89932886, 0.77333333], [0.,0.02013423, 0.01333333, 0.00671141,
+            0.03355705, 0.00675676, 0., 0., 0.],[0., 0., 0.01333333, 0., 0., 0.,
+            0., 0., 0.], [0.03424658, 0.16107383, 0.02666667, 0.00671141,
+            0.14765101, 0.01351351, 0., 0.01342282, 0.00666667]])
+
+        self.coords = COORDS
+        self.coords_header = ['PC.354','PC.356','PC.481','PC.593',
+            'PC.355','PC.607','PC.634', 'PC.636', 'PC.635']
+
+        self.coord_fps = ['output_data/emperor/bray_curtis_pc_transformed_q1.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q10.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q11.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q12.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q13.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q14.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q15.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q16.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q17.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q18.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q19.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q2.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q20.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q21.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q22.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q23.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q24.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q25.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q26.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q27.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q28.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q29.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q3.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q4.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q5.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q6.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q7.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q8.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q9.txt']
+
+        self.coord_fps_garbage = [
+            'output_data/emperor/bray_qurtis_pc_transformed_q1.txt',
+            'output_data/emperor/bray_111urtis_q_transformed_q10.txt',
+            'output_data/emperor/aaaaaaa.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q12.txt',
+            'output_data/emperor/qqq2223_curtis_qc_transformed_q13.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q14.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_reference.txtoutput_data/emperor/bray_curtis_pc_transformed_q15.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q16.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q17.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q18.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q19.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q2.txt',
+            'output_data/emperor/boom.txt',
+            'output_data/emperor/another_file with some characters and stuff .txt',
+            'output_data/emperor/some_other_file_that_foo_wants_to_compare.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q23.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q24.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q25.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q26.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q27.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q28.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q29.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q3.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q4.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q5.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q6.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q7.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q8.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q9.txt']
+
+
+
+    def test_sort_taxa_table_by_pcoa_coords(self):
+        """Make sure OTU table and coordinates are sorted equally"""
+
+        # case with shuffled inputs
+        o_headers, o_otu_table = sort_taxa_table_by_pcoa_coords(
+            self.coords_header, self.otu_table, self.otu_headers)
+
+        self.assertEquals(o_headers, ['PC.354','PC.356','PC.481','PC.593',
+            'PC.355','PC.607','PC.634', 'PC.636', 'PC.635'])
+        assert_almost_equal(o_otu_table, OTU_TABLE_A)
+
+        # case with shuffled inputs and fewer samples
+        o_headers, o_otu_table = sort_taxa_table_by_pcoa_coords(['PC.354',
+            'PC.356','PC.635'], self.otu_table, self.otu_headers)
+        self.assertEquals(o_headers, ['PC.354','PC.356','PC.635'])
+        assert_almost_equal(o_otu_table, array([[ 0.01, 0.02, 0.04697987],[0.,
+            0.02, 0.02013423], [0.38926174, 0.65333333, 0.27516779],[0.,
+            0.03333333, 0.02013423],[0.41610738, 0.22, 0.45637584],[0.03355705,
+            0.01333333, 0.02013423],[0., 0.01333333, 0.],[0.14765101,
+            0.02666667, 0.16107383]]))
+
+    def test_sort_comparison_filenames_regular(self):
+        """Check filenames are sorted correctly"""
+
+        # check it correctly sorts the files according to the suffix
+        out_sorted = sort_comparison_filenames(self.coord_fps)
+        self.assertEquals(out_sorted, [
+            'output_data/emperor/bray_curtis_pc_transformed_q1.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q2.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q3.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q4.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q5.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q6.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q7.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q8.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q9.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q10.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q11.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q12.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q13.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q14.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q15.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q16.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q17.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q18.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q19.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q20.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q21.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q22.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q23.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q24.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q25.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q26.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q27.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q28.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q29.txt'])
+
+        # if files with garbage are passed in, the sorting should be still
+        # consistent,putting the "garbaged" filenames at the beginning
+        out_sorted = sort_comparison_filenames(self.coord_fps_garbage)
+        self.assertEquals(out_sorted, ['output_data/emperor/aaaaaaa.txt',
+            'output_data/emperor/boom.txt',
+            'output_data/emperor/another_file with some characters and stuff .txt',
+            'output_data/emperor/some_other_file_that_foo_wants_to_compare.txt',
+            'output_data/emperor/bray_qurtis_pc_transformed_q1.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q2.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q3.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q4.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q5.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q6.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q7.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q8.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q9.txt',
+            'output_data/emperor/bray_111urtis_q_transformed_q10.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q12.txt',
+            'output_data/emperor/qqq2223_curtis_qc_transformed_q13.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q14.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_reference.txtoutput_data/emperor/bray_curtis_pc_transformed_q15.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q16.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q17.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q18.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q19.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q23.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q24.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q25.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q26.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q27.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q28.txt',
+            'output_data/emperor/bray_curtis_pc_transformed_q29.txt'])
+
+        # tricky case with extensions in things that are not the filename
+        out_sorted = sort_comparison_filenames([
+            'output_data_q1.txt/emperor/bray_curtis_pc_transformed_q9.txt',
+            'output_data/emperorq11.txt/bray_curtis_pc_transformed_q2.txt',
+            'output_data_q44.txt/emperor/bray_curtis_pc_transformed_q11.txt',
+            'output_dataq-5.txt/emperor/bray_curtis_pc_transformed_q3.txt',
+            'output_data_q511.txt/emperor/bray_curtis_pc_transformed_q1.txt'])
+        self.assertEquals(out_sorted, [
+            'output_data_q511.txt/emperor/bray_curtis_pc_transformed_q1.txt',
+            'output_data/emperorq11.txt/bray_curtis_pc_transformed_q2.txt',
+            'output_dataq-5.txt/emperor/bray_curtis_pc_transformed_q3.txt',
+            'output_data_q1.txt/emperor/bray_curtis_pc_transformed_q9.txt',
+            'output_data_q44.txt/emperor/bray_curtis_pc_transformed_q11.txt'])
+
+        # make sure nothing happens when an empty list is passed
+        self.assertEquals(sort_comparison_filenames([]), [])
+
+
+COORDS = array([[0.280399117569, -0.0060128286014, 0.0234854344148, -0.0468109474823, -0.146624450094, 0.00566979124596, -0.0354299634191, -0.255785794275, -4.84141986706e-09],
+[0.228820399536, -0.130142097093, -0.287149447883, 0.0864498846421, 0.0442951919304, 0.20604260722, 0.0310003571386, 0.0719920436501, -4.84141986706e-09],
+[0.0422628480532, -0.0139681511889, 0.0635314615517, -0.346120552134, -0.127813807608, 0.0139350721063, 0.0300206887328, 0.140147849223, -4.84141986706e-09],
+[0.232872767451, 0.139788385269, 0.322871079774, 0.18334700682, 0.0204661596818, 0.0540589147147, -0.0366250872041, 0.0998235721267, -4.84141986706e-09],
+[0.170517581885, -0.194113268955, -0.0308965283066, 0.0198086158783, 0.155100062794, -0.279923941712, 0.0576092515759, 0.0242481862127, -4.84141986706e-09],
+[-0.0913299284215, 0.424147148265, -0.135627421345, -0.057519480907, 0.151363490722, -0.0253935675552, 0.0517306152066, -0.038738217609, -4.84141986706e-09],
+[-0.349339228244, -0.120787589539, 0.115274502117, 0.0694953933826, -0.0253722182853, 0.067853201946, 0.244447634756, -0.0598827706386, -4.84141986706e-09],
+[-0.276542163845, -0.144964375408, 0.0666467344429, -0.0677109454288, 0.176070269506, 0.072969390136, -0.229889463523, -0.0465989416581, -4.84141986706e-09],
+[-0.237661393984, 0.0460527772512, -0.138135814766, 0.159061025229, -0.247484698646, -0.115211468101, -0.112864033263, 0.0647940729676, -4.84141986706e-09]])
+
+OTU_TABLE_A = array([[ 0.01, 0.02, 0.04697987, 0.02027027, 0.01360544, 0.01342282, 0.02666667, 0.02739726, 0.04697987],
+[ 0., 0.02, 0.00671141, 0.00675676, 0., 0., 0., 0.00684932, 0.02013423],
+[ 0.38926174, 0.65333333, 0.52348993, 0.69594595, 0.28571429, 0.0738255, 0.19333333, 0.14383562, 0.27516779],
+[ 0., 0.03333333, 0.01342282, 0.0472973, 0., 0., 0., 0., 0.02013423],
+[ 0.41610738, 0.22, 0.39597315, 0.20945946, 0.70068027, 0.89932886, 0.77333333, 0.78767123, 0.45637584],
+[ 0.03355705, 0.01333333, 0.00671141, 0.00675676, 0., 0., 0., 0., 0.02013423],
+[ 0., 0.01333333, 0., 0., 0., 0., 0., 0., 0.],
+[ 0.14765101, 0.02666667, 0.00671141, 0.01351351, 0., 0.01342282, 0.00666667, 0.03424658, 0.16107383]])
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/test_util.py b/tests/test_util.py
new file mode 100755
index 0000000..74426f3
--- /dev/null
+++ b/tests/test_util.py
@@ -0,0 +1,501 @@
+#!/usr/bin/env python
+# File created on 25 Jan 2013
+from __future__ import division
+
+__author__ = "Yoshiki Vazquez Baeza"
+__copyright__ = "Copyright 2013, The Emperor Project"
+__credits__ = ["Yoshiki Vazquez Baeza"]
+__license__ = "BSD"
+__version__ = "0.9.3"
+__maintainer__ = "Yoshiki Vazquez Baeza"
+__email__ = "yoshiki89 at gmail.com"
+__status__ = "Release"
+
+
+from unittest import TestCase, main
+from shutil import rmtree
+from os.path import exists, join
+from tempfile import gettempdir
+
+from numpy import array
+from numpy.testing import assert_almost_equal
+
+from emperor.util import (copy_support_files, keep_columns_from_mapping_file,
+    preprocess_mapping_file, preprocess_coords_file, EmperorInputFilesError,
+    fill_mapping_field_from_mapping_file, sanitize_mapping_file)
+
+class TopLevelTests(TestCase):
+
+    def setUp(self):
+        self.mapping_file_data = MAPPING_FILE_DATA
+        self.mapping_file_headers = ['SampleID', 'BarcodeSequence',
+            'LinkerPrimerSequence', 'Treatment', 'DOB', 'Description']
+        self.valid_columns = ['Treatment', 'DOB']
+        self.support_files_filename = gettempdir()
+        self.support_files_filename_spaces = join(gettempdir(),
+            'Directory With Spaces/AndNoSpaces')
+
+        # data for the custom axes, contains columns that are gradients
+        self.mapping_file_data_gradient = MAPPING_FILE_DATA_GRADIENT
+        self.mapping_file_headers_gradient = ['SampleID', 'Treatment', 'Time',
+            'Weight', 'Description']
+
+        self.coords_header = ['PC.355', 'PC.635', 'PC.636', 'PC.354']
+        self.coords_data = COORDS_DATA
+        self.coords_eigenvalues = array([1, 2, 3, 4])
+        self.coords_pct = array([40, 30, 20, 10])
+
+        # jackknifed test data
+        self.jk_mapping_file_headers = ['SampleID', 'C2', 'C3', 'C4']
+        self.jk_mapping_file_data = [['1', 'a', 'b', 'c'], ['2', 'd', 'e', 'f'],
+            ['3', 'g', 'h', 'i']]
+        self.jk_coords_header = [['1', '2', '3'], ['1', '2', '3'],
+            ['1', '2', '3'], ['1', '2', '3']]
+        self.jk_coords_data = [array([[1.2, 0.1, -1.2],[-2.5, -4.0, 4.5]]),
+            array([[-1.4, 0.05, 1.3],[2.6, 4.1, -4.7]]),
+            array([[-1.5, 0.05, 1.6],[2.4, 4.0, -4.8]]),
+            array([[-1.5, 0.05, 1.6],[2.4, 4.0, -4.8]])]
+        self.jk_coords_eigenvalues = [array([0.80, .11, 0.09]), array([0.76,
+            .20,0.04]), array([0.84, .14, 0.02]), array([0.84, .11, 0.05])]
+        self.jk_coords_pcts = [array([0.80, .10, 0.10]), array([0.76, .21,
+            0.03]), array([0.84, .11, 0.05]), array([0.84, .15, 0.01])]
+
+        self.jk_mapping_file_data_gradient = MAPPING_FILE_DATA_GRADIENT
+        self.jk_mapping_file_headers_gradient = ['SampleID', 'Treatment','Time',
+            'Weight', 'Description']
+        self.jk_coords_header_gradient = [['PC.354','PC.355','PC.635','PC.636'],
+            ['PC.354','PC.355','PC.635','PC.636'], ['PC.354','PC.355','PC.635',
+            'PC.636'], ['PC.354','PC.355','PC.635','PC.636']]
+        self.jk_coords_data_gradient = [array([[1.2, 0.1, -1.2, 1.1],[-2.5,
+            -4.0, 4.5, 0.3], [.5, -0.4, 3.5, 1.001], [0.67, 0.23, 1.01, 2.2]]),
+            array([[1.2, 1, -0.2, 0.1],[-2.5, -4.0, 4.5, 3.2], [.5, -0.4, 3.5,
+            1.00], [0.57, 0.27, 0.95, 2.1]]), array([[1.0, 1, -1.2, 1.1],[-2.1,
+            -2.0, 3.5, 0.3], [.5, 3, 3.5, 2], [0.60, 0.33, 1.3, 2.0]]), array([
+            [1.2, 0.1, -1.2, 1.1],[-2.5,-4.0, 4.5, 0.3], [.5, -0.4, 3.5, 1.001],
+            [0.69, 0.20, 1.01, 2.2]])]
+        self.jk_coords_eigenvalues_gradient = [array([0.80, .11, 0.09, 0.0]),
+            array([0.76, .20,0.04, 0.0]), array([0.84, .14, 0.02, 0.0]), array([
+            0.84, .11, 0.05, 0.0])]
+        self.jk_coords_pcts_gradient = [array([0.80, .10, 0.10, 0.0]), array(
+            [0.76, .21, 0.03, 0.0]), array([0.84, .11, 0.05, 0.0]), array([0.84,
+            .15, 0.01, 0])]
+
+        self.broken_mapping_file_data = BROKEN_MAPPING_FILE
+        self.broken_mapping_file_data_2_values = BROKEN_MAPPING_FILE_2_VALUES
+
+    def test_copy_support_files(self):
+        """Test the support files are correctly copied to a file path"""
+        copy_support_files(self.support_files_filename)
+        self.assertTrue(exists(join(self.support_files_filename,
+            'emperor_required_resources/')))
+
+        # related to https://github.com/qiime/emperor/issues/66
+        # the target path has spaces, the support files function will work fine
+        copy_support_files(self.support_files_filename_spaces)
+        self.assertTrue(exists(join(self.support_files_filename_spaces,
+            'emperor_required_resources/')))
+
+    def test_preprocess_mapping_file(self):
+        """Check correct preprocessing of metadata is done"""
+
+        # test it concatenates columns together correctly
+        out_data, out_headers = preprocess_mapping_file(self.mapping_file_data,
+            self.mapping_file_headers, ['Treatment', 'DOB', 'Treatment&&DOB'])
+        self.assertEquals(out_headers, ['SampleID', 'Treatment', 'DOB',
+            'Treatment&&DOB'])
+        self.assertEquals(out_data, MAPPING_FILE_DATA_CAT_A)
+
+        # test it has a different order in the concatenated columns i. e. the
+        # value of DOB comes before the value of Treatment in the result
+        out_data, out_headers = preprocess_mapping_file(self.mapping_file_data,
+            self.mapping_file_headers, ['Treatment', 'DOB', 'DOB&&Treatment'])
+        self.assertEquals(out_headers, ['SampleID', 'Treatment', 'DOB',
+            'DOB&&Treatment'])
+        self.assertEquals(out_data, MAPPING_FILE_DATA_CAT_B)
+
+        # test it filter columns properly
+        out_data, out_headers = preprocess_mapping_file(self.mapping_file_data,
+            self.mapping_file_headers, ['Treatment'])
+        self.assertEquals(out_headers, ['SampleID', 'Treatment'])
+        self.assertEquals(out_data, MAPPING_FILE_DATA_CAT_C)
+
+        # check it removes columns with unique values
+        out_data, out_headers = preprocess_mapping_file(self.mapping_file_data,
+            self.mapping_file_headers, ['SampleID', 'BarcodeSequence',
+            'LinkerPrimerSequence', 'Treatment', 'DOB', 'Description'],
+            unique=True)
+        self.assertEquals(out_headers, ['SampleID', 'LinkerPrimerSequence',
+            'Treatment', 'DOB'])
+        self.assertEquals(out_data, MAPPING_FILE_DATA_CAT_D)
+
+        # check it removes columns where there is only one value
+        out_data, out_headers = preprocess_mapping_file(self.mapping_file_data,
+            self.mapping_file_headers, ['SampleID', 'BarcodeSequence',
+            'LinkerPrimerSequence', 'Treatment', 'DOB', 'Description'],
+            single=True)
+        self.assertEquals(out_headers,['SampleID', 'BarcodeSequence',
+            'Treatment', 'DOB', 'Description'])
+        self.assertEquals(out_data, MAPPING_FILE_DATA_CAT_E)
+
+        # keep only treatment concat treatment and DOB and remove all
+        # categories with only one value and all with unique values for field
+        out_data, out_headers = preprocess_mapping_file(self.mapping_file_data,
+            self.mapping_file_headers, ['Treatment', 'Treatment&&DOB'],
+            unique=True, single=True)
+        self.assertEquals(out_headers, ['SampleID', 'Treatment',
+            'Treatment&&DOB'])
+        self.assertEquals(out_data, MAPPING_FILE_DATA_CAT_F)
+
+        out_data, out_headers = preprocess_mapping_file(self.mapping_file_data,
+            self.mapping_file_headers, ['Treatment', 'DOB'], clones=3)
+        self.assertEquals(out_data, MAPPING_FILE_DATA_DUPLICATED)
+        self.assertEquals(out_headers, ['SampleID', 'Treatment', 'DOB'])
+
+
+    def test_keep_columns_from_mapping_file(self):
+        """Check correct selection of metadata is being done"""
+
+        # test it returns the same data
+        out_data, out_headers = keep_columns_from_mapping_file(
+            self.mapping_file_data, self.mapping_file_headers, [])
+        self.assertEquals(out_data, [[], [], [], [], [], [], [], [], []])
+        self.assertEquals(out_headers, [])
+
+        # test it can filter a list of columns
+        out_data, out_headers = keep_columns_from_mapping_file(
+            self.mapping_file_data, self.mapping_file_headers, [
+            'SampleID', 'LinkerPrimerSequence', 'Description'])
+        self.assertEquals(out_headers, ['SampleID', 'LinkerPrimerSequence',
+            'Description'])
+        self.assertEquals(out_data, PRE_PROCESS_B)
+
+        # test correct negation of filtering
+        out_data, out_headers = keep_columns_from_mapping_file(
+            self.mapping_file_data, self.mapping_file_headers,
+            ['LinkerPrimerSequence', 'Description'], True)
+        self.assertEquals(out_data, PRE_PROCESS_A)
+        self.assertEquals(out_headers,  ['SampleID', 'BarcodeSequence',
+            'Treatment', 'DOB'])
+
+    def test_preprocess_coords_file(self):
+        """Check correct processing is applied to the coords"""
+
+        # case with custom axes
+        out_coords_header, out_coords_data, out_eigenvals, out_pcts,\
+            out_coords_low, out_coords_high, o_clones = preprocess_coords_file(
+            self.coords_header, self.coords_data, self.coords_eigenvalues,
+            self.coords_pct, self.mapping_file_headers_gradient,
+            self.mapping_file_data_gradient, ['Time'])
+
+        expected_coords_data = array([[ 0.03333333, -0.2, -0.1,0.06, -0.06],
+           [0.03333333, -0.3, 0.04, -0.1,0.15],
+           [0.2, 0.1, -0.1, -0.2, 0.08],
+           [-0.3, 0.04, -0.01,  0.06, -0.34]])
+
+        self.assertEquals(out_coords_header, self.coords_header)
+        self.assertEquals(out_coords_high, None)
+        self.assertEquals(out_coords_low, None)
+        assert_almost_equal(self.coords_eigenvalues, array([1, 2, 3, 4]))
+        assert_almost_equal(self.coords_pct, array([40, 30, 20, 10]))
+        self.assertEquals(o_clones, 0)
+
+        # check each individual value because currently cogent assertEquals
+        # fails when comparing the whole matrix at once
+        for out_el, exp_el in zip(out_coords_data, expected_coords_data):
+            for out_el_sub, exp_el_sub in zip(out_el, exp_el):
+                self.assertAlmostEquals(out_el_sub, exp_el_sub)        
+
+        # case for jackknifing, based on qiime/tests/test_util.summarize_pcoas
+        out_coords_header, out_coords_data, out_eigenvals, out_pcts,\
+            out_coords_low, out_coords_high, o_clones = preprocess_coords_file(
+            self.jk_coords_header, self.jk_coords_data,
+            self.jk_coords_eigenvalues, self.jk_coords_pcts,
+            self.jk_mapping_file_headers, self.jk_mapping_file_data,
+            jackknifing_method='sdev')
+
+        self.assertEquals(out_coords_header, ['1', '2', '3'])
+        assert_almost_equal(out_coords_data, array([[ 1.4, -0.0125, -1.425],
+            [-2.475, -4.025, 4.7]]))
+        assert_almost_equal(out_eigenvals, array([ 0.81, 0.14, 0.05]))
+        assert_almost_equal(out_pcts, array([0.8, 0.1, 0.1]))
+        self.assertEquals(o_clones, 0)
+
+        # test the coords are working fine
+        assert_almost_equal(out_coords_low, array([[-0.07071068, -0.0375,
+            -0.10307764], [-0.04787136, -0.025, -0.07071068]]))
+        assert_almost_equal(out_coords_high, array([[ 0.07071068, 0.0375, 
+            0.10307764], [0.04787136, 0.025, 0.07071068]]))
+
+        # test custom axes and jackknifed plots
+        out_coords_header, out_coords_data, out_eigenvals, out_pcts,\
+            out_coords_low, out_coords_high, o_clones = preprocess_coords_file(
+            self.jk_coords_header_gradient, self.jk_coords_data_gradient,
+            self.jk_coords_eigenvalues_gradient, self.jk_coords_pcts_gradient,
+            self.jk_mapping_file_headers_gradient,
+            self.jk_mapping_file_data_gradient, custom_axes=['Time'],
+            jackknifing_method='sdev')
+
+        self.assertEquals(out_coords_header, ['PC.354', 'PC.355', 'PC.635',
+            'PC.636'])
+        assert_almost_equal(out_coords_data, array([[-2.4, 1.15, 0.55, -0.95,
+            0.85], [0.73333333, -2.4, -3.5, 4.25, 1.025], [0.73333333, 0.5,
+            0.45, 3.5, 1.2505], [2.3, 0.6325, 0.2575, 1.0675, 2.125]]))
+        assert_almost_equal(out_eigenvals, array([ 0.81, 0.14, 0.05, 0.]))
+        assert_almost_equal(out_pcts, array([ 0.8,  0.1,  0.1,  0. ]))
+
+        # test the coords are working fine
+        assert_almost_equal(out_coords_low, array([[ 0., -0.25980762, -0.25,
+            -0.25], [ 0., -0.5, -0.25, -0.725], [ 0., -0.85, -0., -0.24983344],
+            [ 0., -0.02809953, -0.07877976, -0.04787136]]))
+        assert_almost_equal(out_coords_high, array([[1.00000000e-05, 
+            2.59807621e-01, 2.50000000e-01, 2.50000000e-01], [1.00000000e-05,
+            5.00000000e-01, 2.50000000e-01, 7.25000000e-01], [1.00000000e-05,
+            8.50000000e-01, 0.00000000e+00, 2.49833445e-01], [1.00000000e-05,
+            2.80995255e-02, 7.87797563e-02, 4.78713554e-02]]))
+        self.assertEquals(o_clones, 0)
+
+    def test_preprocess_coords_file_comparison(self):
+        """Check the cases for comparisons plots and the special usages"""
+        # shouldn't allow a comparison computation with only one file
+        self.assertRaises(AssertionError, preprocess_coords_file,
+            self.coords_header, self.coords_data, self.coords_eigenvalues,
+            self.coords_pct, self.mapping_file_headers_gradient,
+            self.mapping_file_data_gradient, None, None, True)
+
+        out_coords_header, out_coords_data, out_eigenvals, out_pcts,\
+            out_coords_low, out_coords_high, o_clones = preprocess_coords_file(
+            self.jk_coords_header, self.jk_coords_data,
+            self.jk_coords_eigenvalues, self.jk_coords_pcts,
+            self.jk_mapping_file_headers, self.jk_mapping_file_data,
+            is_comparison=True)
+
+        self.assertEquals(out_coords_header, ['1_0', '2_0', '3_0', '1_1', '2_1',
+            '3_1', '1_2', '2_2', '3_2', '1_3', '2_3', '3_3'])
+        assert_almost_equal(out_coords_data, array([[ 1.2 , 0.1 , -1.2],[-2.5,
+            -4., 4.5], [-1.4, 0.05, 1.3], [ 2.6, 4.1, -4.7], [-1.5, 0.05, 1.6],
+            [ 2.4, 4., -4.8], [-1.5, 0.05, 1.6], [ 2.4, 4., -4.8]]))
+        assert_almost_equal(out_eigenvals, self.jk_coords_eigenvalues[0])
+        assert_almost_equal(out_pcts, self.jk_coords_pcts[0])
+        self.assertEquals(out_coords_low, None)
+        self.assertEquals(out_coords_high, None)
+        self.assertEquals(o_clones, 4)
+
+    def test_fill_mapping_field_from_mapping_file(self):
+        """Check the values are being correctly filled in"""
+
+        # common usage example
+        out_data = fill_mapping_field_from_mapping_file(
+            self.broken_mapping_file_data, self.mapping_file_headers_gradient,
+            'Time:200;Weight:800')
+        self.assertEquals(out_data, [
+            ['PC.354', 'Control','3', '40', 'Control20061218'],
+            ['PC.355', 'Control','200', '44', 'Control20061218'],
+            ['PC.635', 'Fast','9', '800', 'Fast20080116'],
+            ['PC.636', 'Fast','12', '37.22', 'Fast20080116']])
+
+        # more than one value to fill empty values with
+        self.assertRaises(AssertionError, fill_mapping_field_from_mapping_file,
+            self.broken_mapping_file_data, self.mapping_file_headers_gradient,
+            'Time:200,300;Weight:800')
+
+        # non-existing header in mapping file
+        self.assertRaises(EmperorInputFilesError, fill_mapping_field_from_mapping_file,
+            self.broken_mapping_file_data, self.mapping_file_headers_gradient,
+            'Spam:Foo')
+        
+        # testing multiple values
+        out_data = fill_mapping_field_from_mapping_file(
+            self.broken_mapping_file_data_2_values, self.mapping_file_headers_gradient,
+            'Time:Treatment==Control=444;Time:Treatment==Fast=888')
+        self.assertEquals(out_data, [
+            ['PC.354', 'Control', '3', '40', 'Control20061218'], 
+            ['PC.355', 'Control', '444', '44', 'Control20061218'], 
+            ['PC.635', 'Fast', '888', 'x', 'Fast20080116'], 
+            ['PC.636', 'Fast', '12', '37.22', 'Fast20080116']])
+        
+        # testing multiple values: blank column name
+        self.assertRaises(AssertionError, fill_mapping_field_from_mapping_file,
+            self.broken_mapping_file_data_2_values, self.mapping_file_headers_gradient,
+            'Time:Treatment===200600020')
+        
+        # testing multiple values: wrong order
+        self.assertRaises(AssertionError, fill_mapping_field_from_mapping_file,
+            self.broken_mapping_file_data_2_values, self.mapping_file_headers_gradient,
+            'Time:Treatment=Control==200600020')
+        
+        # testing multiple values: error when more than 1 value is passed
+        self.assertRaises(AssertionError, fill_mapping_field_from_mapping_file,
+            self.broken_mapping_file_data_2_values, self.mapping_file_headers_gradient,
+            'Time:Treatment=Control==200600020,435')
+ 
+    def test_sanitize_mapping_file(self):
+        """Check the mapping file strings are sanitized for it's use in JS"""
+
+        o_sanitized_headers, o_sanitized_data = sanitize_mapping_file(
+            UNSANITZIED_MAPPING_DATA, ['SampleID', 'BarcodeSequence',
+            'LinkerPrimerSequence', 'Treatment', 'DOB', 'Descr"""""iption'])
+
+        self.assertEquals(o_sanitized_data, ['SampleID', 'BarcodeSequence',
+            'LinkerPrimerSequence', 'Treatment', 'DOB','Description'])
+        self.assertEquals(o_sanitized_headers, [
+['PC.354', "Dr. Bronners", 'Control', '20061218', 'Control_mouse_I.D._354'],
+['PC.355', 'AACTCGTCGATG', "Control", '20061218', 'Control_mouse_I.D._355'],
+["PC356", 'ACAGACCACTCA', 'Control', '20061126', 'Control_mouse_I.D._356'],
+['PC.481', 'ACAGCACTAG', 'Control', '20070314', 'Control_mouse_I.D._481'],
+['PC.593', 'AGCAGCACTTGT', 'Control', '20071210', 'Control_mouse_I.D._593']])
+
+
+MAPPING_FILE_DATA = [
+    ['PC.354','AGCACGAGCCTA','YATGCTGCCTCCCGTAGGAGT','Control','20061218','Control_mouse_I.D._354'],
+    ['PC.355','AACTCGTCGATG','YATGCTGCCTCCCGTAGGAGT','Control','20061218','Control_mouse_I.D._355'],
+    ['PC.356','ACAGACCACTCA','YATGCTGCCTCCCGTAGGAGT','Control','20061126','Control_mouse_I.D._356'],
+    ['PC.481','ACCAGCGACTAG','YATGCTGCCTCCCGTAGGAGT','Control','20070314','Control_mouse_I.D._481'],
+    ['PC.593','AGCAGCACTTGT','YATGCTGCCTCCCGTAGGAGT','Control','20071210','Control_mouse_I.D._593'],
+    ['PC.607','AACTGTGCGTAC','YATGCTGCCTCCCGTAGGAGT','Fast','20071112','Fasting_mouse_I.D._607'],
+    ['PC.634','ACAGAGTCGGCT','YATGCTGCCTCCCGTAGGAGT','Fast','20080116','Fasting_mouse_I.D._634'],
+    ['PC.635','ACCGCAGAGTCA','YATGCTGCCTCCCGTAGGAGT','Fast','20080116','Fasting_mouse_I.D._635'],
+    ['PC.636','ACGGTGAGTGTC','YATGCTGCCTCCCGTAGGAGT','Fast','20080116','Fasting_mouse_I.D._636']]
+
+PRE_PROCESS_A = [
+    ['PC.354','AGCACGAGCCTA', 'Control','20061218'],
+    ['PC.355','AACTCGTCGATG', 'Control','20061218'],
+    ['PC.356','ACAGACCACTCA', 'Control','20061126'],
+    ['PC.481','ACCAGCGACTAG', 'Control','20070314'],
+    ['PC.593','AGCAGCACTTGT', 'Control','20071210'],
+    ['PC.607','AACTGTGCGTAC', 'Fast','20071112'],
+    ['PC.634','ACAGAGTCGGCT', 'Fast','20080116'],
+    ['PC.635','ACCGCAGAGTCA', 'Fast','20080116'],
+    ['PC.636','ACGGTGAGTGTC', 'Fast','20080116']]
+
+PRE_PROCESS_B = [
+    ['PC.354', 'YATGCTGCCTCCCGTAGGAGT', 'Control_mouse_I.D._354'],
+    ['PC.355', 'YATGCTGCCTCCCGTAGGAGT', 'Control_mouse_I.D._355'],
+    ['PC.356', 'YATGCTGCCTCCCGTAGGAGT', 'Control_mouse_I.D._356'],
+    ['PC.481', 'YATGCTGCCTCCCGTAGGAGT', 'Control_mouse_I.D._481'],
+    ['PC.593', 'YATGCTGCCTCCCGTAGGAGT', 'Control_mouse_I.D._593'],
+    ['PC.607', 'YATGCTGCCTCCCGTAGGAGT', 'Fasting_mouse_I.D._607'],
+    ['PC.634', 'YATGCTGCCTCCCGTAGGAGT', 'Fasting_mouse_I.D._634'],
+    ['PC.635', 'YATGCTGCCTCCCGTAGGAGT', 'Fasting_mouse_I.D._635'],
+    ['PC.636', 'YATGCTGCCTCCCGTAGGAGT', 'Fasting_mouse_I.D._636']]
+
+MAPPING_FILE_DATA_CAT_A = [
+    ['PC.354', 'Control','20061218', 'Control20061218'],
+    ['PC.355', 'Control','20061218', 'Control20061218'],
+    ['PC.356', 'Control','20061126', 'Control20061126'],
+    ['PC.481', 'Control','20070314', 'Control20070314'],
+    ['PC.593', 'Control','20071210', 'Control20071210'],
+    ['PC.607', 'Fast','20071112', 'Fast20071112'],
+    ['PC.634', 'Fast','20080116', 'Fast20080116'],
+    ['PC.635', 'Fast','20080116', 'Fast20080116'],
+    ['PC.636', 'Fast','20080116', 'Fast20080116']]
+
+MAPPING_FILE_DATA_CAT_B = [
+    ['PC.354', 'Control','20061218', '20061218Control'],
+    ['PC.355', 'Control','20061218', '20061218Control'],
+    ['PC.356', 'Control','20061126', '20061126Control'],
+    ['PC.481', 'Control','20070314', '20070314Control'],
+    ['PC.593', 'Control','20071210', '20071210Control'],
+    ['PC.607', 'Fast','20071112', '20071112Fast'],
+    ['PC.634', 'Fast','20080116', '20080116Fast'],
+    ['PC.635', 'Fast','20080116', '20080116Fast'],
+    ['PC.636', 'Fast','20080116', '20080116Fast']]
+
+MAPPING_FILE_DATA_CAT_C = [['PC.354', 'Control'], ['PC.355', 'Control'],
+    ['PC.356', 'Control'], ['PC.481', 'Control'], ['PC.593', 'Control'],
+    ['PC.607', 'Fast'], ['PC.634', 'Fast'], ['PC.635', 'Fast'],
+    ['PC.636', 'Fast']]
+
+MAPPING_FILE_DATA_CAT_D = [
+    ['PC.354', 'YATGCTGCCTCCCGTAGGAGT', 'Control', '20061218'],
+    ['PC.355', 'YATGCTGCCTCCCGTAGGAGT', 'Control', '20061218'],
+    ['PC.356', 'YATGCTGCCTCCCGTAGGAGT', 'Control', '20061126'],
+    ['PC.481', 'YATGCTGCCTCCCGTAGGAGT', 'Control', '20070314'],
+    ['PC.593', 'YATGCTGCCTCCCGTAGGAGT', 'Control', '20071210'],
+    ['PC.607', 'YATGCTGCCTCCCGTAGGAGT', 'Fast', '20071112'],
+    ['PC.634', 'YATGCTGCCTCCCGTAGGAGT', 'Fast', '20080116'],
+    ['PC.635', 'YATGCTGCCTCCCGTAGGAGT', 'Fast', '20080116'],
+    ['PC.636', 'YATGCTGCCTCCCGTAGGAGT', 'Fast', '20080116']]
+
+MAPPING_FILE_DATA_CAT_E = [
+    ['PC.354', 'AGCACGAGCCTA', 'Control', '20061218', 'Control_mouse_I.D._354'],
+    ['PC.355', 'AACTCGTCGATG', 'Control', '20061218', 'Control_mouse_I.D._355'],
+    ['PC.356', 'ACAGACCACTCA', 'Control', '20061126', 'Control_mouse_I.D._356'],
+    ['PC.481', 'ACCAGCGACTAG', 'Control', '20070314', 'Control_mouse_I.D._481'],
+    ['PC.593', 'AGCAGCACTTGT', 'Control', '20071210', 'Control_mouse_I.D._593'],
+    ['PC.607', 'AACTGTGCGTAC', 'Fast', '20071112', 'Fasting_mouse_I.D._607'],
+    ['PC.634', 'ACAGAGTCGGCT', 'Fast', '20080116', 'Fasting_mouse_I.D._634'],
+    ['PC.635', 'ACCGCAGAGTCA', 'Fast', '20080116', 'Fasting_mouse_I.D._635'],
+    ['PC.636', 'ACGGTGAGTGTC', 'Fast', '20080116', 'Fasting_mouse_I.D._636']]
+
+MAPPING_FILE_DATA_CAT_F = [
+    ['PC.354', 'Control', 'Control20061218'],
+    ['PC.355', 'Control', 'Control20061218'],
+    ['PC.356', 'Control', 'Control20061126'],
+    ['PC.481', 'Control', 'Control20070314'],
+    ['PC.593', 'Control', 'Control20071210'],
+    ['PC.607', 'Fast', 'Fast20071112'],
+    ['PC.634', 'Fast', 'Fast20080116'],
+    ['PC.635', 'Fast', 'Fast20080116'],
+    ['PC.636', 'Fast', 'Fast20080116']]
+
+MAPPING_FILE_DATA_GRADIENT = [
+    ['PC.354', 'Control','3', '40', 'Control20061218'],
+    ['PC.355', 'Control','9', '44', 'Control20061218'],
+    ['PC.635', 'Fast','9', '44', 'Fast20080116'],
+    ['PC.636', 'Fast','12', '37.22', 'Fast20080116']]
+
+MAPPING_FILE_DATA_DUPLICATED = [['PC.354_0', 'Control', '20061218'],
+['PC.355_0', 'Control', '20061218'],
+['PC.356_0', 'Control', '20061126'],
+['PC.481_0', 'Control', '20070314'],
+['PC.593_0', 'Control', '20071210'],
+['PC.607_0', 'Fast', '20071112'],
+['PC.634_0', 'Fast', '20080116'],
+['PC.635_0', 'Fast', '20080116'],
+['PC.636_0', 'Fast', '20080116'],
+['PC.354_1', 'Control', '20061218'],
+['PC.355_1', 'Control', '20061218'],
+['PC.356_1', 'Control', '20061126'],
+['PC.481_1', 'Control', '20070314'],
+['PC.593_1', 'Control', '20071210'],
+['PC.607_1', 'Fast', '20071112'],
+['PC.634_1', 'Fast', '20080116'],
+['PC.635_1', 'Fast', '20080116'],
+['PC.636_1', 'Fast', '20080116'],
+['PC.354_2', 'Control', '20061218'],
+['PC.355_2', 'Control', '20061218'],
+['PC.356_2', 'Control', '20061126'],
+['PC.481_2', 'Control', '20070314'],
+['PC.593_2', 'Control', '20071210'],
+['PC.607_2', 'Fast', '20071112'],
+['PC.634_2', 'Fast', '20080116'],
+['PC.635_2', 'Fast', '20080116'],
+['PC.636_2', 'Fast', '20080116']]
+
+COORDS_DATA = array([
+    [-0.2, -0.1, 0.06, -0.06],
+    [-0.3, 0.04, -0.1, 0.15],
+    [0.1, -0.1, -0.2, 0.08],
+    [0.04, -0.01, 0.06, -0.34]])
+
+BROKEN_MAPPING_FILE = [
+    ['PC.354', 'Control','3', '40', 'Control20061218'],
+    ['PC.355', 'Control','y', '44', 'Control20061218'],
+    ['PC.635', 'Fast','9', 'x', 'Fast20080116'],
+    ['PC.636', 'Fast','12', '37.22', 'Fast20080116']]
+    
+BROKEN_MAPPING_FILE_2_VALUES = [
+    ['PC.354', 'Control','3', '40', 'Control20061218'],
+    ['PC.355', 'Control','NA', '44', 'Control20061218'],
+    ['PC.635', 'Fast', 'NA', 'x', 'Fast20080116'],
+    ['PC.636', 'Fast','12', '37.22', 'Fast20080116']]
+
+UNSANITZIED_MAPPING_DATA = [
+['PC.354', "Dr. Bronner's", 'Cont"rol', '20061218', 'Control_mouse_I.D._354'],
+['PC.355', 'AACTCGTCGATG', "Con''trol", '20061218', 'Control_mouse_I.D._355'],
+["PC'356", 'ACAGACCACTCA', 'Control', '20061126', 'Control_mouse_I.D._"356'],
+['PC.481', 'AC"AGC"ACTAG', 'Control', '20070314', 'Control_mouse_I.D._481'],
+['PC.593', 'AGCAGCACTTGT', 'Control', '20071210', 'Control_mouse_I.D._593']]
+
+if __name__ == "__main__":
+    main()

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/emperor.git



More information about the debian-med-commit mailing list