diff --git a/Images/Lerp_examples.jpg b/Images/Lerp_examples.jpg
new file mode 100644
index 0000000..2abd987
Binary files /dev/null and b/Images/Lerp_examples.jpg differ
diff --git a/Images/MC_Cases.jpg b/Images/MC_Cases.jpg
new file mode 100644
index 0000000..f51a61c
Binary files /dev/null and b/Images/MC_Cases.jpg differ
diff --git a/Images/MetaballContact.jpg b/Images/MetaballContact.jpg
new file mode 100644
index 0000000..3be9794
Binary files /dev/null and b/Images/MetaballContact.jpg differ
diff --git a/Images/Metaballs_vimeoLink.png b/Images/Metaballs_vimeoLink.png
new file mode 100644
index 0000000..a5d0fea
Binary files /dev/null and b/Images/Metaballs_vimeoLink.png differ
diff --git a/Images/NormalCalc1D.jpg b/Images/NormalCalc1D.jpg
new file mode 100644
index 0000000..c967d61
Binary files /dev/null and b/Images/NormalCalc1D.jpg differ
diff --git a/Images/Untitled.png b/Images/Untitled.png
new file mode 100644
index 0000000..3ec9402
Binary files /dev/null and b/Images/Untitled.png differ
diff --git a/Images/Voxel_Indexing.jpg b/Images/Voxel_Indexing.jpg
new file mode 100644
index 0000000..760f3cd
Binary files /dev/null and b/Images/Voxel_Indexing.jpg differ
diff --git a/Images/isoValueEquation.png b/Images/isoValueEquation.png
new file mode 100644
index 0000000..24f6cfc
Binary files /dev/null and b/Images/isoValueEquation.png differ
diff --git a/Images/normalEquation.png b/Images/normalEquation.png
new file mode 100644
index 0000000..374bf86
Binary files /dev/null and b/Images/normalEquation.png differ
diff --git a/README.md b/README.md
index 342e5a3..c6162b1 100644
--- a/README.md
+++ b/README.md
@@ -1,105 +1,70 @@
-# Project 6: Implicit surfaces - Marching cubes
+# Metaballic Lava Lamp
-**Goal:** Implement an isosurface created from metaballs using the marching cubes algorithm.
+[](https://vimeo.com/227361586)
-Metaballs are organic-looking n-dimensional objects. We will be implementing a 3-dimensional metaballs. They are great to make bloppy shapes. An isosurface is created whenever the metaball function crosses a certain threshold, called isolevel. The metaball function describes the total influences of each metaball to a given points. A metaball influence is a function between its radius and distance to the point:
+## Overview
-`f(point) = (radius * radius) / (distance * distance)`
+Metaballs are organic looking n-dimensional objects that are really popular for creating smooth and dynamic shapes. Each metaball is defined as a function having n-dimensions (in this case 3 dimensions). A threshold, also known as the iso-level is chosen to define the iso-surface formed from the combined influence of all the metaballs which defines the resulting shape.
-By summing up all these influences, you effectively describes all the points that are greater than the isolevel as inside, and less than the isolevel as outside (or vice versa). As an observation, the bigger the metaball's radius is, the bigger its influence is.
+## Technical Approach
-Marching cubes essentially voxelize the space, then generate triangles based on the density function distribution at the corners of each voxel. By increasing the voxelized grid's resolution, the surface eventually gets that blobby, organic look of the metaballs. Marching cubes can achieve a similar effect to ray marching for rendering implicit surfaces, but in addition to the rendered image, you also retain actual geometries.
+Since the resulting iso-surface is defined by a density or influence function, the naive approach would be to evaluate that function for every point in space, where the resolution of the space would define the smoothness of the surface. This as one might expect is ridiculously expensive computationally speaking, but there are better techniques we can leverage:
-Marching cubes are commonly used in MRI scanning, where you can generate geometries for the scans. Marching cubes are also used to generate complex terrains with caves in games. The additional geometry information can handily support collision and other physical calculation for game engines. For example, their bounding boxes can then be computed to construct the acceleration data structure for collisions.
+### Marching Cubes Algorithm
-**Warning**: this assignment option requires more effort than the ray marching option. The two base codes diverge significantly, so switching options midway can be costly for your time and effort.
+
-## Resources
-We suggest reading the following resources before starting your assignment:
-
-- [Generating complex terrain](https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch01.html) from [GPU Gems 3](https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_pref01.html).
-- [Polygonising a scalar field](http://paulbourke.net/geometry/polygonise/) by Paul Bourke.
-- [Marching squares](http://jamie-wong.com/2014/08/19/metaballs-and-marching-squares/) by Jamie Wong.
-
-## Base code framework
-
-We have provided a basecode as a reference. You are welcome to modify the framework for your project. The basecode implements metaballs on the CPU.
-
-_main.js_:
+To ease the computation involved, we voxelize the space up to a desired resolution and then for every voxel query the density function and use the reulting values to draw a 3D polygonal approximation of the surface. There are 256 (2^8) configurations for a voxel sampled at its 8 corners, however only 15 different configurations can be used to represent all 256 due to symmetry and with the help of rotations; and all of these configurations can be represented with upto 5 triangles/voxel.
- - `App`:
+#### Linear Interpolation
-This is a global configuration object. All information for the marching cubes are stored here.
+
-**Note**: `App.visualDebug` is a global control of all the visual debugging components. Even though it is helpful for development, it could be memory intensive. Toggle this flag off for better perforamance at high resolution.
+_A 2D example showing how varying densities can effect the edge (isoline) that is formed._
-_marching_cubes.js_:
+The polygonal configurations above can be made a more accurate representation of the isosurface even at low resolutions by using the density values at the corners to find new points on the bounding box of the voxel to define the lines that make the triangles of the isosurface.
- - `class MarchingCubes`:
- This class encapsulates everything about the metaballs, grid, voxels, and sampling information.
+### Look Up Tables
- - `class Voxel`:
- This class contains information about a single voxel, and its sample points. Polygonization happens here.
+The eight corners of the voxel can be represented by an 8-bit number, where 1 means the isovalue is above or below the isolevel based on some predefined threshold.And the twelve edges of the voxel can be represented as a 12-bit number, where 1 means that the isosurface intersects with this edge.
-_inspect_point.js_:
+#### EDGE_TABLE:
- - `class InspectPoint`:
- This class simply contains a single sample point that can output its value on the screen at its pixel location.
+This table returns a 12-bit number that represents the edges intersected by the isosurface. For each intersected edge, we can compute the linearly interpolated vertex position on the edge according to the isovalue at each end corner of the edge.
-_metaball.js_:
+#### TRI_TABLE:
- - `class Metaball`:
- This class represents a single metaball.
+
-_marching_cube_LUT.js_:
+This table stores the triangle indices corresponding to the vertex positions on the edges identified above. Every 16 elements in the table represents a possible polygonizing configuration. Within each configuration, every three consecutive elements represents the indices of a triangle that should be created from the edges above.
-This file contains the edge table and the triangle table for the marching cubes.
+### Normal Calculations
-## Animate metaballs (5 points)
-Implement the `update` for metaballs to move its position based velocity. Reverse the velocity whenever the metaball goes out of bounds. Since the metaball function is not well defined at the boundaries, maintain an additional small margin so that the metaball can reverse its moving direction before reaching the bounds.
+Having implemented all of the above, our metaballs would look quite low poly at lower resolutions. An easy fix for this is to calculate the normal for all the points that makes the polygonal configuration of the voxel. This gives us per-vertex data for normals which when passed through a fragment shader results in a much smoother surface than before (it is the equivalent of the switch from having normals per face to having normals per vertex of the mesh).
-## Metaball function (2 points)
-Implement the metaball function inside `sample` of `MarchingCubes`. This function should return the total influences of all moving metaballs with respect to a given point.
+Calculating the normal at an arbitrary point involves finding the gradient at that point. This can be expensive so a good approximation for the same is to sample neighboring points along the n-dimensions, and use their difference to approximate the slope.
-## Sampling at corners (15 points)
-In order to polygonize a voxel, generate new samples at each corner of the voxel. Their isovalues must be updated as the metaball function changes due of metaballs moving.
+A 1D representation would be:
-## Polygonization (50 points)
-Implement `polygonize` inside `Cell` class. This function should return the list of **vertices** and **normals** of the triangles polygonized in the voxel.
+
-### Vertices (30 points out of 50)
-To compute the vertices, we have provided the look-up tables from Paul Bourke's. The table assumes the following indexing scheme:
-
+For 3D density functions the normal would be represented as:
-- _The eight corners can be represented as an 8-bit number, where 1 means the isovalue is above or below the isolevel based on your implementation._
-- _The twelve edges can be represented as a 12-bit number, where 1 means that the isosurface intersects with this edge._
+
-- **EDGE_TABLE**: This table returns a 12-bit number that represents the edges intersected by the isosurface. For each intersected edge, compute the linearly interpolated vertex position on the edge according to the isovalue at each end corner of the edge.
+### Density Function
+Defined as the sum of the influences of all the metaballs within its vicinity, which have a inverse square fall-off in terms of intensity
-- **TRI_TABLE**: This table acts as the triangle indices. Every 16 elements in the table represents a possible polygonizing configuration. Within each configuration, every three consecutive elements represents the indices of a triangle that should be created from the edges above.
+
-### Normals (20 points out of 50)
-Compute the normals using the gradient of the vertex with respect to the x, y, and z. The normals are then used for shading different materials.
+## More about the Marching Cubes Algorithm
+The Marching cubes algorithm can achieve a effect similar to ray marching for rendering implicit surfaces, but in addition to the rendered image, you also retain geometry.
-## Meshing (18 points)
-The mesh for the metaball's isosurface should be created once. At each frame, using the list of **vertices** and **normals** polygonized from the voxels, update the mesh's geometry for the isosurface. Notice that the total volume of the mesh does change.
+Common Use Cases:
+- In MRI scanning, to generate geometry for the scans.
+- To generate complex terrain for games. The resulting geometrical information can handily support collision and other physical calculation for game engines. For example, their bounding boxes can then be computed to construct the acceleration data structure for collisions.
-## Materials and post-processing (10 points)
-Interesting shader materials beyond just the provided threejs materials. We encourage using your previous shaders assignment for this part.
-
-## Extra credits (Up to 30 points)
-- Metaball can be positive or negative. A negative metaball will substract from the surface, which pushed the surface inward. **Implement a scene with both positive and negative metaballs. (10 points)**
-- **More implicit surfaces!** For example: planes, mesh, etc.). Some very interesting ideas are to blend your metaballs into those surfaces. **(5 points for each)**
-
-## Submission
-
-- Update `README.md` to contain a solid description of your project
-- Publish your project to gh-pages. `npm run deploy`. It should now be visible at http://username.github.io/repo-name
-- Create a [pull request](https://help.github.com/articles/creating-a-pull-request/) to this repository, and in the comment, include a link to your published project.
-- Submit the link to your pull request on Canvas.
-
-## Deploy
-- `npm run build`
-- Add and commit all changes
-- `npm run deploy`
-- If you're having problems with assets not linking correctly, make sure you wrap you're filepaths in `require()`. This will make the bundler package and your static assets as well. So, instead of `loadTexture('./images/thing.bmp')`, do `loadTexture(require('./images/thing.bmp'))`.
\ No newline at end of file
+## Resources
+- [Generating complex terrain](https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch01.html) from [GPU Gems 3](https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_pref01.html).
+- [Polygonising a scalar field](http://paulbourke.net/geometry/polygonise/) by Paul Bourke.
+- [Marching squares](http://jamie-wong.com/2014/08/19/metaballs-and-marching-squares/) by Jamie Wong.
diff --git a/build/assets/Brown-47ac47.bmp b/build/assets/Brown-47ac47.bmp
new file mode 100644
index 0000000..4a4cd05
Binary files /dev/null and b/build/assets/Brown-47ac47.bmp differ
diff --git a/build/assets/Flame-6aaec9.bmp b/build/assets/Flame-6aaec9.bmp
new file mode 100644
index 0000000..ddd8b3f
Binary files /dev/null and b/build/assets/Flame-6aaec9.bmp differ
diff --git a/build/assets/Gold-18a4ff.bmp b/build/assets/Gold-18a4ff.bmp
new file mode 100644
index 0000000..59ba2e1
Binary files /dev/null and b/build/assets/Gold-18a4ff.bmp differ
diff --git a/build/assets/Green-13adc2.bmp b/build/assets/Green-13adc2.bmp
new file mode 100644
index 0000000..85180d5
Binary files /dev/null and b/build/assets/Green-13adc2.bmp differ
diff --git a/build/assets/LavaLamp_glass-b51713.obj b/build/assets/LavaLamp_glass-b51713.obj
new file mode 100644
index 0000000..8567970
--- /dev/null
+++ b/build/assets/LavaLamp_glass-b51713.obj
@@ -0,0 +1,775 @@
+# This file uses centimeters as units for non-parametric coordinates.
+
+mtllib LavaLamp_glass.mtl
+g default
+v 1.169831 2.104592 -0.380132
+v 0.995117 2.104592 -0.723026
+v 0.722995 2.104592 -0.995149
+v 0.380101 2.104592 -1.169862
+v 0.000000 2.104592 -1.230064
+v -0.380101 2.104592 -1.169862
+v -0.722995 2.104592 -0.995148
+v -0.995117 2.104592 -0.723026
+v -1.169830 2.104592 -0.380132
+v -1.230032 2.104592 -0.000031
+v -1.169830 2.104592 0.380069
+v -0.995117 2.104592 0.722963
+v -0.722995 2.104592 0.995085
+v -0.380101 2.104592 1.169799
+v -0.000000 2.104592 1.230001
+v 0.380101 2.104592 1.169798
+v 0.722995 2.104592 0.995085
+v 0.995117 2.104592 0.722963
+v 1.169830 2.104592 0.380069
+v 1.230032 2.104592 -0.000031
+v 1.167771 7.794762 -0.379463
+v 0.993366 7.794762 -0.721754
+v 0.721722 7.794762 -0.993397
+v 0.379432 7.794762 -1.167803
+v 0.000000 7.794762 -1.227898
+v -0.379432 7.794762 -1.167802
+v -0.721722 7.794762 -0.993397
+v -0.993365 7.794762 -0.721753
+v -1.167771 7.794762 -0.379463
+v -1.227867 7.794762 -0.000031
+v -1.167771 7.794762 0.379400
+v -0.993365 7.794762 0.721691
+v -0.721722 7.794762 0.993334
+v -0.379432 7.794762 1.167739
+v -0.000000 7.794762 1.227835
+v 0.379432 7.794762 1.167739
+v 0.721722 7.794762 0.993334
+v 0.993365 7.794762 0.721690
+v 1.167771 7.794762 0.379400
+v 1.227867 7.794762 -0.000031
+v 1.375549 3.249149 -0.446974
+v 1.446337 3.249149 -0.000031
+v 1.375548 3.249149 0.446911
+v 1.170111 3.249149 0.850104
+v 0.850136 3.249149 1.170080
+v 0.446943 3.249149 1.375517
+v -0.000000 3.249149 1.446306
+v -0.446943 3.249149 1.375517
+v -0.850136 3.249149 1.170080
+v -1.170112 3.249149 0.850104
+v -1.375549 3.249149 0.446911
+v -1.446337 3.249149 -0.000031
+v -1.375549 3.249149 -0.446974
+v -1.170112 3.249149 -0.850167
+v -0.850136 3.249149 -1.170143
+v -0.446943 3.249149 -1.375580
+v 0.000000 3.249149 -1.446369
+v 0.446943 3.249149 -1.375581
+v 0.850136 3.249149 -1.170143
+v 1.170112 3.249149 -0.850167
+v 1.512426 4.756187 -0.491448
+v 1.590258 4.756187 -0.000031
+v 1.512425 4.756187 0.491385
+v 1.286546 4.756187 0.934699
+v 0.934730 4.756187 1.286514
+v 0.491417 4.756187 1.512394
+v 0.000000 4.756187 1.590226
+v -0.491417 4.756187 1.512394
+v -0.934730 4.756187 1.286514
+v -1.286546 4.756187 0.934699
+v -1.512425 4.756187 0.491385
+v -1.590258 4.756187 -0.000031
+v -1.512425 4.756187 -0.491448
+v -1.286546 4.756187 -0.934762
+v -0.934730 4.756187 -1.286577
+v -0.491417 4.756187 -1.512457
+v 0.000000 4.756187 -1.590290
+v 0.491417 4.756187 -1.512457
+v 0.934731 4.756187 -1.286578
+v 1.286546 4.756187 -0.934762
+v 1.322722 6.825183 -0.429810
+v 1.390791 6.825183 -0.000031
+v 1.322721 6.825183 0.429747
+v 1.125174 6.825183 0.817455
+v 0.817487 6.825183 1.125142
+v 0.429778 6.825183 1.322690
+v 0.000000 6.825183 1.390760
+v -0.429778 6.825183 1.322690
+v -0.817487 6.825183 1.125142
+v -1.125174 6.825183 0.817455
+v -1.322721 6.825183 0.429747
+v -1.390791 6.825183 -0.000031
+v -1.322721 6.825183 -0.429810
+v -1.125174 6.825183 -0.817518
+v -0.817487 6.825183 -1.125206
+v -0.429778 6.825183 -1.322753
+v 0.000000 6.825183 -1.390823
+v 0.429778 6.825183 -1.322753
+v 0.817487 6.825183 -1.125206
+v 1.125175 6.825183 -0.817518
+v 1.495073 5.241519 -0.485810
+v 1.572011 5.241519 -0.000031
+v 1.495071 5.241519 0.485747
+v 1.271784 5.241519 0.923974
+v 0.924005 5.241519 1.271752
+v 0.485778 5.241519 1.495040
+v 0.000000 5.241519 1.571980
+v -0.485778 5.241519 1.495040
+v -0.924005 5.241519 1.271753
+v -1.271784 5.241519 0.923974
+v -1.495072 5.241519 0.485747
+v -1.572011 5.241519 -0.000031
+v -1.495072 5.241519 -0.485810
+v -1.271784 5.241519 -0.924036
+v -0.924005 5.241519 -1.271816
+v -0.485778 5.241519 -1.495104
+v 0.000000 5.241519 -1.572043
+v 0.485778 5.241519 -1.495104
+v 0.924006 5.241519 -1.271816
+v 1.271785 5.241519 -0.924037
+v 1.434839 6.051395 0.466176
+v 1.220547 6.051395 0.886748
+v 0.886779 6.051395 1.220516
+v 0.466207 6.051395 1.434807
+v 0.000000 6.051395 1.508647
+v -0.466207 6.051395 1.434808
+v -0.886779 6.051395 1.220516
+v -1.220547 6.051395 0.886748
+v -1.434839 6.051395 0.466176
+v -1.508679 6.051395 -0.000031
+v -1.434839 6.051395 -0.466239
+v -1.220547 6.051395 -0.886811
+v -0.886779 6.051395 -1.220579
+v -0.466208 6.051395 -1.434871
+v 0.000000 6.051395 -1.508711
+v 0.466208 6.051395 -1.434871
+v 0.886780 6.051395 -1.220579
+v 1.220548 6.051395 -0.886811
+v 1.434840 6.051395 -0.466239
+v 1.508679 6.051395 -0.000031
+v 1.498276 3.985892 0.486788
+v 1.274509 3.985892 0.925954
+v 0.925985 3.985892 1.274478
+v 0.486819 3.985892 1.498244
+v 0.000000 3.985892 1.575349
+v -0.486819 3.985892 1.498244
+v -0.925985 3.985892 1.274478
+v -1.274509 3.985892 0.925954
+v -1.498276 3.985892 0.486788
+v -1.575380 3.985892 -0.000031
+v -1.498276 3.985892 -0.486851
+v -1.274510 3.985892 -0.926017
+v -0.925985 3.985892 -1.274541
+v -0.486819 3.985892 -1.498307
+v 0.000000 3.985892 -1.575412
+v 0.486820 3.985892 -1.498308
+v 0.925986 3.985892 -1.274541
+v 1.274510 3.985892 -0.926017
+v 1.498276 3.985892 -0.486851
+v 1.575380 3.985892 -0.000031
+vt 0.375000 0.312500
+vt 0.387500 0.312500
+vt 0.400000 0.312500
+vt 0.412500 0.312500
+vt 0.425000 0.312500
+vt 0.437500 0.312500
+vt 0.450000 0.312500
+vt 0.462500 0.312500
+vt 0.475000 0.312500
+vt 0.487500 0.312500
+vt 0.500000 0.312500
+vt 0.512500 0.312500
+vt 0.525000 0.312500
+vt 0.537500 0.312500
+vt 0.550000 0.312500
+vt 0.562500 0.312500
+vt 0.575000 0.312500
+vt 0.587500 0.312500
+vt 0.600000 0.312500
+vt 0.612500 0.312500
+vt 0.625000 0.312500
+vt 0.375000 0.688440
+vt 0.387500 0.688440
+vt 0.400000 0.688440
+vt 0.412500 0.688440
+vt 0.425000 0.688440
+vt 0.437500 0.688440
+vt 0.450000 0.688440
+vt 0.462500 0.688440
+vt 0.475000 0.688440
+vt 0.487500 0.688440
+vt 0.500000 0.688440
+vt 0.512500 0.688440
+vt 0.525000 0.688440
+vt 0.537500 0.688440
+vt 0.550000 0.688440
+vt 0.562500 0.688440
+vt 0.575000 0.688440
+vt 0.587500 0.688440
+vt 0.600000 0.688440
+vt 0.612500 0.688440
+vt 0.625000 0.688440
+vt 0.625000 0.388119
+vt 0.375000 0.388119
+vt 0.612500 0.388119
+vt 0.600000 0.388119
+vt 0.587500 0.388119
+vt 0.575000 0.388119
+vt 0.562500 0.388119
+vt 0.550000 0.388119
+vt 0.537500 0.388119
+vt 0.525000 0.388119
+vt 0.512500 0.388119
+vt 0.500000 0.388119
+vt 0.487500 0.388119
+vt 0.475000 0.388119
+vt 0.462500 0.388119
+vt 0.450000 0.388119
+vt 0.437500 0.388119
+vt 0.425000 0.388119
+vt 0.412500 0.388119
+vt 0.400000 0.388119
+vt 0.387500 0.388119
+vt 0.625000 0.487686
+vt 0.375000 0.487686
+vt 0.612500 0.487686
+vt 0.600000 0.487686
+vt 0.587500 0.487686
+vt 0.575000 0.487686
+vt 0.562500 0.487686
+vt 0.550000 0.487686
+vt 0.537500 0.487686
+vt 0.525000 0.487686
+vt 0.512500 0.487686
+vt 0.500000 0.487686
+vt 0.487500 0.487686
+vt 0.475000 0.487686
+vt 0.462500 0.487686
+vt 0.450000 0.487686
+vt 0.437500 0.487686
+vt 0.425000 0.487686
+vt 0.412500 0.487686
+vt 0.400000 0.487686
+vt 0.387500 0.487686
+vt 0.625000 0.624381
+vt 0.375000 0.624381
+vt 0.612500 0.624381
+vt 0.600000 0.624381
+vt 0.587500 0.624381
+vt 0.575000 0.624381
+vt 0.562500 0.624381
+vt 0.550000 0.624381
+vt 0.537500 0.624381
+vt 0.525000 0.624381
+vt 0.512500 0.624381
+vt 0.500000 0.624381
+vt 0.487500 0.624381
+vt 0.475000 0.624381
+vt 0.462500 0.624381
+vt 0.450000 0.624381
+vt 0.437500 0.624381
+vt 0.425000 0.624381
+vt 0.412500 0.624381
+vt 0.400000 0.624381
+vt 0.387500 0.624381
+vt 0.625000 0.519751
+vt 0.375000 0.519751
+vt 0.612500 0.519751
+vt 0.600000 0.519751
+vt 0.587500 0.519751
+vt 0.575000 0.519751
+vt 0.562500 0.519751
+vt 0.550000 0.519751
+vt 0.537500 0.519751
+vt 0.525000 0.519751
+vt 0.512500 0.519751
+vt 0.500000 0.519751
+vt 0.487500 0.519751
+vt 0.475000 0.519751
+vt 0.462500 0.519751
+vt 0.450000 0.519751
+vt 0.437500 0.519751
+vt 0.425000 0.519751
+vt 0.412500 0.519751
+vt 0.400000 0.519751
+vt 0.387500 0.519751
+vt 0.600000 0.573259
+vt 0.587500 0.573259
+vt 0.575000 0.573259
+vt 0.562500 0.573259
+vt 0.550000 0.573259
+vt 0.537500 0.573259
+vt 0.525000 0.573259
+vt 0.512500 0.573259
+vt 0.500000 0.573259
+vt 0.487500 0.573259
+vt 0.475000 0.573259
+vt 0.462500 0.573259
+vt 0.450000 0.573259
+vt 0.437500 0.573259
+vt 0.425000 0.573259
+vt 0.412500 0.573259
+vt 0.400000 0.573259
+vt 0.387500 0.573259
+vt 0.625000 0.573259
+vt 0.375000 0.573259
+vt 0.612500 0.573259
+vt 0.600000 0.436794
+vt 0.587500 0.436794
+vt 0.575000 0.436794
+vt 0.562500 0.436794
+vt 0.550000 0.436794
+vt 0.537500 0.436794
+vt 0.525000 0.436794
+vt 0.512500 0.436794
+vt 0.500000 0.436794
+vt 0.487500 0.436794
+vt 0.475000 0.436794
+vt 0.462500 0.436794
+vt 0.450000 0.436794
+vt 0.437500 0.436794
+vt 0.425000 0.436794
+vt 0.412500 0.436794
+vt 0.400000 0.436794
+vt 0.387500 0.436794
+vt 0.625000 0.436794
+vt 0.375000 0.436794
+vt 0.612500 0.436794
+vn 0.935479 -0.185698 -0.300657
+vn 0.796785 -0.185698 -0.575021
+vn 0.936224 -0.180338 -0.301602
+vn 0.797201 -0.180338 -0.576150
+vn 0.580096 -0.185698 -0.793098
+vn 0.580143 -0.180338 -0.794300
+vn 0.306623 -0.185698 -0.933541
+vn 0.306296 -0.180338 -0.934698
+vn 0.003136 -0.185698 -0.982602
+vn 0.002467 -0.180338 -0.983602
+vn -0.300658 -0.185698 -0.935479
+vn -0.301603 -0.180338 -0.936223
+vn -0.575022 -0.185698 -0.796785
+vn -0.576150 -0.180338 -0.797201
+vn -0.793098 -0.185698 -0.580096
+vn -0.794300 -0.180338 -0.580142
+vn -0.933541 -0.185698 -0.306623
+vn -0.934698 -0.180338 -0.306296
+vn -0.982602 -0.185698 -0.003136
+vn -0.983602 -0.180338 -0.002468
+vn -0.935479 -0.185698 0.300658
+vn -0.936223 -0.180338 0.301603
+vn -0.796785 -0.185698 0.575022
+vn -0.797201 -0.180338 0.576150
+vn -0.580096 -0.185698 0.793098
+vn -0.580143 -0.180338 0.794300
+vn -0.306623 -0.185698 0.933541
+vn -0.306296 -0.180338 0.934698
+vn -0.003136 -0.185698 0.982602
+vn -0.002467 -0.180338 0.983602
+vn 0.300658 -0.185698 0.935479
+vn 0.301603 -0.180338 0.936223
+vn 0.575021 -0.185698 0.796785
+vn 0.576150 -0.180338 0.797201
+vn 0.793098 -0.185698 0.580096
+vn 0.794300 -0.180338 0.580143
+vn 0.933541 -0.185698 0.306624
+vn 0.934698 -0.180338 0.306296
+vn 0.982602 -0.185698 0.003137
+vn 0.983602 -0.180338 0.002468
+vn 0.995599 -0.093710 0.001235
+vn 0.947253 -0.093710 -0.306482
+vn 0.946489 -0.093710 0.308831
+vn 0.804731 -0.093710 0.586197
+vn 0.584199 -0.093710 0.806182
+vn 0.306483 -0.093710 0.947252
+vn -0.001235 -0.093710 0.995599
+vn -0.308831 -0.093710 0.946489
+vn -0.586197 -0.093710 0.804731
+vn -0.806182 -0.093710 0.584200
+vn -0.947252 -0.093710 0.306483
+vn -0.995599 -0.093710 -0.001235
+vn -0.946489 -0.093710 -0.308831
+vn -0.804731 -0.093710 -0.586197
+vn -0.584200 -0.093710 -0.806182
+vn -0.306483 -0.093710 -0.947252
+vn 0.001234 -0.093710 -0.995599
+vn 0.308831 -0.093710 -0.946489
+vn 0.586197 -0.093710 -0.804731
+vn 0.806182 -0.093710 -0.584199
+vn 0.999997 0.002642 -0.000011
+vn 0.951050 0.002642 -0.309026
+vn 0.998035 0.062659 -0.000843
+vn 0.948927 0.062659 -0.309211
+vn 0.951057 0.002642 0.309004
+vn 0.949448 0.062659 0.307607
+vn 0.809021 0.002642 0.587773
+vn 0.807923 0.062659 0.585947
+vn 0.587793 0.002642 0.809007
+vn 0.587313 0.062659 0.806931
+vn 0.309027 0.002642 0.951050
+vn 0.309212 0.062659 0.948927
+vn 0.000012 0.002642 0.999997
+vn 0.000845 0.062659 0.998035
+vn -0.309004 0.002642 0.951057
+vn -0.307607 0.062659 0.949448
+vn -0.587773 0.002642 0.809021
+vn -0.585948 0.062659 0.807923
+vn -0.809007 0.002642 0.587793
+vn -0.806931 0.062659 0.587313
+vn -0.951050 0.002642 0.309027
+vn -0.948927 0.062659 0.309212
+vn -0.999997 0.002642 0.000012
+vn -0.998035 0.062659 0.000844
+vn -0.951057 0.002642 -0.309004
+vn -0.949448 0.062659 -0.307607
+vn -0.809021 0.002642 -0.587773
+vn -0.807923 0.062659 -0.585947
+vn -0.587793 0.002642 -0.809007
+vn -0.587313 0.062659 -0.806931
+vn -0.309027 0.002642 -0.951050
+vn -0.309212 0.062659 -0.948927
+vn -0.000012 0.002642 -0.999997
+vn -0.000844 0.062659 -0.998035
+vn 0.309004 0.002642 -0.951057
+vn 0.307607 0.062659 -0.949448
+vn 0.587773 0.002642 -0.809021
+vn 0.585947 0.062659 -0.807923
+vn 0.809007 0.002642 -0.587793
+vn 0.806931 0.062659 -0.587313
+vn 0.987308 0.158800 -0.002231
+vn 0.938296 0.158800 -0.307217
+vn 0.986170 0.165713 -0.002683
+vn 0.937075 0.165713 -0.307295
+vn 0.939676 0.158800 0.302972
+vn 0.938733 0.165713 0.302190
+vn 0.800061 0.158800 0.578519
+vn 0.799406 0.165713 0.577485
+vn 0.582131 0.158800 0.797437
+vn 0.581828 0.165713 0.796251
+vn 0.307219 0.158800 0.938296
+vn 0.307296 0.165713 0.937074
+vn 0.002233 0.158800 0.987308
+vn 0.002684 0.165713 0.986170
+vn -0.302972 0.158800 0.939676
+vn -0.302191 0.165713 0.938733
+vn -0.578519 0.158800 0.800061
+vn -0.577485 0.165713 0.799406
+vn -0.797437 0.158800 0.582131
+vn -0.796251 0.165713 0.581828
+vn -0.938296 0.158800 0.307218
+vn -0.937074 0.165713 0.307296
+vn -0.987308 0.158800 0.002232
+vn -0.986170 0.165713 0.002684
+vn -0.939676 0.158800 -0.302972
+vn -0.938733 0.165713 -0.302191
+vn -0.800062 0.158800 -0.578519
+vn -0.799407 0.165713 -0.577485
+vn -0.582132 0.158800 -0.797437
+vn -0.581828 0.165713 -0.796251
+vn -0.307218 0.158800 -0.938296
+vn -0.307297 0.165713 -0.937074
+vn -0.002233 0.158800 -0.987308
+vn -0.002685 0.165713 -0.986170
+vn 0.302971 0.158800 -0.939676
+vn 0.302190 0.165713 -0.938733
+vn 0.578519 0.158800 -0.800062
+vn 0.577485 0.165713 -0.799406
+vn 0.797437 0.158800 -0.582131
+vn 0.796251 0.165713 -0.581828
+vn 0.993604 0.112911 -0.001556
+vn 0.944493 0.112911 -0.308521
+vn 0.945455 0.112911 0.305559
+vn 0.804758 0.112911 0.582766
+vn 0.585286 0.112911 0.802927
+vn 0.308521 0.112911 0.944492
+vn 0.001558 0.112911 0.993604
+vn -0.305559 0.112911 0.945455
+vn -0.582766 0.112911 0.804758
+vn -0.802927 0.112911 0.585286
+vn -0.944492 0.112911 0.308522
+vn -0.993604 0.112911 0.001557
+vn -0.945455 0.112911 -0.305559
+vn -0.804758 0.112911 -0.582766
+vn -0.585286 0.112911 -0.802927
+vn -0.308522 0.112911 -0.944492
+vn -0.001557 0.112911 -0.993604
+vn 0.305559 0.112911 -0.945455
+vn 0.582766 0.112911 -0.804758
+vn 0.802927 0.112911 -0.585286
+s 1
+g pCylinder4
+usemtl initialShadingGroup
+f 1/1/1 2/2/2 41/44/3
+f 41/44/3 2/2/2 60/63/4
+f 2/2/2 3/3/5 60/63/4
+f 60/63/4 3/3/5 59/62/6
+f 3/3/5 4/4/7 59/62/6
+f 59/62/6 4/4/7 58/61/8
+f 4/4/7 5/5/9 58/61/8
+f 58/61/8 5/5/9 57/60/10
+f 5/5/9 6/6/11 57/60/10
+f 57/60/10 6/6/11 56/59/12
+f 6/6/11 7/7/13 56/59/12
+f 56/59/12 7/7/13 55/58/14
+f 7/7/13 8/8/15 55/58/14
+f 55/58/14 8/8/15 54/57/16
+f 8/8/15 9/9/17 54/57/16
+f 54/57/16 9/9/17 53/56/18
+f 9/9/17 10/10/19 53/56/18
+f 53/56/18 10/10/19 52/55/20
+f 10/10/19 11/11/21 52/55/20
+f 52/55/20 11/11/21 51/54/22
+f 11/11/21 12/12/23 51/54/22
+f 51/54/22 12/12/23 50/53/24
+f 12/12/23 13/13/25 50/53/24
+f 50/53/24 13/13/25 49/52/26
+f 13/13/25 14/14/27 49/52/26
+f 49/52/26 14/14/27 48/51/28
+f 14/14/27 15/15/29 48/51/28
+f 48/51/28 15/15/29 47/50/30
+f 15/15/29 16/16/31 47/50/30
+f 47/50/30 16/16/31 46/49/32
+f 16/16/31 17/17/33 46/49/32
+f 46/49/32 17/17/33 45/48/34
+f 17/17/33 18/18/35 45/48/34
+f 45/48/34 18/18/35 44/47/36
+f 18/18/35 19/19/37 44/47/36
+f 44/47/36 19/19/37 43/46/38
+f 19/19/37 20/20/39 43/46/38
+f 43/46/38 20/20/39 42/45/40
+f 20/20/39 1/21/1 42/45/40
+f 42/45/40 1/21/1 41/43/3
+f 42/45/40 41/43/3 160/168/41
+f 160/168/41 41/43/3 159/166/42
+f 43/46/38 42/45/40 141/148/43
+f 141/148/43 42/45/40 160/168/41
+f 44/47/36 43/46/38 142/149/44
+f 142/149/44 43/46/38 141/148/43
+f 45/48/34 44/47/36 143/150/45
+f 143/150/45 44/47/36 142/149/44
+f 46/49/32 45/48/34 144/151/46
+f 144/151/46 45/48/34 143/150/45
+f 47/50/30 46/49/32 145/152/47
+f 145/152/47 46/49/32 144/151/46
+f 48/51/28 47/50/30 146/153/48
+f 146/153/48 47/50/30 145/152/47
+f 49/52/26 48/51/28 147/154/49
+f 147/154/49 48/51/28 146/153/48
+f 50/53/24 49/52/26 148/155/50
+f 148/155/50 49/52/26 147/154/49
+f 51/54/22 50/53/24 149/156/51
+f 149/156/51 50/53/24 148/155/50
+f 52/55/20 51/54/22 150/157/52
+f 150/157/52 51/54/22 149/156/51
+f 53/56/18 52/55/20 151/158/53
+f 151/158/53 52/55/20 150/157/52
+f 54/57/16 53/56/18 152/159/54
+f 152/159/54 53/56/18 151/158/53
+f 55/58/14 54/57/16 153/160/55
+f 153/160/55 54/57/16 152/159/54
+f 56/59/12 55/58/14 154/161/56
+f 154/161/56 55/58/14 153/160/55
+f 57/60/10 56/59/12 155/162/57
+f 155/162/57 56/59/12 154/161/56
+f 58/61/8 57/60/10 156/163/58
+f 156/163/58 57/60/10 155/162/57
+f 59/62/6 58/61/8 157/164/59
+f 157/164/59 58/61/8 156/163/58
+f 60/63/4 59/62/6 158/165/60
+f 158/165/60 59/62/6 157/164/59
+f 41/44/3 60/63/4 159/167/42
+f 159/167/42 60/63/4 158/165/60
+f 62/66/61 61/64/62 102/108/63
+f 102/108/63 61/64/62 101/106/64
+f 63/67/65 62/66/61 103/109/66
+f 103/109/66 62/66/61 102/108/63
+f 64/68/67 63/67/65 104/110/68
+f 104/110/68 63/67/65 103/109/66
+f 65/69/69 64/68/67 105/111/70
+f 105/111/70 64/68/67 104/110/68
+f 66/70/71 65/69/69 106/112/72
+f 106/112/72 65/69/69 105/111/70
+f 67/71/73 66/70/71 107/113/74
+f 107/113/74 66/70/71 106/112/72
+f 68/72/75 67/71/73 108/114/76
+f 108/114/76 67/71/73 107/113/74
+f 69/73/77 68/72/75 109/115/78
+f 109/115/78 68/72/75 108/114/76
+f 70/74/79 69/73/77 110/116/80
+f 110/116/80 69/73/77 109/115/78
+f 71/75/81 70/74/79 111/117/82
+f 111/117/82 70/74/79 110/116/80
+f 72/76/83 71/75/81 112/118/84
+f 112/118/84 71/75/81 111/117/82
+f 73/77/85 72/76/83 113/119/86
+f 113/119/86 72/76/83 112/118/84
+f 74/78/87 73/77/85 114/120/88
+f 114/120/88 73/77/85 113/119/86
+f 75/79/89 74/78/87 115/121/90
+f 115/121/90 74/78/87 114/120/88
+f 76/80/91 75/79/89 116/122/92
+f 116/122/92 75/79/89 115/121/90
+f 77/81/93 76/80/91 117/123/94
+f 117/123/94 76/80/91 116/122/92
+f 78/82/95 77/81/93 118/124/96
+f 118/124/96 77/81/93 117/123/94
+f 79/83/97 78/82/95 119/125/98
+f 119/125/98 78/82/95 118/124/96
+f 80/84/99 79/83/97 120/126/100
+f 120/126/100 79/83/97 119/125/98
+f 61/65/62 80/84/99 101/107/64
+f 101/107/64 80/84/99 120/126/100
+f 82/87/101 81/85/102 40/41/103
+f 40/41/103 81/85/102 21/42/104
+f 83/88/105 82/87/101 39/40/106
+f 39/40/106 82/87/101 40/41/103
+f 84/89/107 83/88/105 38/39/108
+f 38/39/108 83/88/105 39/40/106
+f 85/90/109 84/89/107 37/38/110
+f 37/38/110 84/89/107 38/39/108
+f 86/91/111 85/90/109 36/37/112
+f 36/37/112 85/90/109 37/38/110
+f 87/92/113 86/91/111 35/36/114
+f 35/36/114 86/91/111 36/37/112
+f 88/93/115 87/92/113 34/35/116
+f 34/35/116 87/92/113 35/36/114
+f 89/94/117 88/93/115 33/34/118
+f 33/34/118 88/93/115 34/35/116
+f 90/95/119 89/94/117 32/33/120
+f 32/33/120 89/94/117 33/34/118
+f 91/96/121 90/95/119 31/32/122
+f 31/32/122 90/95/119 32/33/120
+f 92/97/123 91/96/121 30/31/124
+f 30/31/124 91/96/121 31/32/122
+f 93/98/125 92/97/123 29/30/126
+f 29/30/126 92/97/123 30/31/124
+f 94/99/127 93/98/125 28/29/128
+f 28/29/128 93/98/125 29/30/126
+f 95/100/129 94/99/127 27/28/130
+f 27/28/130 94/99/127 28/29/128
+f 96/101/131 95/100/129 26/27/132
+f 26/27/132 95/100/129 27/28/130
+f 97/102/133 96/101/131 25/26/134
+f 25/26/134 96/101/131 26/27/132
+f 98/103/135 97/102/133 24/25/136
+f 24/25/136 97/102/133 25/26/134
+f 99/104/137 98/103/135 23/24/138
+f 23/24/138 98/103/135 24/25/136
+f 100/105/139 99/104/137 22/23/140
+f 22/23/140 99/104/137 23/24/138
+f 81/86/102 100/105/139 21/22/104
+f 21/22/104 100/105/139 22/23/140
+f 102/108/63 101/106/64 140/147/141
+f 140/147/141 101/106/64 139/145/142
+f 103/109/66 102/108/63 121/127/143
+f 121/127/143 102/108/63 140/147/141
+f 104/110/68 103/109/66 122/128/144
+f 122/128/144 103/109/66 121/127/143
+f 105/111/70 104/110/68 123/129/145
+f 123/129/145 104/110/68 122/128/144
+f 106/112/72 105/111/70 124/130/146
+f 124/130/146 105/111/70 123/129/145
+f 107/113/74 106/112/72 125/131/147
+f 125/131/147 106/112/72 124/130/146
+f 108/114/76 107/113/74 126/132/148
+f 126/132/148 107/113/74 125/131/147
+f 109/115/78 108/114/76 127/133/149
+f 127/133/149 108/114/76 126/132/148
+f 110/116/80 109/115/78 128/134/150
+f 128/134/150 109/115/78 127/133/149
+f 111/117/82 110/116/80 129/135/151
+f 129/135/151 110/116/80 128/134/150
+f 112/118/84 111/117/82 130/136/152
+f 130/136/152 111/117/82 129/135/151
+f 113/119/86 112/118/84 131/137/153
+f 131/137/153 112/118/84 130/136/152
+f 114/120/88 113/119/86 132/138/154
+f 132/138/154 113/119/86 131/137/153
+f 115/121/90 114/120/88 133/139/155
+f 133/139/155 114/120/88 132/138/154
+f 116/122/92 115/121/90 134/140/156
+f 134/140/156 115/121/90 133/139/155
+f 117/123/94 116/122/92 135/141/157
+f 135/141/157 116/122/92 134/140/156
+f 118/124/96 117/123/94 136/142/158
+f 136/142/158 117/123/94 135/141/157
+f 119/125/98 118/124/96 137/143/159
+f 137/143/159 118/124/96 136/142/158
+f 120/126/100 119/125/98 138/144/160
+f 138/144/160 119/125/98 137/143/159
+f 101/107/64 120/126/100 139/146/142
+f 139/146/142 120/126/100 138/144/160
+f 122/128/144 121/127/143 84/89/107
+f 84/89/107 121/127/143 83/88/105
+f 123/129/145 122/128/144 85/90/109
+f 85/90/109 122/128/144 84/89/107
+f 124/130/146 123/129/145 86/91/111
+f 86/91/111 123/129/145 85/90/109
+f 125/131/147 124/130/146 87/92/113
+f 87/92/113 124/130/146 86/91/111
+f 126/132/148 125/131/147 88/93/115
+f 88/93/115 125/131/147 87/92/113
+f 127/133/149 126/132/148 89/94/117
+f 89/94/117 126/132/148 88/93/115
+f 128/134/150 127/133/149 90/95/119
+f 90/95/119 127/133/149 89/94/117
+f 129/135/151 128/134/150 91/96/121
+f 91/96/121 128/134/150 90/95/119
+f 130/136/152 129/135/151 92/97/123
+f 92/97/123 129/135/151 91/96/121
+f 131/137/153 130/136/152 93/98/125
+f 93/98/125 130/136/152 92/97/123
+f 132/138/154 131/137/153 94/99/127
+f 94/99/127 131/137/153 93/98/125
+f 133/139/155 132/138/154 95/100/129
+f 95/100/129 132/138/154 94/99/127
+f 134/140/156 133/139/155 96/101/131
+f 96/101/131 133/139/155 95/100/129
+f 135/141/157 134/140/156 97/102/133
+f 97/102/133 134/140/156 96/101/131
+f 136/142/158 135/141/157 98/103/135
+f 98/103/135 135/141/157 97/102/133
+f 137/143/159 136/142/158 99/104/137
+f 99/104/137 136/142/158 98/103/135
+f 138/144/160 137/143/159 100/105/139
+f 100/105/139 137/143/159 99/104/137
+f 139/146/142 138/144/160 81/86/102
+f 81/86/102 138/144/160 100/105/139
+f 140/147/141 139/145/142 82/87/101
+f 82/87/101 139/145/142 81/85/102
+f 121/127/143 140/147/141 83/88/105
+f 83/88/105 140/147/141 82/87/101
+f 142/149/44 141/148/43 64/68/67
+f 64/68/67 141/148/43 63/67/65
+f 143/150/45 142/149/44 65/69/69
+f 65/69/69 142/149/44 64/68/67
+f 144/151/46 143/150/45 66/70/71
+f 66/70/71 143/150/45 65/69/69
+f 145/152/47 144/151/46 67/71/73
+f 67/71/73 144/151/46 66/70/71
+f 146/153/48 145/152/47 68/72/75
+f 68/72/75 145/152/47 67/71/73
+f 147/154/49 146/153/48 69/73/77
+f 69/73/77 146/153/48 68/72/75
+f 148/155/50 147/154/49 70/74/79
+f 70/74/79 147/154/49 69/73/77
+f 149/156/51 148/155/50 71/75/81
+f 71/75/81 148/155/50 70/74/79
+f 150/157/52 149/156/51 72/76/83
+f 72/76/83 149/156/51 71/75/81
+f 151/158/53 150/157/52 73/77/85
+f 73/77/85 150/157/52 72/76/83
+f 152/159/54 151/158/53 74/78/87
+f 74/78/87 151/158/53 73/77/85
+f 153/160/55 152/159/54 75/79/89
+f 75/79/89 152/159/54 74/78/87
+f 154/161/56 153/160/55 76/80/91
+f 76/80/91 153/160/55 75/79/89
+f 155/162/57 154/161/56 77/81/93
+f 77/81/93 154/161/56 76/80/91
+f 156/163/58 155/162/57 78/82/95
+f 78/82/95 155/162/57 77/81/93
+f 157/164/59 156/163/58 79/83/97
+f 79/83/97 156/163/58 78/82/95
+f 158/165/60 157/164/59 80/84/99
+f 80/84/99 157/164/59 79/83/97
+f 159/167/42 158/165/60 61/65/62
+f 61/65/62 158/165/60 80/84/99
+f 160/168/41 159/166/42 62/66/61
+f 62/66/61 159/166/42 61/64/62
+f 141/148/43 160/168/41 63/67/65
+f 63/67/65 160/168/41 62/66/61
diff --git a/build/assets/LavaLamp_metal-8b63ab.obj b/build/assets/LavaLamp_metal-8b63ab.obj
new file mode 100644
index 0000000..32409f6
--- /dev/null
+++ b/build/assets/LavaLamp_metal-8b63ab.obj
@@ -0,0 +1,672 @@
+# This file uses centimeters as units for non-parametric coordinates.
+
+mtllib LavaLamp_metal.mtl
+g default
+v 0.532618 9.734337 -0.173058
+v 0.453073 9.734337 -0.329177
+v 0.329176 9.734337 -0.453073
+v 0.173058 9.734337 -0.532619
+v -0.000000 9.734337 -0.560028
+v -0.173058 9.734337 -0.532619
+v -0.329176 9.734337 -0.453073
+v -0.453073 9.734337 -0.329177
+v -0.532619 9.734337 -0.173058
+v -0.560028 9.734337 -0.000000
+v -0.532619 9.734337 0.173058
+v -0.453073 9.734337 0.329176
+v -0.329176 9.734337 0.453072
+v -0.173058 9.734337 0.532618
+v -0.000000 9.734337 0.560029
+v 0.173058 9.734337 0.532618
+v 0.329176 9.734337 0.453072
+v 0.453072 9.734337 0.329176
+v 0.532618 9.734337 0.173058
+v 0.560028 9.734337 -0.000000
+v 1.185701 7.770060 -0.385258
+v 1.008617 7.770060 -0.732803
+v 0.000000 8.200895 0.000000
+v 0.732804 7.770060 -1.008617
+v 0.385258 7.770060 -1.185701
+v 0.000000 7.765583 -1.246721
+v -0.385258 7.770060 -1.185701
+v -0.732803 7.770060 -1.008617
+v -1.008617 7.770060 -0.732803
+v -1.185700 7.770060 -0.385258
+v -1.246719 7.770060 0.000000
+v -1.185700 7.770060 0.385258
+v -1.008617 7.770060 0.732803
+v -0.732803 7.770060 1.008617
+v -0.385257 7.770060 1.185700
+v -0.000000 7.770060 1.246720
+v 0.385257 7.770060 1.185701
+v 0.732803 7.770060 1.008617
+v 1.008616 7.770060 0.732803
+v 1.185700 7.770060 0.385258
+v 1.246719 7.770060 0.000000
+v -0.000000 9.627193 -0.603509
+v -0.000000 9.734337 -0.000000
+v 1.134989 -0.499812 -0.368780
+v 0.965479 -0.499812 -0.701462
+v 0.701462 -0.499812 -0.965479
+v 0.368780 -0.499812 -1.134989
+v 0.000000 -0.499812 -1.193397
+v -0.368780 -0.499812 -1.134989
+v -0.701462 -0.499812 -0.965479
+v -0.965479 -0.499812 -0.701462
+v -1.134988 -0.499812 -0.368780
+v -1.193397 -0.499812 -0.000000
+v -1.134988 -0.499812 0.368780
+v -0.965479 -0.499812 0.701462
+v -0.701462 -0.499812 0.965479
+v -0.368780 -0.499812 1.134988
+v -0.000000 -0.499812 1.193397
+v 0.368780 -0.499812 1.134988
+v 0.701462 -0.499812 0.965479
+v 0.965479 -0.499812 0.701462
+v 1.134988 -0.499812 0.368780
+v 1.193397 -0.499812 -0.000000
+v 0.532619 1.008568 -0.173058
+v 0.453073 1.008568 -0.329177
+v 0.329176 1.008568 -0.453073
+v 0.173058 1.008568 -0.532619
+v -0.000000 1.008568 -0.560029
+v -0.173058 1.008568 -0.532619
+v -0.329176 1.008568 -0.453073
+v -0.453073 1.008568 -0.329176
+v -0.532619 1.008568 -0.173058
+v -0.560029 1.008568 -0.000000
+v -0.532619 1.008568 0.173058
+v -0.453073 1.008568 0.329176
+v -0.329176 1.008568 0.453072
+v -0.173058 1.008568 0.532618
+v -0.000000 1.008568 0.560028
+v 0.173058 1.008568 0.532618
+v 0.329176 1.008568 0.453072
+v 0.453072 1.008568 0.329176
+v 0.532619 1.008568 0.173058
+v 0.560028 1.008568 -0.000000
+v 0.000000 -0.102997 0.000000
+v 1.185701 2.116568 -0.385258
+v 1.008618 2.116568 -0.732803
+v 0.000000 1.596682 0.000000
+v 0.732804 2.116568 -1.008617
+v 0.385258 2.116568 -1.185701
+v 0.000000 2.116568 -1.246720
+v -0.385258 2.116568 -1.185701
+v -0.732803 2.116568 -1.008617
+v -1.008617 2.116568 -0.732803
+v -1.185701 2.116568 -0.385258
+v -1.246719 2.116568 0.000000
+v -1.185701 2.116568 0.385258
+v -1.008617 2.116568 0.732803
+v -0.732803 2.116568 1.008617
+v -0.385257 2.116568 1.185701
+v -0.000000 2.116568 1.246720
+v 0.385257 2.116568 1.185700
+v 0.732803 2.116568 1.008617
+v 1.008617 2.116568 0.732803
+v 1.185700 2.116568 0.385258
+v 1.246719 2.116568 0.000000
+vt 0.648603 0.795466
+vt 0.626409 0.751908
+vt 0.591842 0.717341
+vt 0.548284 0.695147
+vt 0.500000 0.687500
+vt 0.451716 0.695147
+vt 0.408159 0.717341
+vt 0.373591 0.751909
+vt 0.351397 0.795466
+vt 0.343750 0.843750
+vt 0.351397 0.892034
+vt 0.373591 0.935591
+vt 0.408159 0.970159
+vt 0.451716 0.992353
+vt 0.500000 1.000000
+vt 0.548284 0.992353
+vt 0.591841 0.970159
+vt 0.626409 0.935591
+vt 0.648603 0.892034
+vt 0.656250 0.843750
+vt 0.500000 0.837500
+vt 0.648603 0.892034
+vt 0.626409 0.935591
+vt 0.591841 0.970159
+vt 0.548284 0.992353
+vt 0.500000 1.000000
+vt 0.451716 0.992353
+vt 0.408159 0.970159
+vt 0.373591 0.935591
+vt 0.351397 0.892034
+vt 0.343750 0.843750
+vt 0.351397 0.795466
+vt 0.373591 0.751909
+vt 0.408159 0.717341
+vt 0.451716 0.695147
+vt 0.500000 0.687500
+vt 0.548284 0.695147
+vt 0.591842 0.717341
+vt 0.626409 0.751908
+vt 0.648603 0.795466
+vt 0.656250 0.843750
+vt 0.500000 1.000000
+vt 0.500000 0.843750
+vt 0.375000 0.312500
+vt 0.387500 0.312500
+vt 0.387500 0.688440
+vt 0.375000 0.688440
+vt 0.400000 0.312500
+vt 0.400000 0.688440
+vt 0.412500 0.312500
+vt 0.412500 0.688440
+vt 0.425000 0.312500
+vt 0.425000 0.688440
+vt 0.437500 0.312500
+vt 0.437500 0.688440
+vt 0.450000 0.312500
+vt 0.450000 0.688440
+vt 0.462500 0.312500
+vt 0.462500 0.688440
+vt 0.475000 0.312500
+vt 0.475000 0.688440
+vt 0.487500 0.312500
+vt 0.487500 0.688440
+vt 0.500000 0.312500
+vt 0.500000 0.688440
+vt 0.512500 0.312500
+vt 0.512500 0.688440
+vt 0.525000 0.312500
+vt 0.525000 0.688440
+vt 0.537500 0.312500
+vt 0.537500 0.688440
+vt 0.550000 0.312500
+vt 0.550000 0.688440
+vt 0.562500 0.312500
+vt 0.562500 0.688440
+vt 0.575000 0.312500
+vt 0.575000 0.688440
+vt 0.587500 0.312500
+vt 0.587500 0.688440
+vt 0.600000 0.312500
+vt 0.600000 0.688440
+vt 0.612500 0.312500
+vt 0.612500 0.688440
+vt 0.625000 0.312500
+vt 0.625000 0.688440
+vt 0.626409 0.064408
+vt 0.648603 0.107966
+vt 0.500000 0.150000
+vt 0.591842 0.029841
+vt 0.548284 0.007647
+vt 0.500000 -0.000000
+vt 0.451716 0.007647
+vt 0.408159 0.029841
+vt 0.373591 0.064409
+vt 0.351397 0.107966
+vt 0.343750 0.156250
+vt 0.351397 0.204534
+vt 0.373591 0.248091
+vt 0.408159 0.282659
+vt 0.451716 0.304853
+vt 0.500000 0.312500
+vt 0.548284 0.304853
+vt 0.591841 0.282659
+vt 0.626409 0.248091
+vt 0.648603 0.204534
+vt 0.656250 0.156250
+vt 0.648603 0.892034
+vt 0.626409 0.935591
+vt 0.500000 0.837500
+vt 0.591841 0.970159
+vt 0.548284 0.992353
+vt 0.500000 1.000000
+vt 0.451716 0.992353
+vt 0.408159 0.970159
+vt 0.373591 0.935591
+vt 0.351397 0.892034
+vt 0.343750 0.843750
+vt 0.351397 0.795466
+vt 0.373591 0.751909
+vt 0.408159 0.717341
+vt 0.451716 0.695147
+vt 0.500000 0.687500
+vt 0.548284 0.695147
+vt 0.591842 0.717341
+vt 0.626409 0.751908
+vt 0.648603 0.795466
+vt 0.656250 0.843750
+vt 0.648603 0.892034
+vt 0.626409 0.935591
+vt 0.591841 0.970159
+vt 0.548284 0.992353
+vt 0.500000 1.000000
+vt 0.451716 0.992353
+vt 0.408159 0.970159
+vt 0.373591 0.935591
+vt 0.351397 0.892034
+vt 0.343750 0.843750
+vt 0.351397 0.795466
+vt 0.373591 0.751909
+vt 0.408159 0.717341
+vt 0.451716 0.695147
+vt 0.500000 0.687500
+vt 0.548284 0.695147
+vt 0.591842 0.717341
+vt 0.626409 0.751908
+vt 0.648603 0.795466
+vt 0.656250 0.843750
+vn -0.310636 -0.945155 0.100931
+vn 0.000000 -1.000000 0.000342
+vn -0.264243 -0.945155 0.191982
+vn -0.191984 -0.945155 0.264242
+vn -0.095635 -0.945140 0.312352
+vn -0.000000 -0.944104 0.329649
+vn 0.095635 -0.945140 0.312352
+vn 0.191984 -0.945155 0.264244
+vn 0.264243 -0.945155 0.191983
+vn 0.310636 -0.945155 0.100931
+vn 0.326622 -0.945155 0.000000
+vn 0.310636 -0.945155 -0.100931
+vn 0.264243 -0.945155 -0.191984
+vn 0.191984 -0.945155 -0.264244
+vn 0.100932 -0.945155 -0.310636
+vn 0.000000 -0.945155 -0.326622
+vn -0.100932 -0.945155 -0.310636
+vn -0.191984 -0.945155 -0.264242
+vn -0.264243 -0.945155 -0.191982
+vn -0.310636 -0.945155 -0.100931
+vn -0.326622 -0.945155 -0.000000
+vn 0.900780 0.329989 -0.282315
+vn 0.898485 0.330005 -0.289518
+vn 0.769453 0.329990 -0.546854
+vn 0.765045 0.330004 -0.552996
+vn 0.562805 0.329989 -0.757864
+vn 0.556716 0.330005 -0.762341
+vn 0.325869 0.328458 -0.886524
+vn 0.289370 0.330005 -0.898533
+vn 0.002104 0.328084 -0.944646
+vn 0.000000 0.376037 -0.926605
+vn -0.012835 0.330539 -0.943705
+vn -0.294017 0.328555 -0.897555
+vn -0.326241 0.330306 -0.885700
+vn -0.546854 0.329990 -0.769453
+vn -0.552997 0.330004 -0.765044
+vn -0.757864 0.329989 -0.562805
+vn -0.762342 0.330004 -0.556715
+vn -0.894688 0.329989 -0.301066
+vn -0.897065 0.330004 -0.293891
+vn -0.943933 0.329989 -0.009860
+vn -0.943977 0.330005 -0.002298
+vn -0.900781 0.329989 0.282315
+vn -0.898486 0.330004 0.289518
+vn -0.769453 0.329989 0.546854
+vn -0.765045 0.330005 0.552995
+vn -0.562806 0.329989 0.757863
+vn -0.556715 0.330004 0.762342
+vn -0.301070 0.329989 0.894687
+vn -0.293893 0.330004 0.897064
+vn -0.009859 0.329990 0.943933
+vn -0.002300 0.330005 0.943976
+vn 0.282317 0.329989 0.900780
+vn 0.289520 0.330005 0.898485
+vn 0.546854 0.329990 0.769453
+vn 0.552996 0.330004 0.765044
+vn 0.757863 0.329989 0.562806
+vn 0.762342 0.330005 0.556715
+vn 0.894687 0.329989 0.301068
+vn 0.897065 0.330005 0.293891
+vn 0.943933 0.329989 0.009859
+vn 0.943977 0.330005 0.002299
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 -0.000000
+vn 0.000000 1.000000 0.000003
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000005
+vn 0.000000 1.000000 -0.000005
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 -0.000000
+vn 0.000000 1.000000 -0.000005
+vn 0.000000 1.000000 -0.000002
+vn 0.000000 1.000000 0.000002
+vn 0.000000 1.000000 0.000002
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000001
+vn 0.000000 1.000000 0.000001
+vn 0.876030 0.387153 -0.287548
+vn 0.744297 0.387153 -0.544182
+vn 0.873547 0.387132 -0.295033
+vn 0.739622 0.387133 -0.550534
+vn 0.539705 0.387153 -0.747550
+vn 0.533298 0.387132 -0.752145
+vn 0.282285 0.387153 -0.877740
+vn 0.274771 0.387132 -0.880130
+vn -0.002767 0.387152 -0.922012
+vn -0.010652 0.387132 -0.921963
+vn -0.287547 0.387153 -0.876030
+vn -0.295034 0.387132 -0.873547
+vn -0.544182 0.387153 -0.744297
+vn -0.550534 0.387133 -0.739622
+vn -0.747550 0.387153 -0.539705
+vn -0.752145 0.387133 -0.533298
+vn -0.877740 0.387153 -0.282285
+vn -0.880131 0.387132 -0.274771
+vn -0.922012 0.387152 0.002767
+vn -0.921963 0.387132 0.010652
+vn -0.876031 0.387153 0.287548
+vn -0.873547 0.387132 0.295033
+vn -0.744297 0.387153 0.544182
+vn -0.739622 0.387133 0.550534
+vn -0.539705 0.387153 0.747550
+vn -0.533297 0.387132 0.752145
+vn -0.282285 0.387153 0.877740
+vn -0.274771 0.387132 0.880131
+vn 0.002767 0.387152 0.922012
+vn 0.010653 0.387132 0.921963
+vn 0.287548 0.387152 0.876030
+vn 0.295034 0.387132 0.873547
+vn 0.544182 0.387153 0.744297
+vn 0.550534 0.387133 0.739622
+vn 0.747550 0.387153 0.539705
+vn 0.752145 0.387132 0.533298
+vn 0.877740 0.387153 0.282285
+vn 0.880131 0.387132 0.274771
+vn 0.922012 0.387153 -0.002766
+vn 0.921963 0.387132 -0.010652
+vn -0.255264 -0.948918 0.185460
+vn -0.300080 -0.948918 0.097502
+vn -0.000000 -1.000000 -0.000000
+vn -0.185460 -0.948918 0.255264
+vn -0.097502 -0.948918 0.300080
+vn 0.000000 -0.948918 0.315523
+vn 0.097502 -0.948918 0.300081
+vn 0.185459 -0.948918 0.255264
+vn 0.255264 -0.948918 0.185459
+vn 0.300081 -0.948918 0.097502
+vn 0.315523 -0.948918 0.000000
+vn 0.300081 -0.948918 -0.097502
+vn 0.255264 -0.948918 -0.185460
+vn 0.185459 -0.948918 -0.255264
+vn 0.097502 -0.948918 -0.300081
+vn 0.000000 -0.948918 -0.315523
+vn -0.097502 -0.948918 -0.300081
+vn -0.185460 -0.948918 -0.255264
+vn -0.255264 -0.948918 -0.185460
+vn -0.300081 -0.948918 -0.097502
+vn -0.315523 -0.948918 -0.000000
+vn -0.366042 0.922967 0.118934
+vn -0.311374 0.922967 0.226227
+vn 0.000000 1.000000 0.000000
+vn -0.226227 0.922967 0.311374
+vn -0.118935 0.922967 0.366042
+vn -0.000000 0.922967 0.384879
+vn 0.118935 0.922967 0.366042
+vn 0.226227 0.922967 0.311374
+vn 0.311374 0.922967 0.226226
+vn 0.366042 0.922967 0.118934
+vn 0.384880 0.922967 0.000000
+vn 0.366042 0.922967 -0.118934
+vn 0.311374 0.922967 -0.226226
+vn 0.226227 0.922967 -0.311374
+vn 0.118935 0.922967 -0.366042
+vn -0.000000 0.922967 -0.384880
+vn -0.118935 0.922967 -0.366042
+vn -0.226227 0.922967 -0.311375
+vn -0.311374 0.922967 -0.226227
+vn -0.366043 0.922967 -0.118934
+vn -0.384880 0.922967 0.000000
+vn 0.812445 -0.526744 -0.249949
+vn 0.695442 -0.526744 -0.488775
+vn 0.809428 -0.526788 -0.259462
+vn 0.689634 -0.526788 -0.496891
+vn 0.510365 -0.526744 -0.679756
+vn 0.502333 -0.526788 -0.685679
+vn 0.275330 -0.526744 -0.804198
+vn 0.265861 -0.526788 -0.807349
+vn 0.013344 -0.526744 -0.849919
+vn 0.003364 -0.526788 -0.849990
+vn -0.249950 -0.526744 -0.812445
+vn -0.259463 -0.526788 -0.809428
+vn -0.488775 -0.526743 -0.695442
+vn -0.496891 -0.526788 -0.689633
+vn -0.679756 -0.526744 -0.510365
+vn -0.685680 -0.526787 -0.502333
+vn -0.804198 -0.526744 -0.275330
+vn -0.807350 -0.526787 -0.265860
+vn -0.849919 -0.526744 -0.013344
+vn -0.849990 -0.526787 -0.003363
+vn -0.812445 -0.526744 0.249949
+vn -0.809428 -0.526788 0.259463
+vn -0.695442 -0.526744 0.488775
+vn -0.689633 -0.526788 0.496891
+vn -0.510366 -0.526744 0.679756
+vn -0.502333 -0.526788 0.685680
+vn -0.275331 -0.526744 0.804198
+vn -0.265860 -0.526788 0.807349
+vn -0.013344 -0.526744 0.849919
+vn -0.003363 -0.526788 0.849990
+vn 0.249950 -0.526744 0.812445
+vn 0.259463 -0.526787 0.809428
+vn 0.488775 -0.526744 0.695443
+vn 0.496891 -0.526788 0.689633
+vn 0.679756 -0.526744 0.510366
+vn 0.685680 -0.526788 0.502333
+vn 0.804198 -0.526744 0.275330
+vn 0.807350 -0.526788 0.265860
+vn 0.849919 -0.526744 0.013344
+vn 0.849990 -0.526787 0.003364
+s 1
+g pCylinder3
+usemtl initialShadingGroup
+f 21/22/1 23/21/2 22/23/3
+f 22/23/3 23/21/2 24/24/4
+f 24/24/4 23/21/2 25/25/5
+f 25/25/5 23/21/2 26/26/6
+f 26/26/6 23/21/2 27/27/7
+f 27/27/7 23/21/2 28/28/8
+f 28/28/8 23/21/2 29/29/9
+f 29/29/9 23/21/2 30/30/10
+f 30/30/10 23/21/2 31/31/11
+f 31/31/11 23/21/2 32/32/12
+f 32/32/12 23/21/2 33/33/13
+f 33/33/13 23/21/2 34/34/14
+f 34/34/14 23/21/2 35/35/15
+f 35/35/15 23/21/2 36/36/16
+f 36/36/16 23/21/2 37/37/17
+f 37/37/17 23/21/2 38/38/18
+f 38/38/18 23/21/2 39/39/19
+f 39/39/19 23/21/2 40/40/20
+f 40/40/20 23/21/2 41/41/21
+f 41/41/21 23/21/2 21/22/1
+s 2
+f 1/19/22 21/22/23 2/18/24
+f 2/18/24 21/22/23 22/23/25
+f 2/18/24 22/23/25 3/17/26
+f 3/17/26 22/23/25 24/24/27
+f 3/17/26 24/24/27 4/16/28
+f 4/16/28 24/24/27 25/25/29
+f 25/25/29 26/26/30 4/16/28
+f 5/15/31 4/16/28 42/42/32
+f 4/16/28 26/26/30 42/42/32
+f 26/26/30 27/27/33 42/42/32
+f 5/15/31 42/42/32 6/14/34
+f 42/42/32 27/27/33 6/14/34
+f 6/14/34 27/27/33 7/13/35
+f 7/13/35 27/27/33 28/28/36
+f 7/13/35 28/28/36 8/12/37
+f 8/12/37 28/28/36 29/29/38
+f 8/12/37 29/29/38 9/11/39
+f 9/11/39 29/29/38 30/30/40
+f 9/11/39 30/30/40 10/10/41
+f 10/10/41 30/30/40 31/31/42
+f 10/10/41 31/31/42 11/9/43
+f 11/9/43 31/31/42 32/32/44
+f 11/9/43 32/32/44 12/8/45
+f 12/8/45 32/32/44 33/33/46
+f 12/8/45 33/33/46 13/7/47
+f 13/7/47 33/33/46 34/34/48
+f 13/7/47 34/34/48 14/6/49
+f 14/6/49 34/34/48 35/35/50
+f 14/6/49 35/35/50 15/5/51
+f 15/5/51 35/35/50 36/36/52
+f 15/5/51 36/36/52 16/4/53
+f 16/4/53 36/36/52 37/37/54
+f 16/4/53 37/37/54 17/3/55
+f 17/3/55 37/37/54 38/38/56
+f 17/3/55 38/38/56 18/2/57
+f 18/2/57 38/38/56 39/39/58
+f 18/2/57 39/39/58 19/1/59
+f 19/1/59 39/39/58 40/40/60
+f 19/1/59 40/40/60 20/20/61
+f 20/20/61 40/40/60 41/41/62
+f 20/20/61 41/41/62 1/19/22
+f 1/19/22 41/41/62 21/22/23
+s 3
+f 43/43/63 20/20/64 1/19/65
+f 8/12/66 9/11/67 43/43/63
+f 43/43/63 19/1/68 20/20/64
+f 14/6/69 15/5/70 43/43/63
+f 10/10/71 11/9/72 43/43/63
+f 2/18/73 43/43/63 1/19/65
+f 43/43/63 16/4/74 17/3/75
+f 9/11/67 10/10/71 43/43/63
+f 5/15/76 6/14/77 43/43/63
+f 43/43/63 18/2/78 19/1/68
+f 12/8/79 13/7/80 43/43/63
+f 3/17/81 4/16/82 43/43/63
+f 43/43/63 15/5/70 16/4/74
+f 7/13/83 8/12/66 43/43/63
+f 6/14/77 7/13/83 43/43/63
+f 43/43/63 17/3/75 18/2/78
+f 13/7/80 14/6/69 43/43/63
+f 4/16/82 5/15/76 43/43/63
+f 11/9/72 12/8/79 43/43/63
+f 2/18/73 3/17/81 43/43/63
+s 8
+f 44/44/84 45/45/85 64/47/86
+f 64/47/86 45/45/85 65/46/87
+f 45/45/85 46/48/88 65/46/87
+f 65/46/87 46/48/88 66/49/89
+f 46/48/88 47/50/90 66/49/89
+f 66/49/89 47/50/90 67/51/91
+f 47/50/90 48/52/92 67/51/91
+f 67/51/91 48/52/92 68/53/93
+f 48/52/92 49/54/94 68/53/93
+f 68/53/93 49/54/94 69/55/95
+f 49/54/94 50/56/96 69/55/95
+f 69/55/95 50/56/96 70/57/97
+f 50/56/96 51/58/98 70/57/97
+f 70/57/97 51/58/98 71/59/99
+f 51/58/98 52/60/100 71/59/99
+f 71/59/99 52/60/100 72/61/101
+f 52/60/100 53/62/102 72/61/101
+f 72/61/101 53/62/102 73/63/103
+f 53/62/102 54/64/104 73/63/103
+f 73/63/103 54/64/104 74/65/105
+f 54/64/104 55/66/106 74/65/105
+f 74/65/105 55/66/106 75/67/107
+f 55/66/106 56/68/108 75/67/107
+f 75/67/107 56/68/108 76/69/109
+f 56/68/108 57/70/110 76/69/109
+f 76/69/109 57/70/110 77/71/111
+f 57/70/110 58/72/112 77/71/111
+f 77/71/111 58/72/112 78/73/113
+f 58/72/112 59/74/114 78/73/113
+f 78/73/113 59/74/114 79/75/115
+f 59/74/114 60/76/116 79/75/115
+f 79/75/115 60/76/116 80/77/117
+f 60/76/116 61/78/118 80/77/117
+f 80/77/117 61/78/118 81/79/119
+f 61/78/118 62/80/120 81/79/119
+f 81/79/119 62/80/120 82/81/121
+f 62/80/120 63/82/122 82/81/121
+f 82/81/121 63/82/122 83/83/123
+f 63/82/122 44/84/84 83/83/123
+f 83/83/123 44/84/84 64/85/86
+s 9
+f 45/86/124 44/87/125 84/88/126
+f 46/89/127 45/86/124 84/88/126
+f 47/90/128 46/89/127 84/88/126
+f 48/91/129 47/90/128 84/88/126
+f 49/92/130 48/91/129 84/88/126
+f 50/93/131 49/92/130 84/88/126
+f 51/94/132 50/93/131 84/88/126
+f 52/95/133 51/94/132 84/88/126
+f 53/96/134 52/95/133 84/88/126
+f 54/97/135 53/96/134 84/88/126
+f 55/98/136 54/97/135 84/88/126
+f 56/99/137 55/98/136 84/88/126
+f 57/100/138 56/99/137 84/88/126
+f 58/101/139 57/100/138 84/88/126
+f 59/102/140 58/101/139 84/88/126
+f 60/103/141 59/102/140 84/88/126
+f 61/104/142 60/103/141 84/88/126
+f 62/105/143 61/104/142 84/88/126
+f 63/106/144 62/105/143 84/88/126
+f 44/87/125 63/106/144 84/88/126
+s 10
+f 85/107/145 86/108/146 87/109/147
+f 86/108/146 88/110/148 87/109/147
+f 88/110/148 89/111/149 87/109/147
+f 89/111/149 90/112/150 87/109/147
+f 90/112/150 91/113/151 87/109/147
+f 91/113/151 92/114/152 87/109/147
+f 92/114/152 93/115/153 87/109/147
+f 93/115/153 94/116/154 87/109/147
+f 94/116/154 95/117/155 87/109/147
+f 95/117/155 96/118/156 87/109/147
+f 96/118/156 97/119/157 87/109/147
+f 97/119/157 98/120/158 87/109/147
+f 98/120/158 99/121/159 87/109/147
+f 99/121/159 100/122/160 87/109/147
+f 100/122/160 101/123/161 87/109/147
+f 101/123/161 102/124/162 87/109/147
+f 102/124/162 103/125/163 87/109/147
+f 103/125/163 104/126/164 87/109/147
+f 104/126/164 105/127/165 87/109/147
+f 105/127/165 85/107/145 87/109/147
+s 11
+f 64/128/166 65/129/167 85/107/168
+f 85/107/168 65/129/167 86/108/169
+f 65/129/167 66/130/170 86/108/169
+f 86/108/169 66/130/170 88/110/171
+f 66/130/170 67/131/172 88/110/171
+f 88/110/171 67/131/172 89/111/173
+f 67/131/172 68/132/174 89/111/173
+f 89/111/173 68/132/174 90/112/175
+f 68/132/174 69/133/176 90/112/175
+f 90/112/175 69/133/176 91/113/177
+f 69/133/176 70/134/178 91/113/177
+f 91/113/177 70/134/178 92/114/179
+f 70/134/178 71/135/180 92/114/179
+f 92/114/179 71/135/180 93/115/181
+f 71/135/180 72/136/182 93/115/181
+f 93/115/181 72/136/182 94/116/183
+f 72/136/182 73/137/184 94/116/183
+f 94/116/183 73/137/184 95/117/185
+f 73/137/184 74/138/186 95/117/185
+f 95/117/185 74/138/186 96/118/187
+f 74/138/186 75/139/188 96/118/187
+f 96/118/187 75/139/188 97/119/189
+f 75/139/188 76/140/190 97/119/189
+f 97/119/189 76/140/190 98/120/191
+f 76/140/190 77/141/192 98/120/191
+f 98/120/191 77/141/192 99/121/193
+f 77/141/192 78/142/194 99/121/193
+f 99/121/193 78/142/194 100/122/195
+f 78/142/194 79/143/196 100/122/195
+f 100/122/195 79/143/196 101/123/197
+f 79/143/196 80/144/198 101/123/197
+f 101/123/197 80/144/198 102/124/199
+f 80/144/198 81/145/200 102/124/199
+f 102/124/199 81/145/200 103/125/201
+f 81/145/200 82/146/202 103/125/201
+f 103/125/201 82/146/202 104/126/203
+f 82/146/202 83/147/204 104/126/203
+f 104/126/203 83/147/204 105/127/205
+f 83/147/204 64/128/166 105/127/205
+f 105/127/205 64/128/166 85/107/168
diff --git a/build/assets/Lights-eaf0da.bmp b/build/assets/Lights-eaf0da.bmp
new file mode 100644
index 0000000..470afaa
Binary files /dev/null and b/build/assets/Lights-eaf0da.bmp differ
diff --git a/build/assets/Normal-38c0d1.bmp b/build/assets/Normal-38c0d1.bmp
new file mode 100644
index 0000000..20071d9
Binary files /dev/null and b/build/assets/Normal-38c0d1.bmp differ
diff --git a/build/assets/Silver-bb39da.bmp b/build/assets/Silver-bb39da.bmp
new file mode 100644
index 0000000..48b43a4
Binary files /dev/null and b/build/assets/Silver-bb39da.bmp differ
diff --git a/build/bundle.js b/build/bundle.js
new file mode 100644
index 0000000..d96312b
--- /dev/null
+++ b/build/bundle.js
@@ -0,0 +1,51655 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ var _framework = __webpack_require__(1);
+
+ var _framework2 = _interopRequireDefault(_framework);
+
+ var _marching_cube_LUT = __webpack_require__(8);
+
+ var _marching_cube_LUT2 = _interopRequireDefault(_marching_cube_LUT);
+
+ var _marching_cubes = __webpack_require__(9);
+
+ var _marching_cubes2 = _interopRequireDefault(_marching_cubes);
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+ __webpack_require__(12);
+
+ // Resources:
+ // http://jamie-wong.com/2014/08/19/metaballs-and-marching-squares/
+ // http://paulbourke.net/geometry/polygonise/
+
+ var THREE = __webpack_require__(6); // older modules are imported like this. You shouldn't have to worry about this much
+
+ // import OBJLoader from './OBJLoader.js'
+
+ var OBJLoader = __webpack_require__(13);
+ OBJLoader(THREE);
+
+ var DEFAULT_VISUAL_DEBUG = false;
+ var DEFAULT_ISO_LEVEL = 1.3;
+ var DEFAULT_GRID_RES = 30;
+ var DEFAULT_GRID_WIDTH = 10;
+ var DEFAULT_GRID_HEIGHT = 20;
+ var DEFAULT_GRID_DEPTH = 10;
+ var DEFAULT_NUM_METABALLS = 6;
+ var DEFAULT_MIN_RADIUS = 1.3;
+ var DEFAULT_MAX_RADIUS = 1.8;
+ var DEFAULT_MAX_SPEED = 1.0;
+
+ //------------------------------------------------------------------------------------------------------------------
+ var lavalamp_metalGeo;
+ var metal_mat = new THREE.MeshStandardMaterial({
+ color: 0xBCC6CC,
+ emissive: 0xffffff,
+ emissiveIntensity: 0.5,
+ metalness: 1,
+ roughness: 0.4
+ });
+ var lampmetal;
+
+ var lavalamp_glassGeo;
+ var glass_mat = new THREE.MeshLambertMaterial({ color: 0xffffff, emissive: 0xd447ff, transparent: true, opacity: 0.3 });
+ var lampglass;
+
+ //------------------------------------------------------------------------------------------------------------------
+
+ var iridescent_Material = new THREE.ShaderMaterial({
+ uniforms: {
+ metaball_color: {
+ type: "v3",
+ value: new THREE.Color(0x2194ce)
+ },
+ lightPos: {
+ type: "v3",
+ value: new THREE.Vector3(1.0, 10.0, 2.0)
+ }
+ },
+ vertexShader: __webpack_require__(14),
+ fragmentShader: __webpack_require__(15)
+ });
+
+ var litSphere_Material = new THREE.ShaderMaterial({
+ uniforms: {
+ texture: {
+ type: "t",
+ value: THREE.ImageUtils.loadTexture(__webpack_require__(16))
+ },
+ u_albedo: {
+ type: 'v3',
+ value: new THREE.Vector3(0.867, 0.867, 0.867)
+ },
+ lightPos: {
+ type: 'v3',
+ value: new THREE.Vector3(1.0, 10.0, 2.0)
+ }
+ },
+ vertexShader: __webpack_require__(17),
+ fragmentShader: __webpack_require__(18)
+ });
+
+ //------------------------------------------------------------------------------------------------------------------
+
+ var App = {
+
+ marchingCubes: undefined,
+ config: {
+ // Global control of all visual debugging.
+ // This can be set to false to disallow any memory allocation of visual debugging components.
+ // **Note**: If your application experiences performance drop, disable this flag.
+ visualDebug: DEFAULT_VISUAL_DEBUG,
+
+ // The isolevel for marching cubes
+ isolevel: DEFAULT_ISO_LEVEL,
+
+ // Grid resolution in each dimension. If gridRes = 4, then we have a 4x4x4 grid
+ gridRes: DEFAULT_GRID_RES,
+
+ // Total width of grid
+ gridWidth: DEFAULT_GRID_WIDTH,
+ gridHeight: DEFAULT_GRID_HEIGHT,
+ gridDepth: DEFAULT_GRID_DEPTH,
+
+ // Width of each voxel
+ // Ideally, we want the voxel to be small (higher resolution)
+ gridCellWidth: DEFAULT_GRID_WIDTH / DEFAULT_GRID_RES,
+ gridCellHeight: DEFAULT_GRID_HEIGHT / DEFAULT_GRID_RES,
+ gridCellDepth: DEFAULT_GRID_DEPTH / DEFAULT_GRID_RES,
+
+ // Number of metaballs
+ numMetaballs: DEFAULT_NUM_METABALLS,
+
+ // Minimum radius of a metaball
+ minRadius: DEFAULT_MIN_RADIUS,
+
+ // Maxium radius of a metaball
+ maxRadius: DEFAULT_MAX_RADIUS,
+
+ // Maximum speed of a metaball
+ maxSpeed: DEFAULT_MAX_SPEED,
+
+ //actual speed of metaballs
+ speed: 0.1,
+
+ //material used by metaballs
+ material: iridescent_Material
+ },
+
+ // Scene's framework objects
+ camera: undefined,
+ scene: undefined,
+ renderer: undefined,
+
+ //Position of the Light in the scene; for iridescent shader
+ lightPos: new THREE.Vector3(1.0, 10.0, 2.0),
+
+ // Play/pause control for the simulation
+ isPaused: false,
+ Scenario: 1,
+ Shader: 1,
+ LitSphereTexture: 1
+ };
+
+ // called after the scene loads
+ function onLoad(framework) {
+ var scene = framework.scene,
+ camera = framework.camera,
+ renderer = framework.renderer,
+ gui = framework.gui,
+ stats = framework.stats;
+
+ App.scene = scene;
+ App.camera = camera;
+ App.renderer = renderer;
+
+ renderer.setClearColor(0xbfd1e5);
+
+ //scene.add(new THREE.AxisHelper(20));
+
+ LoadLavaLamp(App.scene);
+
+ setupCamera(App.camera);
+ setupLights(App.scene);
+ setupScene(App.scene);
+ setupGUI(gui, App.scene);
+ }
+
+ // called on frame updates
+ function onUpdate(framework) {
+ if (App.marchingCubes) {
+ App.marchingCubes.update();
+ }
+ }
+
+ function LoadLavaLamp(scene) {
+ var objLoader = new THREE.OBJLoader();
+
+ var obj = objLoader.load(__webpack_require__(19), function (obj) {
+ lavalamp_metalGeo = obj.children[0].geometry;
+ lampmetal = new THREE.Mesh(lavalamp_metalGeo, metal_mat);
+ lampmetal.position.set(DEFAULT_GRID_WIDTH * 0.5, -7, DEFAULT_GRID_DEPTH * 0.5);
+ lampmetal.scale.set(3.8, 3.5, 3.8);
+ scene.add(lampmetal);
+ });
+
+ var obj = objLoader.load(__webpack_require__(20), function (obj) {
+ lavalamp_glassGeo = obj.children[0].geometry;
+ lampglass = new THREE.Mesh(lavalamp_glassGeo, glass_mat);
+ lampglass.position.set(DEFAULT_GRID_WIDTH * 0.5, -7, DEFAULT_GRID_DEPTH * 0.5);
+ lampglass.scale.set(3.8, 3.5, 3.8);
+ scene.add(lampglass);
+ });
+ }
+
+ function setupCamera(camera) {
+ // set camera position
+ camera.position.set(25, 8, 25);
+ camera.lookAt(new THREE.Vector3(0, 8, 0));
+ }
+
+ function setupLights(scene) {
+ // Directional light
+ var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
+ directionalLight.color.setHSL(0.1, 1, 0.95);
+ directionalLight.position.set(1, 10, 2);
+ directionalLight.position.multiplyScalar(10);
+
+ App.lightPos = directionalLight.position;
+ scene.add(directionalLight);
+ }
+
+ function setupScene(scene) {
+ App.marchingCubes = new _marching_cubes2.default(App);
+ }
+
+ function setupGUI(gui, scene) {
+ // --- CONFIG ---
+ gui.add(App.config, 'isolevel', 0.5, 2).name("IsoLevel").onChange(function (value) {
+ onreset(App.scene);
+ });
+ // gui.add(App.config, 'gridRes', 30, 70).name("Grid Resolution").step(1).onChange(function(value) {
+ // onreset(App.scene);
+ // });
+
+ gui.add(App.config, 'numMetaballs', 1, 10).name("Number of Metaballs").step(1).onChange(function (value) {
+ onreset(App.scene);
+ });
+ gui.add(App.config, 'speed', 0.01, DEFAULT_MAX_SPEED).name("Speed").onChange(function (value) {
+ onreset(App.scene);
+ });
+
+ gui.add(App, 'Shader', { Iridescent: 1, LitSphere: 2 }).onChange(function (value) {
+ onreset(App.scene);
+ });
+
+ gui.add(App, 'LitSphereTexture', { Green: 1, Gold: 2, Silver: 3, Flame: 4, Lights: 5, Brown: 6, Normal: 7 }).name("LitSphere Texture").onChange(function (value) {
+ App.Shader = 2;
+ if (value == 1) {
+ litSphere_Material.uniforms.texture.value = THREE.ImageUtils.loadTexture(__webpack_require__(21));
+ } else if (value == 2) {
+ litSphere_Material.uniforms.texture.value = THREE.ImageUtils.loadTexture(__webpack_require__(22));
+ } else if (value == 3) {
+ litSphere_Material.uniforms.texture.value = THREE.ImageUtils.loadTexture(__webpack_require__(16));
+ } else if (value == 4) {
+ litSphere_Material.uniforms.texture.value = THREE.ImageUtils.loadTexture(__webpack_require__(23));
+ } else if (value == 5) {
+ litSphere_Material.uniforms.texture.value = THREE.ImageUtils.loadTexture(__webpack_require__(24));
+ } else if (value == 6) {
+ litSphere_Material.uniforms.texture.value = THREE.ImageUtils.loadTexture(__webpack_require__(25));
+ } else {
+ litSphere_Material.uniforms.texture.value = THREE.ImageUtils.loadTexture(__webpack_require__(26));
+ }
+
+ onreset(App.scene);
+ });
+
+ gui.add(App, 'Scenario', { Lavalamp: 1, Metaballs: 2 }).onChange(function (value) {
+ onreset(App.scene);
+ });
+
+ gui.add(App, 'isPaused').name("Pause").onChange(function (value) {
+ App.isPaused = value;
+ if (value) {
+ App.marchingCubes.pause();
+ } else {
+ App.marchingCubes.play();
+ }
+ });
+
+ // --- DEBUG ---
+ //uncomment for debugging purposes
+ /*
+ var debugFolder = gui.addFolder('Debug');
+ debugFolder.add(App.marchingCubes, 'showGrid').onChange(function(value) {
+ App.marchingCubes.showGrid = value;
+ if (value)
+ {
+ App.marchingCubes.show();
+ }
+ else
+ {
+ App.marchingCubes.hide();
+ }
+ });
+
+ debugFolder.add(App.marchingCubes, 'showSpheres').onChange(function(value) {
+ App.marchingCubes.showSpheres = value;
+ if (value)
+ {
+ for (var i = 0; i < App.config.numMetaballs; i++)
+ {
+ App.marchingCubes.balls[i].show();
+ }
+ }
+ else
+ {
+ for (var i = 0; i < App.config.numMetaballs; i++)
+ {
+ App.marchingCubes.balls[i].hide();
+ }
+ }
+ });
+ debugFolder.open();
+ */
+ }
+
+ function onreset(scene) {
+ cleanscene(scene);
+ setupLights(App.scene);
+
+ if (App.Shader == 1) {
+ App.config.material = iridescent_Material;
+ } else {
+ App.config.material = litSphere_Material;
+ }
+
+ if (App.Scenario == 1) {
+ scene.add(lampmetal);
+ scene.add(lampglass);
+ App.renderer.setClearColor(0xbfd1e5);
+ // set camera position
+ App.camera.position.set(25, 8, 25);
+ App.camera.lookAt(new THREE.Vector3(0, 8, 0));
+ } else {
+ App.renderer.setClearColor(0x000);
+ // set camera position
+ App.camera.position.set(18, 10, 18);
+ App.camera.lookAt(new THREE.Vector3(0, 10, 0));
+ }
+
+ App.marchingCubes.init(App);
+ }
+
+ function cleanscene(scene) {
+ //remove all objects from the scene
+ for (var i = scene.children.length - 1; i >= 0; i--) {
+ var obj = scene.children[i];
+ scene.remove(obj);
+ }
+ }
+
+ // when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate
+ _framework2.default.init(onLoad, onUpdate);
+
+/***/ }),
+/* 1 */
+/***/ (function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ Object.defineProperty(exports, "__esModule", {
+ value: true
+ });
+
+ var _statsJs = __webpack_require__(2);
+
+ var _statsJs2 = _interopRequireDefault(_statsJs);
+
+ var _datGui = __webpack_require__(3);
+
+ var _datGui2 = _interopRequireDefault(_datGui);
+
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+ var THREE = __webpack_require__(6);
+ var OrbitControls = __webpack_require__(7)(THREE);
+
+
+ // when the scene is done initializing, the function passed as `callback` will be executed
+ // then, every frame, the function passed as `update` will be executed
+ function init(callback, update) {
+ var stats = new _statsJs2.default();
+ stats.setMode(1);
+ stats.domElement.style.position = 'absolute';
+ stats.domElement.style.left = '0px';
+ stats.domElement.style.top = '0px';
+ document.body.appendChild(stats.domElement);
+
+ var gui = new _datGui2.default.GUI({ width: 300 });
+
+ var framework = {
+ gui: gui,
+ stats: stats
+ };
+
+ // run this function after the window loads
+ window.addEventListener('load', function () {
+
+ var scene = new THREE.Scene();
+ var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
+ var renderer = new THREE.WebGLRenderer({ antialias: true });
+ renderer.setPixelRatio(window.devicePixelRatio);
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setClearColor(0x020202, 0);
+
+ var controls = new OrbitControls(camera, renderer.domElement);
+ controls.enableDamping = true;
+ controls.enableZoom = true;
+ controls.target.set(0, 10, 0);
+ controls.rotateSpeed = 0.3;
+ controls.zoomSpeed = 1.0;
+ controls.panSpeed = 2.0;
+ controls.addEventListener('change', function () {
+ camera.hasMoved = true;
+ });
+
+ document.body.appendChild(renderer.domElement);
+
+ // resize the canvas when the window changes
+ window.addEventListener('resize', function () {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ }, false);
+
+ // assign THREE.js objects to the object we will return
+ framework.scene = scene;
+ framework.camera = camera;
+ framework.renderer = renderer;
+
+ // begin the animation loop
+ (function tick() {
+ stats.begin();
+ update(framework); // perform any requested updates
+ renderer.render(scene, camera); // render the scene
+ stats.end();
+ requestAnimationFrame(tick); // register to call this again when the browser renders a new frame
+ })();
+
+ // we will pass the scene, gui, renderer, camera, etc... to the callback function
+ return callback(framework);
+ });
+ }
+
+ exports.default = {
+ init: init
+ };
+
+/***/ }),
+/* 2 */
+/***/ (function(module, exports) {
+
+ // stats.js - http://github.com/mrdoob/stats.js
+ var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";
+ i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
+ k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
+ "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:12,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
+ a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};"object"===typeof module&&(module.exports=Stats);
+
+
+/***/ }),
+/* 3 */
+/***/ (function(module, exports, __webpack_require__) {
+
+ module.exports = __webpack_require__(4)
+ module.exports.color = __webpack_require__(5)
+
+/***/ }),
+/* 4 */
+/***/ (function(module, exports) {
+
+ /**
+ * dat-gui JavaScript Controller Library
+ * http://code.google.com/p/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
+ */
+
+ /** @namespace */
+ var dat = module.exports = dat || {};
+
+ /** @namespace */
+ dat.gui = dat.gui || {};
+
+ /** @namespace */
+ dat.utils = dat.utils || {};
+
+ /** @namespace */
+ dat.controllers = dat.controllers || {};
+
+ /** @namespace */
+ dat.dom = dat.dom || {};
+
+ /** @namespace */
+ dat.color = dat.color || {};
+
+ dat.utils.css = (function () {
+ return {
+ load: function (url, doc) {
+ doc = doc || document;
+ var link = doc.createElement('link');
+ link.type = 'text/css';
+ link.rel = 'stylesheet';
+ link.href = url;
+ doc.getElementsByTagName('head')[0].appendChild(link);
+ },
+ inject: function(css, doc) {
+ doc = doc || document;
+ var injected = document.createElement('style');
+ injected.type = 'text/css';
+ injected.innerHTML = css;
+ doc.getElementsByTagName('head')[0].appendChild(injected);
+ }
+ }
+ })();
+
+
+ dat.utils.common = (function () {
+
+ var ARR_EACH = Array.prototype.forEach;
+ var ARR_SLICE = Array.prototype.slice;
+
+ /**
+ * Band-aid methods for things that should be a lot easier in JavaScript.
+ * Implementation and structure inspired by underscore.js
+ * http://documentcloud.github.com/underscore/
+ */
+
+ return {
+
+ BREAK: {},
+
+ extend: function(target) {
+
+ this.each(ARR_SLICE.call(arguments, 1), function(obj) {
+
+ for (var key in obj)
+ if (!this.isUndefined(obj[key]))
+ target[key] = obj[key];
+
+ }, this);
+
+ return target;
+
+ },
+
+ defaults: function(target) {
+
+ this.each(ARR_SLICE.call(arguments, 1), function(obj) {
+
+ for (var key in obj)
+ if (this.isUndefined(target[key]))
+ target[key] = obj[key];
+
+ }, this);
+
+ return target;
+
+ },
+
+ compose: function() {
+ var toCall = ARR_SLICE.call(arguments);
+ return function() {
+ var args = ARR_SLICE.call(arguments);
+ for (var i = toCall.length -1; i >= 0; i--) {
+ args = [toCall[i].apply(this, args)];
+ }
+ return args[0];
+ }
+ },
+
+ each: function(obj, itr, scope) {
+
+
+ if (ARR_EACH && obj.forEach === ARR_EACH) {
+
+ obj.forEach(itr, scope);
+
+ } else if (obj.length === obj.length + 0) { // Is number but not NaN
+
+ for (var key = 0, l = obj.length; key < l; key++)
+ if (key in obj && itr.call(scope, obj[key], key) === this.BREAK)
+ return;
+
+ } else {
+
+ for (var key in obj)
+ if (itr.call(scope, obj[key], key) === this.BREAK)
+ return;
+
+ }
+
+ },
+
+ defer: function(fnc) {
+ setTimeout(fnc, 0);
+ },
+
+ toArray: function(obj) {
+ if (obj.toArray) return obj.toArray();
+ return ARR_SLICE.call(obj);
+ },
+
+ isUndefined: function(obj) {
+ return obj === undefined;
+ },
+
+ isNull: function(obj) {
+ return obj === null;
+ },
+
+ isNaN: function(obj) {
+ return obj !== obj;
+ },
+
+ isArray: Array.isArray || function(obj) {
+ return obj.constructor === Array;
+ },
+
+ isObject: function(obj) {
+ return obj === Object(obj);
+ },
+
+ isNumber: function(obj) {
+ return obj === obj+0;
+ },
+
+ isString: function(obj) {
+ return obj === obj+'';
+ },
+
+ isBoolean: function(obj) {
+ return obj === false || obj === true;
+ },
+
+ isFunction: function(obj) {
+ return Object.prototype.toString.call(obj) === '[object Function]';
+ }
+
+ };
+
+ })();
+
+
+ dat.controllers.Controller = (function (common) {
+
+ /**
+ * @class An "abstract" class that represents a given property of an object.
+ *
+ * @param {Object} object The object to be manipulated
+ * @param {string} property The name of the property to be manipulated
+ *
+ * @member dat.controllers
+ */
+ var Controller = function(object, property) {
+
+ this.initialValue = object[property];
+
+ /**
+ * Those who extend this class will put their DOM elements in here.
+ * @type {DOMElement}
+ */
+ this.domElement = document.createElement('div');
+
+ /**
+ * The object to manipulate
+ * @type {Object}
+ */
+ this.object = object;
+
+ /**
+ * The name of the property to manipulate
+ * @type {String}
+ */
+ this.property = property;
+
+ /**
+ * The function to be called on change.
+ * @type {Function}
+ * @ignore
+ */
+ this.__onChange = undefined;
+
+ /**
+ * The function to be called on finishing change.
+ * @type {Function}
+ * @ignore
+ */
+ this.__onFinishChange = undefined;
+
+ };
+
+ common.extend(
+
+ Controller.prototype,
+
+ /** @lends dat.controllers.Controller.prototype */
+ {
+
+ /**
+ * Specify that a function fire every time someone changes the value with
+ * this Controller.
+ *
+ * @param {Function} fnc This function will be called whenever the value
+ * is modified via this Controller.
+ * @returns {dat.controllers.Controller} this
+ */
+ onChange: function(fnc) {
+ this.__onChange = fnc;
+ return this;
+ },
+
+ /**
+ * Specify that a function fire every time someone "finishes" changing
+ * the value wih this Controller. Useful for values that change
+ * incrementally like numbers or strings.
+ *
+ * @param {Function} fnc This function will be called whenever
+ * someone "finishes" changing the value via this Controller.
+ * @returns {dat.controllers.Controller} this
+ */
+ onFinishChange: function(fnc) {
+ this.__onFinishChange = fnc;
+ return this;
+ },
+
+ /**
+ * Change the value of object[property]
+ *
+ * @param {Object} newValue The new value of object[property]
+ */
+ setValue: function(newValue) {
+ this.object[this.property] = newValue;
+ if (this.__onChange) {
+ this.__onChange.call(this, newValue);
+ }
+ this.updateDisplay();
+ return this;
+ },
+
+ /**
+ * Gets the value of object[property]
+ *
+ * @returns {Object} The current value of object[property]
+ */
+ getValue: function() {
+ return this.object[this.property];
+ },
+
+ /**
+ * Refreshes the visual display of a Controller in order to keep sync
+ * with the object's current value.
+ * @returns {dat.controllers.Controller} this
+ */
+ updateDisplay: function() {
+ return this;
+ },
+
+ /**
+ * @returns {Boolean} true if the value has deviated from initialValue
+ */
+ isModified: function() {
+ return this.initialValue !== this.getValue()
+ }
+
+ }
+
+ );
+
+ return Controller;
+
+
+ })(dat.utils.common);
+
+
+ dat.dom.dom = (function (common) {
+
+ var EVENT_MAP = {
+ 'HTMLEvents': ['change'],
+ 'MouseEvents': ['click','mousemove','mousedown','mouseup', 'mouseover'],
+ 'KeyboardEvents': ['keydown']
+ };
+
+ var EVENT_MAP_INV = {};
+ common.each(EVENT_MAP, function(v, k) {
+ common.each(v, function(e) {
+ EVENT_MAP_INV[e] = k;
+ });
+ });
+
+ var CSS_VALUE_PIXELS = /(\d+(\.\d+)?)px/;
+
+ function cssValueToPixels(val) {
+
+ if (val === '0' || common.isUndefined(val)) return 0;
+
+ var match = val.match(CSS_VALUE_PIXELS);
+
+ if (!common.isNull(match)) {
+ return parseFloat(match[1]);
+ }
+
+ // TODO ...ems? %?
+
+ return 0;
+
+ }
+
+ /**
+ * @namespace
+ * @member dat.dom
+ */
+ var dom = {
+
+ /**
+ *
+ * @param elem
+ * @param selectable
+ */
+ makeSelectable: function(elem, selectable) {
+
+ if (elem === undefined || elem.style === undefined) return;
+
+ elem.onselectstart = selectable ? function() {
+ return false;
+ } : function() {
+ };
+
+ elem.style.MozUserSelect = selectable ? 'auto' : 'none';
+ elem.style.KhtmlUserSelect = selectable ? 'auto' : 'none';
+ elem.unselectable = selectable ? 'on' : 'off';
+
+ },
+
+ /**
+ *
+ * @param elem
+ * @param horizontal
+ * @param vertical
+ */
+ makeFullscreen: function(elem, horizontal, vertical) {
+
+ if (common.isUndefined(horizontal)) horizontal = true;
+ if (common.isUndefined(vertical)) vertical = true;
+
+ elem.style.position = 'absolute';
+
+ if (horizontal) {
+ elem.style.left = 0;
+ elem.style.right = 0;
+ }
+ if (vertical) {
+ elem.style.top = 0;
+ elem.style.bottom = 0;
+ }
+
+ },
+
+ /**
+ *
+ * @param elem
+ * @param eventType
+ * @param params
+ */
+ fakeEvent: function(elem, eventType, params, aux) {
+ params = params || {};
+ var className = EVENT_MAP_INV[eventType];
+ if (!className) {
+ throw new Error('Event type ' + eventType + ' not supported.');
+ }
+ var evt = document.createEvent(className);
+ switch (className) {
+ case 'MouseEvents':
+ var clientX = params.x || params.clientX || 0;
+ var clientY = params.y || params.clientY || 0;
+ evt.initMouseEvent(eventType, params.bubbles || false,
+ params.cancelable || true, window, params.clickCount || 1,
+ 0, //screen X
+ 0, //screen Y
+ clientX, //client X
+ clientY, //client Y
+ false, false, false, false, 0, null);
+ break;
+ case 'KeyboardEvents':
+ var init = evt.initKeyboardEvent || evt.initKeyEvent; // webkit || moz
+ common.defaults(params, {
+ cancelable: true,
+ ctrlKey: false,
+ altKey: false,
+ shiftKey: false,
+ metaKey: false,
+ keyCode: undefined,
+ charCode: undefined
+ });
+ init(eventType, params.bubbles || false,
+ params.cancelable, window,
+ params.ctrlKey, params.altKey,
+ params.shiftKey, params.metaKey,
+ params.keyCode, params.charCode);
+ break;
+ default:
+ evt.initEvent(eventType, params.bubbles || false,
+ params.cancelable || true);
+ break;
+ }
+ common.defaults(evt, aux);
+ elem.dispatchEvent(evt);
+ },
+
+ /**
+ *
+ * @param elem
+ * @param event
+ * @param func
+ * @param bool
+ */
+ bind: function(elem, event, func, bool) {
+ bool = bool || false;
+ if (elem.addEventListener)
+ elem.addEventListener(event, func, bool);
+ else if (elem.attachEvent)
+ elem.attachEvent('on' + event, func);
+ return dom;
+ },
+
+ /**
+ *
+ * @param elem
+ * @param event
+ * @param func
+ * @param bool
+ */
+ unbind: function(elem, event, func, bool) {
+ bool = bool || false;
+ if (elem.removeEventListener)
+ elem.removeEventListener(event, func, bool);
+ else if (elem.detachEvent)
+ elem.detachEvent('on' + event, func);
+ return dom;
+ },
+
+ /**
+ *
+ * @param elem
+ * @param className
+ */
+ addClass: function(elem, className) {
+ if (elem.className === undefined) {
+ elem.className = className;
+ } else if (elem.className !== className) {
+ var classes = elem.className.split(/ +/);
+ if (classes.indexOf(className) == -1) {
+ classes.push(className);
+ elem.className = classes.join(' ').replace(/^\s+/, '').replace(/\s+$/, '');
+ }
+ }
+ return dom;
+ },
+
+ /**
+ *
+ * @param elem
+ * @param className
+ */
+ removeClass: function(elem, className) {
+ if (className) {
+ if (elem.className === undefined) {
+ // elem.className = className;
+ } else if (elem.className === className) {
+ elem.removeAttribute('class');
+ } else {
+ var classes = elem.className.split(/ +/);
+ var index = classes.indexOf(className);
+ if (index != -1) {
+ classes.splice(index, 1);
+ elem.className = classes.join(' ');
+ }
+ }
+ } else {
+ elem.className = undefined;
+ }
+ return dom;
+ },
+
+ hasClass: function(elem, className) {
+ return new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)').test(elem.className) || false;
+ },
+
+ /**
+ *
+ * @param elem
+ */
+ getWidth: function(elem) {
+
+ var style = getComputedStyle(elem);
+
+ return cssValueToPixels(style['border-left-width']) +
+ cssValueToPixels(style['border-right-width']) +
+ cssValueToPixels(style['padding-left']) +
+ cssValueToPixels(style['padding-right']) +
+ cssValueToPixels(style['width']);
+ },
+
+ /**
+ *
+ * @param elem
+ */
+ getHeight: function(elem) {
+
+ var style = getComputedStyle(elem);
+
+ return cssValueToPixels(style['border-top-width']) +
+ cssValueToPixels(style['border-bottom-width']) +
+ cssValueToPixels(style['padding-top']) +
+ cssValueToPixels(style['padding-bottom']) +
+ cssValueToPixels(style['height']);
+ },
+
+ /**
+ *
+ * @param elem
+ */
+ getOffset: function(elem) {
+ var offset = {left: 0, top:0};
+ if (elem.offsetParent) {
+ do {
+ offset.left += elem.offsetLeft;
+ offset.top += elem.offsetTop;
+ } while (elem = elem.offsetParent);
+ }
+ return offset;
+ },
+
+ // http://stackoverflow.com/posts/2684561/revisions
+ /**
+ *
+ * @param elem
+ */
+ isActive: function(elem) {
+ return elem === document.activeElement && ( elem.type || elem.href );
+ }
+
+ };
+
+ return dom;
+
+ })(dat.utils.common);
+
+
+ dat.controllers.OptionController = (function (Controller, dom, common) {
+
+ /**
+ * @class Provides a select input to alter the property of an object, using a
+ * list of accepted values.
+ *
+ * @extends dat.controllers.Controller
+ *
+ * @param {Object} object The object to be manipulated
+ * @param {string} property The name of the property to be manipulated
+ * @param {Object|string[]} options A map of labels to acceptable values, or
+ * a list of acceptable string values.
+ *
+ * @member dat.controllers
+ */
+ var OptionController = function(object, property, options) {
+
+ OptionController.superclass.call(this, object, property);
+
+ var _this = this;
+
+ /**
+ * The drop down menu
+ * @ignore
+ */
+ this.__select = document.createElement('select');
+
+ if (common.isArray(options)) {
+ var map = {};
+ common.each(options, function(element) {
+ map[element] = element;
+ });
+ options = map;
+ }
+
+ common.each(options, function(value, key) {
+
+ var opt = document.createElement('option');
+ opt.innerHTML = key;
+ opt.setAttribute('value', value);
+ _this.__select.appendChild(opt);
+
+ });
+
+ // Acknowledge original value
+ this.updateDisplay();
+
+ dom.bind(this.__select, 'change', function() {
+ var desiredValue = this.options[this.selectedIndex].value;
+ _this.setValue(desiredValue);
+ });
+
+ this.domElement.appendChild(this.__select);
+
+ };
+
+ OptionController.superclass = Controller;
+
+ common.extend(
+
+ OptionController.prototype,
+ Controller.prototype,
+
+ {
+
+ setValue: function(v) {
+ var toReturn = OptionController.superclass.prototype.setValue.call(this, v);
+ if (this.__onFinishChange) {
+ this.__onFinishChange.call(this, this.getValue());
+ }
+ return toReturn;
+ },
+
+ updateDisplay: function() {
+ this.__select.value = this.getValue();
+ return OptionController.superclass.prototype.updateDisplay.call(this);
+ }
+
+ }
+
+ );
+
+ return OptionController;
+
+ })(dat.controllers.Controller,
+ dat.dom.dom,
+ dat.utils.common);
+
+
+ dat.controllers.NumberController = (function (Controller, common) {
+
+ /**
+ * @class Represents a given property of an object that is a number.
+ *
+ * @extends dat.controllers.Controller
+ *
+ * @param {Object} object The object to be manipulated
+ * @param {string} property The name of the property to be manipulated
+ * @param {Object} [params] Optional parameters
+ * @param {Number} [params.min] Minimum allowed value
+ * @param {Number} [params.max] Maximum allowed value
+ * @param {Number} [params.step] Increment by which to change value
+ *
+ * @member dat.controllers
+ */
+ var NumberController = function(object, property, params) {
+
+ NumberController.superclass.call(this, object, property);
+
+ params = params || {};
+
+ this.__min = params.min;
+ this.__max = params.max;
+ this.__step = params.step;
+
+ if (common.isUndefined(this.__step)) {
+
+ if (this.initialValue == 0) {
+ this.__impliedStep = 1; // What are we, psychics?
+ } else {
+ // Hey Doug, check this out.
+ this.__impliedStep = Math.pow(10, Math.floor(Math.log(this.initialValue)/Math.LN10))/10;
+ }
+
+ } else {
+
+ this.__impliedStep = this.__step;
+
+ }
+
+ this.__precision = numDecimals(this.__impliedStep);
+
+
+ };
+
+ NumberController.superclass = Controller;
+
+ common.extend(
+
+ NumberController.prototype,
+ Controller.prototype,
+
+ /** @lends dat.controllers.NumberController.prototype */
+ {
+
+ setValue: function(v) {
+
+ if (this.__min !== undefined && v < this.__min) {
+ v = this.__min;
+ } else if (this.__max !== undefined && v > this.__max) {
+ v = this.__max;
+ }
+
+ if (this.__step !== undefined && v % this.__step != 0) {
+ v = Math.round(v / this.__step) * this.__step;
+ }
+
+ return NumberController.superclass.prototype.setValue.call(this, v);
+
+ },
+
+ /**
+ * Specify a minimum value for object[property].
+ *
+ * @param {Number} minValue The minimum value for
+ * object[property]
+ * @returns {dat.controllers.NumberController} this
+ */
+ min: function(v) {
+ this.__min = v;
+ return this;
+ },
+
+ /**
+ * Specify a maximum value for object[property].
+ *
+ * @param {Number} maxValue The maximum value for
+ * object[property]
+ * @returns {dat.controllers.NumberController} this
+ */
+ max: function(v) {
+ this.__max = v;
+ return this;
+ },
+
+ /**
+ * Specify a step value that dat.controllers.NumberController
+ * increments by.
+ *
+ * @param {Number} stepValue The step value for
+ * dat.controllers.NumberController
+ * @default if minimum and maximum specified increment is 1% of the
+ * difference otherwise stepValue is 1
+ * @returns {dat.controllers.NumberController} this
+ */
+ step: function(v) {
+ this.__step = v;
+ return this;
+ }
+
+ }
+
+ );
+
+ function numDecimals(x) {
+ x = x.toString();
+ if (x.indexOf('.') > -1) {
+ return x.length - x.indexOf('.') - 1;
+ } else {
+ return 0;
+ }
+ }
+
+ return NumberController;
+
+ })(dat.controllers.Controller,
+ dat.utils.common);
+
+
+ dat.controllers.NumberControllerBox = (function (NumberController, dom, common) {
+
+ /**
+ * @class Represents a given property of an object that is a number and
+ * provides an input element with which to manipulate it.
+ *
+ * @extends dat.controllers.Controller
+ * @extends dat.controllers.NumberController
+ *
+ * @param {Object} object The object to be manipulated
+ * @param {string} property The name of the property to be manipulated
+ * @param {Object} [params] Optional parameters
+ * @param {Number} [params.min] Minimum allowed value
+ * @param {Number} [params.max] Maximum allowed value
+ * @param {Number} [params.step] Increment by which to change value
+ *
+ * @member dat.controllers
+ */
+ var NumberControllerBox = function(object, property, params) {
+
+ this.__truncationSuspended = false;
+
+ NumberControllerBox.superclass.call(this, object, property, params);
+
+ var _this = this;
+
+ /**
+ * {Number} Previous mouse y position
+ * @ignore
+ */
+ var prev_y;
+
+ this.__input = document.createElement('input');
+ this.__input.setAttribute('type', 'text');
+
+ // Makes it so manually specified values are not truncated.
+
+ dom.bind(this.__input, 'change', onChange);
+ dom.bind(this.__input, 'blur', onBlur);
+ dom.bind(this.__input, 'mousedown', onMouseDown);
+ dom.bind(this.__input, 'keydown', function(e) {
+
+ // When pressing entire, you can be as precise as you want.
+ if (e.keyCode === 13) {
+ _this.__truncationSuspended = true;
+ this.blur();
+ _this.__truncationSuspended = false;
+ }
+
+ });
+
+ function onChange() {
+ var attempted = parseFloat(_this.__input.value);
+ if (!common.isNaN(attempted)) _this.setValue(attempted);
+ }
+
+ function onBlur() {
+ onChange();
+ if (_this.__onFinishChange) {
+ _this.__onFinishChange.call(_this, _this.getValue());
+ }
+ }
+
+ function onMouseDown(e) {
+ dom.bind(window, 'mousemove', onMouseDrag);
+ dom.bind(window, 'mouseup', onMouseUp);
+ prev_y = e.clientY;
+ }
+
+ function onMouseDrag(e) {
+
+ var diff = prev_y - e.clientY;
+ _this.setValue(_this.getValue() + diff * _this.__impliedStep);
+
+ prev_y = e.clientY;
+
+ }
+
+ function onMouseUp() {
+ dom.unbind(window, 'mousemove', onMouseDrag);
+ dom.unbind(window, 'mouseup', onMouseUp);
+ }
+
+ this.updateDisplay();
+
+ this.domElement.appendChild(this.__input);
+
+ };
+
+ NumberControllerBox.superclass = NumberController;
+
+ common.extend(
+
+ NumberControllerBox.prototype,
+ NumberController.prototype,
+
+ {
+
+ updateDisplay: function() {
+
+ this.__input.value = this.__truncationSuspended ? this.getValue() : roundToDecimal(this.getValue(), this.__precision);
+ return NumberControllerBox.superclass.prototype.updateDisplay.call(this);
+ }
+
+ }
+
+ );
+
+ function roundToDecimal(value, decimals) {
+ var tenTo = Math.pow(10, decimals);
+ return Math.round(value * tenTo) / tenTo;
+ }
+
+ return NumberControllerBox;
+
+ })(dat.controllers.NumberController,
+ dat.dom.dom,
+ dat.utils.common);
+
+
+ dat.controllers.NumberControllerSlider = (function (NumberController, dom, css, common, styleSheet) {
+
+ /**
+ * @class Represents a given property of an object that is a number, contains
+ * a minimum and maximum, and provides a slider element with which to
+ * manipulate it. It should be noted that the slider element is made up of
+ * <div> tags, not the html5
+ * <slider> element.
+ *
+ * @extends dat.controllers.Controller
+ * @extends dat.controllers.NumberController
+ *
+ * @param {Object} object The object to be manipulated
+ * @param {string} property The name of the property to be manipulated
+ * @param {Number} minValue Minimum allowed value
+ * @param {Number} maxValue Maximum allowed value
+ * @param {Number} stepValue Increment by which to change value
+ *
+ * @member dat.controllers
+ */
+ var NumberControllerSlider = function(object, property, min, max, step) {
+
+ NumberControllerSlider.superclass.call(this, object, property, { min: min, max: max, step: step });
+
+ var _this = this;
+
+ this.__background = document.createElement('div');
+ this.__foreground = document.createElement('div');
+
+
+
+ dom.bind(this.__background, 'mousedown', onMouseDown);
+
+ dom.addClass(this.__background, 'slider');
+ dom.addClass(this.__foreground, 'slider-fg');
+
+ function onMouseDown(e) {
+
+ dom.bind(window, 'mousemove', onMouseDrag);
+ dom.bind(window, 'mouseup', onMouseUp);
+
+ onMouseDrag(e);
+ }
+
+ function onMouseDrag(e) {
+
+ e.preventDefault();
+
+ var offset = dom.getOffset(_this.__background);
+ var width = dom.getWidth(_this.__background);
+
+ _this.setValue(
+ map(e.clientX, offset.left, offset.left + width, _this.__min, _this.__max)
+ );
+
+ return false;
+
+ }
+
+ function onMouseUp() {
+ dom.unbind(window, 'mousemove', onMouseDrag);
+ dom.unbind(window, 'mouseup', onMouseUp);
+ if (_this.__onFinishChange) {
+ _this.__onFinishChange.call(_this, _this.getValue());
+ }
+ }
+
+ this.updateDisplay();
+
+ this.__background.appendChild(this.__foreground);
+ this.domElement.appendChild(this.__background);
+
+ };
+
+ NumberControllerSlider.superclass = NumberController;
+
+ /**
+ * Injects default stylesheet for slider elements.
+ */
+ NumberControllerSlider.useDefaultStyles = function() {
+ css.inject(styleSheet);
+ };
+
+ common.extend(
+
+ NumberControllerSlider.prototype,
+ NumberController.prototype,
+
+ {
+
+ updateDisplay: function() {
+ var pct = (this.getValue() - this.__min)/(this.__max - this.__min);
+ this.__foreground.style.width = pct*100+'%';
+ return NumberControllerSlider.superclass.prototype.updateDisplay.call(this);
+ }
+
+ }
+
+
+
+ );
+
+ function map(v, i1, i2, o1, o2) {
+ return o1 + (o2 - o1) * ((v - i1) / (i2 - i1));
+ }
+
+ return NumberControllerSlider;
+
+ })(dat.controllers.NumberController,
+ dat.dom.dom,
+ dat.utils.css,
+ dat.utils.common,
+ ".slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}");
+
+
+ dat.controllers.FunctionController = (function (Controller, dom, common) {
+
+ /**
+ * @class Provides a GUI interface to fire a specified method, a property of an object.
+ *
+ * @extends dat.controllers.Controller
+ *
+ * @param {Object} object The object to be manipulated
+ * @param {string} property The name of the property to be manipulated
+ *
+ * @member dat.controllers
+ */
+ var FunctionController = function(object, property, text) {
+
+ FunctionController.superclass.call(this, object, property);
+
+ var _this = this;
+
+ this.__button = document.createElement('div');
+ this.__button.innerHTML = text === undefined ? 'Fire' : text;
+ dom.bind(this.__button, 'click', function(e) {
+ e.preventDefault();
+ _this.fire();
+ return false;
+ });
+
+ dom.addClass(this.__button, 'button');
+
+ this.domElement.appendChild(this.__button);
+
+
+ };
+
+ FunctionController.superclass = Controller;
+
+ common.extend(
+
+ FunctionController.prototype,
+ Controller.prototype,
+ {
+
+ fire: function() {
+ if (this.__onChange) {
+ this.__onChange.call(this);
+ }
+ if (this.__onFinishChange) {
+ this.__onFinishChange.call(this, this.getValue());
+ }
+ this.getValue().call(this.object);
+ }
+ }
+
+ );
+
+ return FunctionController;
+
+ })(dat.controllers.Controller,
+ dat.dom.dom,
+ dat.utils.common);
+
+
+ dat.controllers.BooleanController = (function (Controller, dom, common) {
+
+ /**
+ * @class Provides a checkbox input to alter the boolean property of an object.
+ * @extends dat.controllers.Controller
+ *
+ * @param {Object} object The object to be manipulated
+ * @param {string} property The name of the property to be manipulated
+ *
+ * @member dat.controllers
+ */
+ var BooleanController = function(object, property) {
+
+ BooleanController.superclass.call(this, object, property);
+
+ var _this = this;
+ this.__prev = this.getValue();
+
+ this.__checkbox = document.createElement('input');
+ this.__checkbox.setAttribute('type', 'checkbox');
+
+
+ dom.bind(this.__checkbox, 'change', onChange, false);
+
+ this.domElement.appendChild(this.__checkbox);
+
+ // Match original value
+ this.updateDisplay();
+
+ function onChange() {
+ _this.setValue(!_this.__prev);
+ }
+
+ };
+
+ BooleanController.superclass = Controller;
+
+ common.extend(
+
+ BooleanController.prototype,
+ Controller.prototype,
+
+ {
+
+ setValue: function(v) {
+ var toReturn = BooleanController.superclass.prototype.setValue.call(this, v);
+ if (this.__onFinishChange) {
+ this.__onFinishChange.call(this, this.getValue());
+ }
+ this.__prev = this.getValue();
+ return toReturn;
+ },
+
+ updateDisplay: function() {
+
+ if (this.getValue() === true) {
+ this.__checkbox.setAttribute('checked', 'checked');
+ this.__checkbox.checked = true;
+ } else {
+ this.__checkbox.checked = false;
+ }
+
+ return BooleanController.superclass.prototype.updateDisplay.call(this);
+
+ }
+
+
+ }
+
+ );
+
+ return BooleanController;
+
+ })(dat.controllers.Controller,
+ dat.dom.dom,
+ dat.utils.common);
+
+
+ dat.color.toString = (function (common) {
+
+ return function(color) {
+
+ if (color.a == 1 || common.isUndefined(color.a)) {
+
+ var s = color.hex.toString(16);
+ while (s.length < 6) {
+ s = '0' + s;
+ }
+
+ return '#' + s;
+
+ } else {
+
+ return 'rgba(' + Math.round(color.r) + ',' + Math.round(color.g) + ',' + Math.round(color.b) + ',' + color.a + ')';
+
+ }
+
+ }
+
+ })(dat.utils.common);
+
+
+ dat.color.interpret = (function (toString, common) {
+
+ var result, toReturn;
+
+ var interpret = function() {
+
+ toReturn = false;
+
+ var original = arguments.length > 1 ? common.toArray(arguments) : arguments[0];
+
+ common.each(INTERPRETATIONS, function(family) {
+
+ if (family.litmus(original)) {
+
+ common.each(family.conversions, function(conversion, conversionName) {
+
+ result = conversion.read(original);
+
+ if (toReturn === false && result !== false) {
+ toReturn = result;
+ result.conversionName = conversionName;
+ result.conversion = conversion;
+ return common.BREAK;
+
+ }
+
+ });
+
+ return common.BREAK;
+
+ }
+
+ });
+
+ return toReturn;
+
+ };
+
+ var INTERPRETATIONS = [
+
+ // Strings
+ {
+
+ litmus: common.isString,
+
+ conversions: {
+
+ THREE_CHAR_HEX: {
+
+ read: function(original) {
+
+ var test = original.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);
+ if (test === null) return false;
+
+ return {
+ space: 'HEX',
+ hex: parseInt(
+ '0x' +
+ test[1].toString() + test[1].toString() +
+ test[2].toString() + test[2].toString() +
+ test[3].toString() + test[3].toString())
+ };
+
+ },
+
+ write: toString
+
+ },
+
+ SIX_CHAR_HEX: {
+
+ read: function(original) {
+
+ var test = original.match(/^#([A-F0-9]{6})$/i);
+ if (test === null) return false;
+
+ return {
+ space: 'HEX',
+ hex: parseInt('0x' + test[1].toString())
+ };
+
+ },
+
+ write: toString
+
+ },
+
+ CSS_RGB: {
+
+ read: function(original) {
+
+ var test = original.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
+ if (test === null) return false;
+
+ return {
+ space: 'RGB',
+ r: parseFloat(test[1]),
+ g: parseFloat(test[2]),
+ b: parseFloat(test[3])
+ };
+
+ },
+
+ write: toString
+
+ },
+
+ CSS_RGBA: {
+
+ read: function(original) {
+
+ var test = original.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);
+ if (test === null) return false;
+
+ return {
+ space: 'RGB',
+ r: parseFloat(test[1]),
+ g: parseFloat(test[2]),
+ b: parseFloat(test[3]),
+ a: parseFloat(test[4])
+ };
+
+ },
+
+ write: toString
+
+ }
+
+ }
+
+ },
+
+ // Numbers
+ {
+
+ litmus: common.isNumber,
+
+ conversions: {
+
+ HEX: {
+ read: function(original) {
+ return {
+ space: 'HEX',
+ hex: original,
+ conversionName: 'HEX'
+ }
+ },
+
+ write: function(color) {
+ return color.hex;
+ }
+ }
+
+ }
+
+ },
+
+ // Arrays
+ {
+
+ litmus: common.isArray,
+
+ conversions: {
+
+ RGB_ARRAY: {
+ read: function(original) {
+ if (original.length != 3) return false;
+ return {
+ space: 'RGB',
+ r: original[0],
+ g: original[1],
+ b: original[2]
+ };
+ },
+
+ write: function(color) {
+ return [color.r, color.g, color.b];
+ }
+
+ },
+
+ RGBA_ARRAY: {
+ read: function(original) {
+ if (original.length != 4) return false;
+ return {
+ space: 'RGB',
+ r: original[0],
+ g: original[1],
+ b: original[2],
+ a: original[3]
+ };
+ },
+
+ write: function(color) {
+ return [color.r, color.g, color.b, color.a];
+ }
+
+ }
+
+ }
+
+ },
+
+ // Objects
+ {
+
+ litmus: common.isObject,
+
+ conversions: {
+
+ RGBA_OBJ: {
+ read: function(original) {
+ if (common.isNumber(original.r) &&
+ common.isNumber(original.g) &&
+ common.isNumber(original.b) &&
+ common.isNumber(original.a)) {
+ return {
+ space: 'RGB',
+ r: original.r,
+ g: original.g,
+ b: original.b,
+ a: original.a
+ }
+ }
+ return false;
+ },
+
+ write: function(color) {
+ return {
+ r: color.r,
+ g: color.g,
+ b: color.b,
+ a: color.a
+ }
+ }
+ },
+
+ RGB_OBJ: {
+ read: function(original) {
+ if (common.isNumber(original.r) &&
+ common.isNumber(original.g) &&
+ common.isNumber(original.b)) {
+ return {
+ space: 'RGB',
+ r: original.r,
+ g: original.g,
+ b: original.b
+ }
+ }
+ return false;
+ },
+
+ write: function(color) {
+ return {
+ r: color.r,
+ g: color.g,
+ b: color.b
+ }
+ }
+ },
+
+ HSVA_OBJ: {
+ read: function(original) {
+ if (common.isNumber(original.h) &&
+ common.isNumber(original.s) &&
+ common.isNumber(original.v) &&
+ common.isNumber(original.a)) {
+ return {
+ space: 'HSV',
+ h: original.h,
+ s: original.s,
+ v: original.v,
+ a: original.a
+ }
+ }
+ return false;
+ },
+
+ write: function(color) {
+ return {
+ h: color.h,
+ s: color.s,
+ v: color.v,
+ a: color.a
+ }
+ }
+ },
+
+ HSV_OBJ: {
+ read: function(original) {
+ if (common.isNumber(original.h) &&
+ common.isNumber(original.s) &&
+ common.isNumber(original.v)) {
+ return {
+ space: 'HSV',
+ h: original.h,
+ s: original.s,
+ v: original.v
+ }
+ }
+ return false;
+ },
+
+ write: function(color) {
+ return {
+ h: color.h,
+ s: color.s,
+ v: color.v
+ }
+ }
+
+ }
+
+ }
+
+ }
+
+
+ ];
+
+ return interpret;
+
+
+ })(dat.color.toString,
+ dat.utils.common);
+
+
+ dat.GUI = dat.gui.GUI = (function (css, saveDialogueContents, styleSheet, controllerFactory, Controller, BooleanController, FunctionController, NumberControllerBox, NumberControllerSlider, OptionController, ColorController, requestAnimationFrame, CenteredDiv, dom, common) {
+
+ css.inject(styleSheet);
+
+ /** Outer-most className for GUI's */
+ var CSS_NAMESPACE = 'dg';
+
+ var HIDE_KEY_CODE = 72;
+
+ /** The only value shared between the JS and SCSS. Use caution. */
+ var CLOSE_BUTTON_HEIGHT = 20;
+
+ var DEFAULT_DEFAULT_PRESET_NAME = 'Default';
+
+ var SUPPORTS_LOCAL_STORAGE = (function() {
+ try {
+ return 'localStorage' in window && window['localStorage'] !== null;
+ } catch (e) {
+ return false;
+ }
+ })();
+
+ var SAVE_DIALOGUE;
+
+ /** Have we yet to create an autoPlace GUI? */
+ var auto_place_virgin = true;
+
+ /** Fixed position div that auto place GUI's go inside */
+ var auto_place_container;
+
+ /** Are we hiding the GUI's ? */
+ var hide = false;
+
+ /** GUI's which should be hidden */
+ var hideable_guis = [];
+
+ /**
+ * A lightweight controller library for JavaScript. It allows you to easily
+ * manipulate variables and fire functions on the fly.
+ * @class
+ *
+ * @member dat.gui
+ *
+ * @param {Object} [params]
+ * @param {String} [params.name] The name of this GUI.
+ * @param {Object} [params.load] JSON object representing the saved state of
+ * this GUI.
+ * @param {Boolean} [params.auto=true]
+ * @param {dat.gui.GUI} [params.parent] The GUI I'm nested in.
+ * @param {Boolean} [params.closed] If true, starts closed
+ */
+ var GUI = function(params) {
+
+ var _this = this;
+
+ /**
+ * Outermost DOM Element
+ * @type DOMElement
+ */
+ this.domElement = document.createElement('div');
+ this.__ul = document.createElement('ul');
+ this.domElement.appendChild(this.__ul);
+
+ dom.addClass(this.domElement, CSS_NAMESPACE);
+
+ /**
+ * Nested GUI's by name
+ * @ignore
+ */
+ this.__folders = {};
+
+ this.__controllers = [];
+
+ /**
+ * List of objects I'm remembering for save, only used in top level GUI
+ * @ignore
+ */
+ this.__rememberedObjects = [];
+
+ /**
+ * Maps the index of remembered objects to a map of controllers, only used
+ * in top level GUI.
+ *
+ * @private
+ * @ignore
+ *
+ * @example
+ * [
+ * {
+ * propertyName: Controller,
+ * anotherPropertyName: Controller
+ * },
+ * {
+ * propertyName: Controller
+ * }
+ * ]
+ */
+ this.__rememberedObjectIndecesToControllers = [];
+
+ this.__listening = [];
+
+ params = params || {};
+
+ // Default parameters
+ params = common.defaults(params, {
+ autoPlace: true,
+ width: GUI.DEFAULT_WIDTH
+ });
+
+ params = common.defaults(params, {
+ resizable: params.autoPlace,
+ hideable: params.autoPlace
+ });
+
+
+ if (!common.isUndefined(params.load)) {
+
+ // Explicit preset
+ if (params.preset) params.load.preset = params.preset;
+
+ } else {
+
+ params.load = { preset: DEFAULT_DEFAULT_PRESET_NAME };
+
+ }
+
+ if (common.isUndefined(params.parent) && params.hideable) {
+ hideable_guis.push(this);
+ }
+
+ // Only root level GUI's are resizable.
+ params.resizable = common.isUndefined(params.parent) && params.resizable;
+
+
+ if (params.autoPlace && common.isUndefined(params.scrollable)) {
+ params.scrollable = true;
+ }
+ // params.scrollable = common.isUndefined(params.parent) && params.scrollable === true;
+
+ // Not part of params because I don't want people passing this in via
+ // constructor. Should be a 'remembered' value.
+ var use_local_storage =
+ SUPPORTS_LOCAL_STORAGE &&
+ localStorage.getItem(getLocalStorageHash(this, 'isLocal')) === 'true';
+
+ Object.defineProperties(this,
+
+ /** @lends dat.gui.GUI.prototype */
+ {
+
+ /**
+ * The parent GUI
+ * @type dat.gui.GUI
+ */
+ parent: {
+ get: function() {
+ return params.parent;
+ }
+ },
+
+ scrollable: {
+ get: function() {
+ return params.scrollable;
+ }
+ },
+
+ /**
+ * Handles GUI's element placement for you
+ * @type Boolean
+ */
+ autoPlace: {
+ get: function() {
+ return params.autoPlace;
+ }
+ },
+
+ /**
+ * The identifier for a set of saved values
+ * @type String
+ */
+ preset: {
+
+ get: function() {
+ if (_this.parent) {
+ return _this.getRoot().preset;
+ } else {
+ return params.load.preset;
+ }
+ },
+
+ set: function(v) {
+ if (_this.parent) {
+ _this.getRoot().preset = v;
+ } else {
+ params.load.preset = v;
+ }
+ setPresetSelectIndex(this);
+ _this.revert();
+ }
+
+ },
+
+ /**
+ * The width of GUI element
+ * @type Number
+ */
+ width: {
+ get: function() {
+ return params.width;
+ },
+ set: function(v) {
+ params.width = v;
+ setWidth(_this, v);
+ }
+ },
+
+ /**
+ * The name of GUI. Used for folders. i.e
+ * a folder's name
+ * @type String
+ */
+ name: {
+ get: function() {
+ return params.name;
+ },
+ set: function(v) {
+ // TODO Check for collisions among sibling folders
+ params.name = v;
+ if (title_row_name) {
+ title_row_name.innerHTML = params.name;
+ }
+ }
+ },
+
+ /**
+ * Whether the GUI is collapsed or not
+ * @type Boolean
+ */
+ closed: {
+ get: function() {
+ return params.closed;
+ },
+ set: function(v) {
+ params.closed = v;
+ if (params.closed) {
+ dom.addClass(_this.__ul, GUI.CLASS_CLOSED);
+ } else {
+ dom.removeClass(_this.__ul, GUI.CLASS_CLOSED);
+ }
+ // For browsers that aren't going to respect the CSS transition,
+ // Lets just check our height against the window height right off
+ // the bat.
+ this.onResize();
+
+ if (_this.__closeButton) {
+ _this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN : GUI.TEXT_CLOSED;
+ }
+ }
+ },
+
+ /**
+ * Contains all presets
+ * @type Object
+ */
+ load: {
+ get: function() {
+ return params.load;
+ }
+ },
+
+ /**
+ * Determines whether or not to use localStorage as the means for
+ * remembering
+ * @type Boolean
+ */
+ useLocalStorage: {
+
+ get: function() {
+ return use_local_storage;
+ },
+ set: function(bool) {
+ if (SUPPORTS_LOCAL_STORAGE) {
+ use_local_storage = bool;
+ if (bool) {
+ dom.bind(window, 'unload', saveToLocalStorage);
+ } else {
+ dom.unbind(window, 'unload', saveToLocalStorage);
+ }
+ localStorage.setItem(getLocalStorageHash(_this, 'isLocal'), bool);
+ }
+ }
+
+ }
+
+ });
+
+ // Are we a root level GUI?
+ if (common.isUndefined(params.parent)) {
+
+ params.closed = false;
+
+ dom.addClass(this.domElement, GUI.CLASS_MAIN);
+ dom.makeSelectable(this.domElement, false);
+
+ // Are we supposed to be loading locally?
+ if (SUPPORTS_LOCAL_STORAGE) {
+
+ if (use_local_storage) {
+
+ _this.useLocalStorage = true;
+
+ var saved_gui = localStorage.getItem(getLocalStorageHash(this, 'gui'));
+
+ if (saved_gui) {
+ params.load = JSON.parse(saved_gui);
+ }
+
+ }
+
+ }
+
+ this.__closeButton = document.createElement('div');
+ this.__closeButton.innerHTML = GUI.TEXT_CLOSED;
+ dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON);
+ this.domElement.appendChild(this.__closeButton);
+
+ dom.bind(this.__closeButton, 'click', function() {
+
+ _this.closed = !_this.closed;
+
+
+ });
+
+
+ // Oh, you're a nested GUI!
+ } else {
+
+ if (params.closed === undefined) {
+ params.closed = true;
+ }
+
+ var title_row_name = document.createTextNode(params.name);
+ dom.addClass(title_row_name, 'controller-name');
+
+ var title_row = addRow(_this, title_row_name);
+
+ var on_click_title = function(e) {
+ e.preventDefault();
+ _this.closed = !_this.closed;
+ return false;
+ };
+
+ dom.addClass(this.__ul, GUI.CLASS_CLOSED);
+
+ dom.addClass(title_row, 'title');
+ dom.bind(title_row, 'click', on_click_title);
+
+ if (!params.closed) {
+ this.closed = false;
+ }
+
+ }
+
+ if (params.autoPlace) {
+
+ if (common.isUndefined(params.parent)) {
+
+ if (auto_place_virgin) {
+ auto_place_container = document.createElement('div');
+ dom.addClass(auto_place_container, CSS_NAMESPACE);
+ dom.addClass(auto_place_container, GUI.CLASS_AUTO_PLACE_CONTAINER);
+ document.body.appendChild(auto_place_container);
+ auto_place_virgin = false;
+ }
+
+ // Put it in the dom for you.
+ auto_place_container.appendChild(this.domElement);
+
+ // Apply the auto styles
+ dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE);
+
+ }
+
+
+ // Make it not elastic.
+ if (!this.parent) setWidth(_this, params.width);
+
+ }
+
+ dom.bind(window, 'resize', function() { _this.onResize() });
+ dom.bind(this.__ul, 'webkitTransitionEnd', function() { _this.onResize(); });
+ dom.bind(this.__ul, 'transitionend', function() { _this.onResize() });
+ dom.bind(this.__ul, 'oTransitionEnd', function() { _this.onResize() });
+ this.onResize();
+
+
+ if (params.resizable) {
+ addResizeHandle(this);
+ }
+
+ function saveToLocalStorage() {
+ localStorage.setItem(getLocalStorageHash(_this, 'gui'), JSON.stringify(_this.getSaveObject()));
+ }
+
+ var root = _this.getRoot();
+ function resetWidth() {
+ var root = _this.getRoot();
+ root.width += 1;
+ common.defer(function() {
+ root.width -= 1;
+ });
+ }
+
+ if (!params.parent) {
+ resetWidth();
+ }
+
+ };
+
+ GUI.toggleHide = function() {
+
+ hide = !hide;
+ common.each(hideable_guis, function(gui) {
+ gui.domElement.style.zIndex = hide ? -999 : 999;
+ gui.domElement.style.opacity = hide ? 0 : 1;
+ });
+ };
+
+ GUI.CLASS_AUTO_PLACE = 'a';
+ GUI.CLASS_AUTO_PLACE_CONTAINER = 'ac';
+ GUI.CLASS_MAIN = 'main';
+ GUI.CLASS_CONTROLLER_ROW = 'cr';
+ GUI.CLASS_TOO_TALL = 'taller-than-window';
+ GUI.CLASS_CLOSED = 'closed';
+ GUI.CLASS_CLOSE_BUTTON = 'close-button';
+ GUI.CLASS_DRAG = 'drag';
+
+ GUI.DEFAULT_WIDTH = 245;
+ GUI.TEXT_CLOSED = 'Close Controls';
+ GUI.TEXT_OPEN = 'Open Controls';
+
+ dom.bind(window, 'keydown', function(e) {
+
+ if (document.activeElement.type !== 'text' &&
+ (e.which === HIDE_KEY_CODE || e.keyCode == HIDE_KEY_CODE)) {
+ GUI.toggleHide();
+ }
+
+ }, false);
+
+ common.extend(
+
+ GUI.prototype,
+
+ /** @lends dat.gui.GUI */
+ {
+
+ /**
+ * @param object
+ * @param property
+ * @returns {dat.controllers.Controller} The new controller that was added.
+ * @instance
+ */
+ add: function(object, property) {
+
+ return add(
+ this,
+ object,
+ property,
+ {
+ factoryArgs: Array.prototype.slice.call(arguments, 2)
+ }
+ );
+
+ },
+
+ /**
+ * @param object
+ * @param property
+ * @returns {dat.controllers.ColorController} The new controller that was added.
+ * @instance
+ */
+ addColor: function(object, property) {
+
+ return add(
+ this,
+ object,
+ property,
+ {
+ color: true
+ }
+ );
+
+ },
+
+ /**
+ * @param controller
+ * @instance
+ */
+ remove: function(controller) {
+
+ // TODO listening?
+ this.__ul.removeChild(controller.__li);
+ this.__controllers.slice(this.__controllers.indexOf(controller), 1);
+ var _this = this;
+ common.defer(function() {
+ _this.onResize();
+ });
+
+ },
+
+ destroy: function() {
+
+ if (this.autoPlace) {
+ auto_place_container.removeChild(this.domElement);
+ }
+
+ },
+
+ /**
+ * @param name
+ * @returns {dat.gui.GUI} The new folder.
+ * @throws {Error} if this GUI already has a folder by the specified
+ * name
+ * @instance
+ */
+ addFolder: function(name) {
+
+ // We have to prevent collisions on names in order to have a key
+ // by which to remember saved values
+ if (this.__folders[name] !== undefined) {
+ throw new Error('You already have a folder in this GUI by the' +
+ ' name "' + name + '"');
+ }
+
+ var new_gui_params = { name: name, parent: this };
+
+ // We need to pass down the autoPlace trait so that we can
+ // attach event listeners to open/close folder actions to
+ // ensure that a scrollbar appears if the window is too short.
+ new_gui_params.autoPlace = this.autoPlace;
+
+ // Do we have saved appearance data for this folder?
+
+ if (this.load && // Anything loaded?
+ this.load.folders && // Was my parent a dead-end?
+ this.load.folders[name]) { // Did daddy remember me?
+
+ // Start me closed if I was closed
+ new_gui_params.closed = this.load.folders[name].closed;
+
+ // Pass down the loaded data
+ new_gui_params.load = this.load.folders[name];
+
+ }
+
+ var gui = new GUI(new_gui_params);
+ this.__folders[name] = gui;
+
+ var li = addRow(this, gui.domElement);
+ dom.addClass(li, 'folder');
+ return gui;
+
+ },
+
+ open: function() {
+ this.closed = false;
+ },
+
+ close: function() {
+ this.closed = true;
+ },
+
+ onResize: function() {
+
+ var root = this.getRoot();
+
+ if (root.scrollable) {
+
+ var top = dom.getOffset(root.__ul).top;
+ var h = 0;
+
+ common.each(root.__ul.childNodes, function(node) {
+ if (! (root.autoPlace && node === root.__save_row))
+ h += dom.getHeight(node);
+ });
+
+ if (window.innerHeight - top - CLOSE_BUTTON_HEIGHT < h) {
+ dom.addClass(root.domElement, GUI.CLASS_TOO_TALL);
+ root.__ul.style.height = window.innerHeight - top - CLOSE_BUTTON_HEIGHT + 'px';
+ } else {
+ dom.removeClass(root.domElement, GUI.CLASS_TOO_TALL);
+ root.__ul.style.height = 'auto';
+ }
+
+ }
+
+ if (root.__resize_handle) {
+ common.defer(function() {
+ root.__resize_handle.style.height = root.__ul.offsetHeight + 'px';
+ });
+ }
+
+ if (root.__closeButton) {
+ root.__closeButton.style.width = root.width + 'px';
+ }
+
+ },
+
+ /**
+ * Mark objects for saving. The order of these objects cannot change as
+ * the GUI grows. When remembering new objects, append them to the end
+ * of the list.
+ *
+ * @param {Object...} objects
+ * @throws {Error} if not called on a top level GUI.
+ * @instance
+ */
+ remember: function() {
+
+ if (common.isUndefined(SAVE_DIALOGUE)) {
+ SAVE_DIALOGUE = new CenteredDiv();
+ SAVE_DIALOGUE.domElement.innerHTML = saveDialogueContents;
+ }
+
+ if (this.parent) {
+ throw new Error("You can only call remember on a top level GUI.");
+ }
+
+ var _this = this;
+
+ common.each(Array.prototype.slice.call(arguments), function(object) {
+ if (_this.__rememberedObjects.length == 0) {
+ addSaveMenu(_this);
+ }
+ if (_this.__rememberedObjects.indexOf(object) == -1) {
+ _this.__rememberedObjects.push(object);
+ }
+ });
+
+ if (this.autoPlace) {
+ // Set save row width
+ setWidth(this, this.width);
+ }
+
+ },
+
+ /**
+ * @returns {dat.gui.GUI} the topmost parent GUI of a nested GUI.
+ * @instance
+ */
+ getRoot: function() {
+ var gui = this;
+ while (gui.parent) {
+ gui = gui.parent;
+ }
+ return gui;
+ },
+
+ /**
+ * @returns {Object} a JSON object representing the current state of
+ * this GUI as well as its remembered properties.
+ * @instance
+ */
+ getSaveObject: function() {
+
+ var toReturn = this.load;
+
+ toReturn.closed = this.closed;
+
+ // Am I remembering any values?
+ if (this.__rememberedObjects.length > 0) {
+
+ toReturn.preset = this.preset;
+
+ if (!toReturn.remembered) {
+ toReturn.remembered = {};
+ }
+
+ toReturn.remembered[this.preset] = getCurrentPreset(this);
+
+ }
+
+ toReturn.folders = {};
+ common.each(this.__folders, function(element, key) {
+ toReturn.folders[key] = element.getSaveObject();
+ });
+
+ return toReturn;
+
+ },
+
+ save: function() {
+
+ if (!this.load.remembered) {
+ this.load.remembered = {};
+ }
+
+ this.load.remembered[this.preset] = getCurrentPreset(this);
+ markPresetModified(this, false);
+
+ },
+
+ saveAs: function(presetName) {
+
+ if (!this.load.remembered) {
+
+ // Retain default values upon first save
+ this.load.remembered = {};
+ this.load.remembered[DEFAULT_DEFAULT_PRESET_NAME] = getCurrentPreset(this, true);
+
+ }
+
+ this.load.remembered[presetName] = getCurrentPreset(this);
+ this.preset = presetName;
+ addPresetOption(this, presetName, true);
+
+ },
+
+ revert: function(gui) {
+
+ common.each(this.__controllers, function(controller) {
+ // Make revert work on Default.
+ if (!this.getRoot().load.remembered) {
+ controller.setValue(controller.initialValue);
+ } else {
+ recallSavedValue(gui || this.getRoot(), controller);
+ }
+ }, this);
+
+ common.each(this.__folders, function(folder) {
+ folder.revert(folder);
+ });
+
+ if (!gui) {
+ markPresetModified(this.getRoot(), false);
+ }
+
+
+ },
+
+ listen: function(controller) {
+
+ var init = this.__listening.length == 0;
+ this.__listening.push(controller);
+ if (init) updateDisplays(this.__listening);
+
+ }
+
+ }
+
+ );
+
+ function add(gui, object, property, params) {
+
+ if (object[property] === undefined) {
+ throw new Error("Object " + object + " has no property \"" + property + "\"");
+ }
+
+ var controller;
+
+ if (params.color) {
+
+ controller = new ColorController(object, property);
+
+ } else {
+
+ var factoryArgs = [object,property].concat(params.factoryArgs);
+ controller = controllerFactory.apply(gui, factoryArgs);
+
+ }
+
+ if (params.before instanceof Controller) {
+ params.before = params.before.__li;
+ }
+
+ recallSavedValue(gui, controller);
+
+ dom.addClass(controller.domElement, 'c');
+
+ var name = document.createElement('span');
+ dom.addClass(name, 'property-name');
+ name.innerHTML = controller.property;
+
+ var container = document.createElement('div');
+ container.appendChild(name);
+ container.appendChild(controller.domElement);
+
+ var li = addRow(gui, container, params.before);
+
+ dom.addClass(li, GUI.CLASS_CONTROLLER_ROW);
+ dom.addClass(li, typeof controller.getValue());
+
+ augmentController(gui, li, controller);
+
+ gui.__controllers.push(controller);
+
+ return controller;
+
+ }
+
+ /**
+ * Add a row to the end of the GUI or before another row.
+ *
+ * @param gui
+ * @param [dom] If specified, inserts the dom content in the new row
+ * @param [liBefore] If specified, places the new row before another row
+ */
+ function addRow(gui, dom, liBefore) {
+ var li = document.createElement('li');
+ if (dom) li.appendChild(dom);
+ if (liBefore) {
+ gui.__ul.insertBefore(li, params.before);
+ } else {
+ gui.__ul.appendChild(li);
+ }
+ gui.onResize();
+ return li;
+ }
+
+ function augmentController(gui, li, controller) {
+
+ controller.__li = li;
+ controller.__gui = gui;
+
+ common.extend(controller, {
+
+ options: function(options) {
+
+ if (arguments.length > 1) {
+ controller.remove();
+
+ return add(
+ gui,
+ controller.object,
+ controller.property,
+ {
+ before: controller.__li.nextElementSibling,
+ factoryArgs: [common.toArray(arguments)]
+ }
+ );
+
+ }
+
+ if (common.isArray(options) || common.isObject(options)) {
+ controller.remove();
+
+ return add(
+ gui,
+ controller.object,
+ controller.property,
+ {
+ before: controller.__li.nextElementSibling,
+ factoryArgs: [options]
+ }
+ );
+
+ }
+
+ },
+
+ name: function(v) {
+ controller.__li.firstElementChild.firstElementChild.innerHTML = v;
+ return controller;
+ },
+
+ listen: function() {
+ controller.__gui.listen(controller);
+ return controller;
+ },
+
+ remove: function() {
+ controller.__gui.remove(controller);
+ return controller;
+ }
+
+ });
+
+ // All sliders should be accompanied by a box.
+ if (controller instanceof NumberControllerSlider) {
+
+ var box = new NumberControllerBox(controller.object, controller.property,
+ { min: controller.__min, max: controller.__max, step: controller.__step });
+
+ common.each(['updateDisplay', 'onChange', 'onFinishChange'], function(method) {
+ var pc = controller[method];
+ var pb = box[method];
+ controller[method] = box[method] = function() {
+ var args = Array.prototype.slice.call(arguments);
+ pc.apply(controller, args);
+ return pb.apply(box, args);
+ }
+ });
+
+ dom.addClass(li, 'has-slider');
+ controller.domElement.insertBefore(box.domElement, controller.domElement.firstElementChild);
+
+ }
+ else if (controller instanceof NumberControllerBox) {
+
+ var r = function(returned) {
+
+ // Have we defined both boundaries?
+ if (common.isNumber(controller.__min) && common.isNumber(controller.__max)) {
+
+ // Well, then lets just replace this with a slider.
+ controller.remove();
+ return add(
+ gui,
+ controller.object,
+ controller.property,
+ {
+ before: controller.__li.nextElementSibling,
+ factoryArgs: [controller.__min, controller.__max, controller.__step]
+ });
+
+ }
+
+ return returned;
+
+ };
+
+ controller.min = common.compose(r, controller.min);
+ controller.max = common.compose(r, controller.max);
+
+ }
+ else if (controller instanceof BooleanController) {
+
+ dom.bind(li, 'click', function() {
+ dom.fakeEvent(controller.__checkbox, 'click');
+ });
+
+ dom.bind(controller.__checkbox, 'click', function(e) {
+ e.stopPropagation(); // Prevents double-toggle
+ })
+
+ }
+ else if (controller instanceof FunctionController) {
+
+ dom.bind(li, 'click', function() {
+ dom.fakeEvent(controller.__button, 'click');
+ });
+
+ dom.bind(li, 'mouseover', function() {
+ dom.addClass(controller.__button, 'hover');
+ });
+
+ dom.bind(li, 'mouseout', function() {
+ dom.removeClass(controller.__button, 'hover');
+ });
+
+ }
+ else if (controller instanceof ColorController) {
+
+ dom.addClass(li, 'color');
+ controller.updateDisplay = common.compose(function(r) {
+ li.style.borderLeftColor = controller.__color.toString();
+ return r;
+ }, controller.updateDisplay);
+
+ controller.updateDisplay();
+
+ }
+
+ controller.setValue = common.compose(function(r) {
+ if (gui.getRoot().__preset_select && controller.isModified()) {
+ markPresetModified(gui.getRoot(), true);
+ }
+ return r;
+ }, controller.setValue);
+
+ }
+
+ function recallSavedValue(gui, controller) {
+
+ // Find the topmost GUI, that's where remembered objects live.
+ var root = gui.getRoot();
+
+ // Does the object we're controlling match anything we've been told to
+ // remember?
+ var matched_index = root.__rememberedObjects.indexOf(controller.object);
+
+ // Why yes, it does!
+ if (matched_index != -1) {
+
+ // Let me fetch a map of controllers for thcommon.isObject.
+ var controller_map =
+ root.__rememberedObjectIndecesToControllers[matched_index];
+
+ // Ohp, I believe this is the first controller we've created for this
+ // object. Lets make the map fresh.
+ if (controller_map === undefined) {
+ controller_map = {};
+ root.__rememberedObjectIndecesToControllers[matched_index] =
+ controller_map;
+ }
+
+ // Keep track of this controller
+ controller_map[controller.property] = controller;
+
+ // Okay, now have we saved any values for this controller?
+ if (root.load && root.load.remembered) {
+
+ var preset_map = root.load.remembered;
+
+ // Which preset are we trying to load?
+ var preset;
+
+ if (preset_map[gui.preset]) {
+
+ preset = preset_map[gui.preset];
+
+ } else if (preset_map[DEFAULT_DEFAULT_PRESET_NAME]) {
+
+ // Uhh, you can have the default instead?
+ preset = preset_map[DEFAULT_DEFAULT_PRESET_NAME];
+
+ } else {
+
+ // Nada.
+
+ return;
+
+ }
+
+
+ // Did the loaded object remember thcommon.isObject?
+ if (preset[matched_index] &&
+
+ // Did we remember this particular property?
+ preset[matched_index][controller.property] !== undefined) {
+
+ // We did remember something for this guy ...
+ var value = preset[matched_index][controller.property];
+
+ // And that's what it is.
+ controller.initialValue = value;
+ controller.setValue(value);
+
+ }
+
+ }
+
+ }
+
+ }
+
+ function getLocalStorageHash(gui, key) {
+ // TODO how does this deal with multiple GUI's?
+ return document.location.href + '.' + key;
+
+ }
+
+ function addSaveMenu(gui) {
+
+ var div = gui.__save_row = document.createElement('li');
+
+ dom.addClass(gui.domElement, 'has-save');
+
+ gui.__ul.insertBefore(div, gui.__ul.firstChild);
+
+ dom.addClass(div, 'save-row');
+
+ var gears = document.createElement('span');
+ gears.innerHTML = ' ';
+ dom.addClass(gears, 'button gears');
+
+ // TODO replace with FunctionController
+ var button = document.createElement('span');
+ button.innerHTML = 'Save';
+ dom.addClass(button, 'button');
+ dom.addClass(button, 'save');
+
+ var button2 = document.createElement('span');
+ button2.innerHTML = 'New';
+ dom.addClass(button2, 'button');
+ dom.addClass(button2, 'save-as');
+
+ var button3 = document.createElement('span');
+ button3.innerHTML = 'Revert';
+ dom.addClass(button3, 'button');
+ dom.addClass(button3, 'revert');
+
+ var select = gui.__preset_select = document.createElement('select');
+
+ if (gui.load && gui.load.remembered) {
+
+ common.each(gui.load.remembered, function(value, key) {
+ addPresetOption(gui, key, key == gui.preset);
+ });
+
+ } else {
+ addPresetOption(gui, DEFAULT_DEFAULT_PRESET_NAME, false);
+ }
+
+ dom.bind(select, 'change', function() {
+
+
+ for (var index = 0; index < gui.__preset_select.length; index++) {
+ gui.__preset_select[index].innerHTML = gui.__preset_select[index].value;
+ }
+
+ gui.preset = this.value;
+
+ });
+
+ div.appendChild(select);
+ div.appendChild(gears);
+ div.appendChild(button);
+ div.appendChild(button2);
+ div.appendChild(button3);
+
+ if (SUPPORTS_LOCAL_STORAGE) {
+
+ var saveLocally = document.getElementById('dg-save-locally');
+ var explain = document.getElementById('dg-local-explain');
+
+ saveLocally.style.display = 'block';
+
+ var localStorageCheckBox = document.getElementById('dg-local-storage');
+
+ if (localStorage.getItem(getLocalStorageHash(gui, 'isLocal')) === 'true') {
+ localStorageCheckBox.setAttribute('checked', 'checked');
+ }
+
+ function showHideExplain() {
+ explain.style.display = gui.useLocalStorage ? 'block' : 'none';
+ }
+
+ showHideExplain();
+
+ // TODO: Use a boolean controller, fool!
+ dom.bind(localStorageCheckBox, 'change', function() {
+ gui.useLocalStorage = !gui.useLocalStorage;
+ showHideExplain();
+ });
+
+ }
+
+ var newConstructorTextArea = document.getElementById('dg-new-constructor');
+
+ dom.bind(newConstructorTextArea, 'keydown', function(e) {
+ if (e.metaKey && (e.which === 67 || e.keyCode == 67)) {
+ SAVE_DIALOGUE.hide();
+ }
+ });
+
+ dom.bind(gears, 'click', function() {
+ newConstructorTextArea.innerHTML = JSON.stringify(gui.getSaveObject(), undefined, 2);
+ SAVE_DIALOGUE.show();
+ newConstructorTextArea.focus();
+ newConstructorTextArea.select();
+ });
+
+ dom.bind(button, 'click', function() {
+ gui.save();
+ });
+
+ dom.bind(button2, 'click', function() {
+ var presetName = prompt('Enter a new preset name.');
+ if (presetName) gui.saveAs(presetName);
+ });
+
+ dom.bind(button3, 'click', function() {
+ gui.revert();
+ });
+
+ // div.appendChild(button2);
+
+ }
+
+ function addResizeHandle(gui) {
+
+ gui.__resize_handle = document.createElement('div');
+
+ common.extend(gui.__resize_handle.style, {
+
+ width: '6px',
+ marginLeft: '-3px',
+ height: '200px',
+ cursor: 'ew-resize',
+ position: 'absolute'
+ // border: '1px solid blue'
+
+ });
+
+ var pmouseX;
+
+ dom.bind(gui.__resize_handle, 'mousedown', dragStart);
+ dom.bind(gui.__closeButton, 'mousedown', dragStart);
+
+ gui.domElement.insertBefore(gui.__resize_handle, gui.domElement.firstElementChild);
+
+ function dragStart(e) {
+
+ e.preventDefault();
+
+ pmouseX = e.clientX;
+
+ dom.addClass(gui.__closeButton, GUI.CLASS_DRAG);
+ dom.bind(window, 'mousemove', drag);
+ dom.bind(window, 'mouseup', dragStop);
+
+ return false;
+
+ }
+
+ function drag(e) {
+
+ e.preventDefault();
+
+ gui.width += pmouseX - e.clientX;
+ gui.onResize();
+ pmouseX = e.clientX;
+
+ return false;
+
+ }
+
+ function dragStop() {
+
+ dom.removeClass(gui.__closeButton, GUI.CLASS_DRAG);
+ dom.unbind(window, 'mousemove', drag);
+ dom.unbind(window, 'mouseup', dragStop);
+
+ }
+
+ }
+
+ function setWidth(gui, w) {
+ gui.domElement.style.width = w + 'px';
+ // Auto placed save-rows are position fixed, so we have to
+ // set the width manually if we want it to bleed to the edge
+ if (gui.__save_row && gui.autoPlace) {
+ gui.__save_row.style.width = w + 'px';
+ }if (gui.__closeButton) {
+ gui.__closeButton.style.width = w + 'px';
+ }
+ }
+
+ function getCurrentPreset(gui, useInitialValues) {
+
+ var toReturn = {};
+
+ // For each object I'm remembering
+ common.each(gui.__rememberedObjects, function(val, index) {
+
+ var saved_values = {};
+
+ // The controllers I've made for thcommon.isObject by property
+ var controller_map =
+ gui.__rememberedObjectIndecesToControllers[index];
+
+ // Remember each value for each property
+ common.each(controller_map, function(controller, property) {
+ saved_values[property] = useInitialValues ? controller.initialValue : controller.getValue();
+ });
+
+ // Save the values for thcommon.isObject
+ toReturn[index] = saved_values;
+
+ });
+
+ return toReturn;
+
+ }
+
+ function addPresetOption(gui, name, setSelected) {
+ var opt = document.createElement('option');
+ opt.innerHTML = name;
+ opt.value = name;
+ gui.__preset_select.appendChild(opt);
+ if (setSelected) {
+ gui.__preset_select.selectedIndex = gui.__preset_select.length - 1;
+ }
+ }
+
+ function setPresetSelectIndex(gui) {
+ for (var index = 0; index < gui.__preset_select.length; index++) {
+ if (gui.__preset_select[index].value == gui.preset) {
+ gui.__preset_select.selectedIndex = index;
+ }
+ }
+ }
+
+ function markPresetModified(gui, modified) {
+ var opt = gui.__preset_select[gui.__preset_select.selectedIndex];
+ // console.log('mark', modified, opt);
+ if (modified) {
+ opt.innerHTML = opt.value + "*";
+ } else {
+ opt.innerHTML = opt.value;
+ }
+ }
+
+ function updateDisplays(controllerArray) {
+
+
+ if (controllerArray.length != 0) {
+
+ requestAnimationFrame(function() {
+ updateDisplays(controllerArray);
+ });
+
+ }
+
+ common.each(controllerArray, function(c) {
+ c.updateDisplay();
+ });
+
+ }
+
+ return GUI;
+
+ })(dat.utils.css,
+ "
\n\n Here's the new load parameter for your GUI's constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI's constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n