├── favicon.ico ├── img └── screen.jpg ├── scenes ├── test_scene.hipnc └── test_scene_htoa_rs.hipnc ├── test_imgs ├── test_render.png ├── colors_vertical.png └── colors_horizontal.png ├── web ├── index.html ├── shaders.js ├── components.js └── dat.gui.min.js ├── LICENSE ├── toolbar └── hmd_preview.shelf ├── .gitignore ├── README.md └── scripts └── python └── hou2vr.py /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/houdini2vr/HEAD/favicon.ico -------------------------------------------------------------------------------- /img/screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/houdini2vr/HEAD/img/screen.jpg -------------------------------------------------------------------------------- /scenes/test_scene.hipnc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/houdini2vr/HEAD/scenes/test_scene.hipnc -------------------------------------------------------------------------------- /test_imgs/test_render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/houdini2vr/HEAD/test_imgs/test_render.png -------------------------------------------------------------------------------- /test_imgs/colors_vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/houdini2vr/HEAD/test_imgs/colors_vertical.png -------------------------------------------------------------------------------- /scenes/test_scene_htoa_rs.hipnc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/houdini2vr/HEAD/scenes/test_scene_htoa_rs.hipnc -------------------------------------------------------------------------------- /test_imgs/colors_horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jtomori/houdini2vr/HEAD/test_imgs/colors_horizontal.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Houdini 2 VR 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Juraj Tomori 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /toolbar/hmd_preview.shelf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #custom 2 | backup 3 | tmp 4 | .vscode 5 | web/node_modules 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Houdini 2 VR 2 | ## Preview your Houdini mono/stereo VR renders in VR headset 3 | This tool takes displayed image plane in Render View pane and displays it in your VR headset. 4 | 5 | Note that this tool can be used also without a headset. In this case it will be displayed as a panorama image. 6 | 7 | Check *Houdini 2 VR* in action: 8 | [![Screencast](img/screen.jpg)](https://youtu.be/B5eOd3h8jAc) 9 | 10 | And you can read [blog post](https://jurajtomori.wordpress.com/2019/03/23/houdini-2-vr/) introducing this tool. 11 | 12 |
13 | 14 | ### Setup 15 | * Add this repository into **HOUDINI_PATH** environment variable (e.g. in *houdini.env* file) 16 | ``` 17 | HOUDINI_PATH = &;/home/juraj/Work/houdini_hmd_preview/ 18 | ``` 19 | * Show **Houdini 2 VR** shelf 20 | 21 |
22 | 23 | ### Shelf tools 24 | * **Save as PNG** - saves rendered image as *$HIP/tmp/tmp.png* 25 | * **Start auto save** - starts auto-saving thread at a specified time interval *(specified in `hou2vr.py`)* 26 | * **Show in browser** - opens a web browser with rendering image 27 | * **Stop auto save** - stops auto-saving thread 28 | 29 |
30 | 31 | ### Supported renderers, devices 32 | This tool can support any renderer plugin which can render into Houdini's **Render View** pane. It has been tested with the following renderers: 33 | * Mantra 34 | * Arnold 35 | * Redshift 36 | * *Note: you need to make sure that **Linked ROP** parameter on your **Redshift_IPR** node is pointing to the corresponding **Redshift** node.* 37 | 38 | You can read about supported browsers and HMDs [here](https://webvr.rocks/). 39 | 40 | If your renderer is missing then let me know, it can be easily added. 41 | 42 |
43 | 44 | ### Future work 45 | * Send values from Houdini directly to browser (to skip image saving/loading times) and support HDR images 46 | * using websockets? 47 | * would solve problem with loading partial frames (unfinished saving) 48 | * Different way of reloading image texture 49 | * to address short black-outs between page reloads 50 | * re-assigning a texture? (and kill its cache first) 51 | * Move saving at Houdini side to a separate process - to minimze lags in Houdini UI 52 | * using shared memory? 53 | 54 |
55 | 56 | ### Limitations & Bugs 57 | * With automatic refreshing there are short black-outs between page reloads 58 | * Sometimes browser loads image which is not fully written to drive, therefore it displays only a part of it 59 | * HDR renderings are currently clamped because of transfering them as a PNG file 60 | 61 |
62 | 63 | ### Contributing 64 | Feel free to contribute to this project by creating pull requests or by [buying me a beer :)](https://www.paypal.me/jurajtomori) 65 | -------------------------------------------------------------------------------- /web/shaders.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerShader("uv-show", { 2 | /* 3 | A simple debug shader showing UV coordinates as colors 4 | */ 5 | raw: false, 6 | vertexShader: ` 7 | varying vec2 vUv; 8 | 9 | void main() { 10 | vUv = uv; 11 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 12 | } 13 | `, 14 | fragmentShader: ` 15 | precision mediump float; 16 | varying vec2 vUv; 17 | 18 | void main () { 19 | gl_FragColor = vec4(vUv.x, vUv.y, 0.0, 1.0); 20 | } 21 | ` 22 | }); 23 | 24 | AFRAME.registerShader("vr-map", { 25 | /* 26 | Shader to display offline VR renders which contain both eyes in one texture (stereo: 1), or only one eye (stereo: 0) 27 | */ 28 | schema: { 29 | src: {type: "map", is: "uniform"}, 30 | stereo: {type: "int", is: "uniform"}, // 0: mono, 1: stereo 31 | layout: {type: "int", is: "uniform"}, // 0: horizontal (left-right), 1: vertical (top-bottom) 32 | eye: {type: "int", is: "uniform"}, // 0: left eye, 1: right eye 33 | gamma_correct: {type: "int", is: "uniform", default: 1}, // 0: disabled, 1: enabled 34 | exposure: {type: "number", is: "uniform"} 35 | }, 36 | raw: true, 37 | vertexShader: ` 38 | attribute vec2 uv; 39 | attribute vec3 position; 40 | 41 | uniform mat4 projectionMatrix; 42 | uniform mat4 modelViewMatrix; 43 | uniform int stereo; 44 | uniform int layout; 45 | uniform int eye; 46 | 47 | varying vec2 vUv; 48 | 49 | void main() { 50 | float offset = 0.0; 51 | if (eye == 0) 52 | offset = 0.0; 53 | else if (eye == 1) 54 | offset = 0.5; 55 | 56 | if (stereo == 1) 57 | { 58 | if (layout == 0) 59 | { 60 | vUv = vec2(uv.x/2.0+offset, uv.y); 61 | } 62 | else if (layout == 1) 63 | { 64 | vUv = vec2(uv.x, uv.y/2.0+(0.5-offset)); 65 | } 66 | } 67 | if (stereo == 0) 68 | vUv = vec2(uv.x, uv.y); 69 | 70 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 71 | } 72 | `, 73 | fragmentShader: ` 74 | precision highp float; 75 | 76 | uniform sampler2D src; 77 | uniform int gamma_correct; 78 | uniform float exposure; 79 | 80 | varying vec2 vUv; 81 | 82 | void main () { 83 | vec2 uv = vUv; 84 | vec4 texColor = texture2D(src, uv); 85 | vec3 texColorCorrected = texColor.xyz; 86 | 87 | if (gamma_correct == 1) 88 | texColorCorrected = pow(texColor.xyz, vec3(1.0/2.2)); 89 | 90 | texColorCorrected *= pow(vec3(2.0), vec3(exposure)); 91 | 92 | gl_FragColor = vec4(texColorCorrected, texColor.w); 93 | } 94 | ` 95 | }); 96 | -------------------------------------------------------------------------------- /scripts/python/hou2vr.py: -------------------------------------------------------------------------------- 1 | """ 2 | Preview your Houdini VR renders in HMD 3 | """ 4 | 5 | import os 6 | import hou 7 | import time 8 | import base64 9 | import inspect 10 | import logging 11 | import urllib2 12 | import webbrowser 13 | import numpy as np 14 | import SocketServer 15 | from PIL import Image 16 | import SimpleHTTPServer 17 | from pathlib2 import Path 18 | from threading import Thread 19 | 20 | logging.basicConfig(level=logging.INFO) 21 | log = logging.getLogger(__name__) 22 | 23 | # CONFIG 24 | web_server_address = "127.0.0.1" 25 | web_server_port = 8000 26 | auto_save_interval = 5 27 | 28 | def getCameraInfo(viewer): 29 | """ 30 | Returns a dictionary with information about camera rendering in passed IPR: 31 | layout: integer representing camera layout 32 | 0: horizontal (left - right) 33 | 1: vertical (top - bottom) 34 | stereo: integer representing whether rendering is stereo (1 - two eyes), or mono (0 - one eye) 35 | 36 | default values are 0 - if ROP node was not implemented (parm names change between renderers) 37 | """ 38 | 39 | out_dict = { 40 | "layout" : 0, 41 | "stereo" : 0 42 | } 43 | 44 | rop_node = viewer.ropNode() 45 | 46 | if rop_node.type().name() == "ifd" or rop_node.type().name() == "arnold": # Mantra / Arnold - they use the same camera and parameters 47 | cam_node = rop_node.parm("camera").evalAsNode() 48 | layout = cam_node.parm("vrlayout").eval() 49 | 50 | out_dict["layout"] = layout 51 | if layout < 2: 52 | out_dict["stereo"] = 1 53 | 54 | elif rop_node.type().name() == "Redshift_ROP" or rop_node.type().name() == "Redshift_IPR": # Redshift 55 | if rop_node.parm("linked_rop").eval() == "": 56 | log.warning("Redshift_IPR node has empty Linked ROP parameter.") 57 | return out_dict 58 | 59 | cam_node = rop_node.parm("linked_rop").evalAsNode().parm("RS_renderCamera").evalAsNode() 60 | 61 | layout = cam_node.parm("RS_campro_stereoMode").eval() 62 | 63 | out_dict["layout"] = layout 64 | if layout < 2: 65 | out_dict["stereo"] = 1 66 | 67 | else: # Not implemented warning 68 | log.warning("ROP node is not implemented") 69 | 70 | return out_dict 71 | 72 | def getImageData(): 73 | """ 74 | Returns a dictionary with information about displayed image 75 | 76 | it contains: 77 | img_plane - string with name of displayed image plane 78 | res - tuple of two ints for X and Y image resolution 79 | pixels - tuple of pixel tuples (4 floats, RGBA), in row-major order starting at the bottom left corner of the image 80 | layout - an integer representing camera layout 81 | 0: horizontal (left - right) 82 | 1: vertical (top - bottom) 83 | stereo - an integer whether rendering is stereo (1) or not (0) 84 | viewer - an reference pointing to hou.paneTabType.IPRViewer pane tab object 85 | """ 86 | viewer = hou.ui.paneTabOfType(hou.paneTabType.IPRViewer) 87 | img_data = {} 88 | 89 | if not viewer: 90 | log.warning("No Render View pane found") 91 | return None 92 | else: 93 | if viewer.planes() == (): 94 | log.warning("No image planes found, is your scene rendering?") 95 | return None 96 | else: 97 | cam_info = getCameraInfo(viewer) 98 | img_plane = viewer.displayedPlane() 99 | 100 | img_data["img_plane"] = img_plane 101 | img_data["res"] = viewer.imageResolution() 102 | img_data["pixels"] = viewer.pixels(img_plane) 103 | img_data["layout"] = cam_info["layout"] 104 | img_data["stereo"] = cam_info["stereo"] 105 | img_data["viewer"] = viewer 106 | 107 | return img_data 108 | 109 | def updateImageData(img_data): 110 | """ 111 | Updates value of "pixels" in img_data dictionary, it is meant to be run after getImageData() call, which will populate needed information in the img_data dict. 112 | """ 113 | img_data["pixels"] = img_data["viewer"].pixels( img_data["img_plane"] ) 114 | 115 | def plotImage(img_data): 116 | """ 117 | Plots gama-corrected image using matplotlib 118 | Depends on matplotlib 119 | """ 120 | import matplotlib 121 | matplotlib.use("Qt5Agg") 122 | import matplotlib.pyplot as plt 123 | 124 | pixels = img_data["pixels"] 125 | res = img_data["res"] 126 | 127 | pixels = np.array(pixels) 128 | pixels = pixels.reshape(res[1], res[0], 4) 129 | pixels = np.flipud(pixels) 130 | 131 | pixels[:,:,:3] = pixels[:,:,:3]**(1/2.2) 132 | 133 | fig2d = plt.figure() 134 | plot = fig2d.add_subplot(111) 135 | imgplot = plt.imshow(pixels) 136 | plot.set_title(img_data["img_plane"]) 137 | 138 | plt.show() 139 | 140 | return 141 | 142 | def saveImageAsPng(img_data, path=None): 143 | """ 144 | Saves incoming data as PNG in specified path, or in $HIP/tmp/tmp.png if not specified 145 | img_data is a dict which is returned from getImageData() 146 | path is a Path object 147 | """ 148 | if not img_data: 149 | return 150 | 151 | if not path: 152 | folder_path = Path(hou.getenv("HIP"), "tmp") 153 | if not folder_path.exists(): 154 | os.makedirs(str(folder_path)) 155 | img_path = folder_path / "tmp.png" 156 | else: 157 | img_path = path 158 | 159 | pixels = img_data["pixels"] 160 | res = img_data["res"] 161 | 162 | pixels = np.array(pixels) 163 | pixels = pixels.reshape(res[1], res[0], 4) 164 | pixels = np.flipud(pixels) 165 | 166 | pixels *= 255 167 | pixels = np.clip(pixels, 0, 255) 168 | 169 | png_img = Image.fromarray(pixels.astype(np.uint8)) 170 | png_img.save(str(img_path)) 171 | log.info("Saving image into {}".format(str(img_path))) 172 | 173 | def isServerRunning(): 174 | """ 175 | Checks whether server is running or not 176 | returns True if connection can be made or False if not 177 | """ 178 | try: 179 | os.environ["no_proxy"] = "127.0.0.1,localhost" # fixes a problem when behing a proxy 180 | urllib2.urlopen("http://{}:{}".format(web_server_address, web_server_port), timeout=1) 181 | return True 182 | except urllib2.URLError: 183 | return False 184 | 185 | def startServer(): 186 | """ 187 | Starts a simple httpserver at specified address and port (specified through global vars) 188 | """ 189 | handler = SimpleHTTPServer.SimpleHTTPRequestHandler 190 | httpd = SocketServer.TCPServer((web_server_address, web_server_port), handler) 191 | log.info("Starting web server at port {}".format(web_server_port)) 192 | httpd.serve_forever() 193 | 194 | def autoSaveThread(): 195 | """ 196 | Thread responsible for fetching pixels from IPR and saving them out 197 | Checks hou.session.hou2vr_autoSave to see if it should keep on running 198 | """ 199 | 200 | img_data = getImageData() 201 | img_path = getImgOutPath() 202 | run = True 203 | 204 | if not img_data: 205 | return 206 | 207 | while run: 208 | old_pixels = img_data["pixels"] 209 | updateImageData(img_data) 210 | 211 | if img_data["pixels"] != old_pixels: 212 | saveImageAsPng(img_data=img_data, path=img_path) 213 | else: 214 | log.info("Not saving, render hasn't changed") 215 | 216 | try: 217 | run = hou.session.hou2vr_autoSave 218 | except AttributeError: 219 | run = False 220 | 221 | time.sleep(auto_save_interval) 222 | 223 | def startAutoSave(): 224 | """ 225 | Starts a separate thread which will be automatically saving out rendering image 226 | It also sets global hou.session.hou2vr_autoSave variable to True, which thread is checking against to know if it should keep running 227 | """ 228 | hou.session.hou2vr_autoSave = True 229 | 230 | process = Thread(target=autoSaveThread, args=()) 231 | process.setDaemon(True) 232 | process.start() 233 | 234 | def stopAutoSave(): 235 | """ 236 | Stops auto saving thread by setting hou.session.hou2vr_autoSave to False 237 | """ 238 | try: 239 | hou.session.hou2vr_autoSave = False 240 | except AttributeError: 241 | pass 242 | 243 | def getImgOutPath(img_name="tmp.png"): 244 | """ 245 | Constructs a path (and creates folders if needed) relative to this repo where image will be saved, e.g. 246 | tmp/juraj/tmp.png 247 | 248 | Returns Path object 249 | """ 250 | root = Path(__file__).parents[2] 251 | tmp = root / "tmp" / os.environ["USER"] 252 | img = tmp / "tmp.png" 253 | if not tmp.exists(): 254 | os.makedirs(str(tmp)) 255 | 256 | return img 257 | 258 | def showInWebBrowser(): 259 | """ 260 | Saves image in a tmp location, launches web server if not already running and launches web-browser which displays render image in VR 261 | """ 262 | root = Path(__file__).parents[2] 263 | 264 | img = getImgOutPath() 265 | img_relative = img.relative_to(root) 266 | 267 | img_data = getImageData() 268 | 269 | try: 270 | auto_refresh = int(hou.session.hou2vr_autoSave) 271 | except AttributeError: 272 | auto_refresh = 0 273 | 274 | if img_data: 275 | saveImageAsPng(img_data=img_data, path=img) 276 | 277 | if isServerRunning(): 278 | log.info("Server is running") 279 | else: 280 | log.info("Server is not running, starting...") 281 | os.chdir(str(root)) 282 | 283 | process = Thread(target=startServer, args=()) 284 | process.setDaemon(True) 285 | process.start() 286 | 287 | webbrowser.open_new_tab(url="http://{address}:{port}/web/index.html?img_path={img}&layout={layout}&stereo={stereo}&auto_refresh={auto_refresh}&save_interval={save_interval}".format( address=web_server_address, port=web_server_port, img="/" + str(img_relative).replace("\\", "/"), layout=img_data["layout"], stereo=img_data["stereo"], auto_refresh=auto_refresh, save_interval=auto_save_interval )) 288 | 289 | def encodeImage(img_data): 290 | """ 291 | Encodes image into base64 292 | 293 | TODO: * check if it produces identical results with decodeImage() 294 | """ 295 | res = img_data["res"] 296 | pixels = img_data["pixels"] 297 | 298 | pixels_array = np.array(pixels) 299 | 300 | pixels_array = pixels_array.reshape(res[1], res[0], 4) 301 | pixels_array = np.flip(pixels_array, 0) 302 | pixels_array[:,:,:3] = pixels_array[:,:,:3]**(1/2.2) 303 | pixels_array = pixels_array.flatten() 304 | 305 | pixels_array *= 255 306 | 307 | return base64.b64encode( pixels_array.astype(np.uint8) ) 308 | 309 | def decodeImage(img_string): 310 | """ 311 | Decodes image from base64 312 | 313 | TODO: * this seems not to work properly, produces different results, maybe flipped? 314 | * get somehow resolution in 315 | """ 316 | pixels = np.frombuffer(base64.b64decode(img_string), np.uint8) 317 | pixels = pixels.reshape(10, 10, 4) 318 | pixels = pixels.astype(np.float32) / 255 319 | 320 | return pixels 321 | -------------------------------------------------------------------------------- /web/components.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent("load-map", { 2 | /* 3 | Reads img_path parameter from url and sets its value to src property of material component - assigns a texture 4 | */ 5 | init: function () { 6 | // get img_path parameter from url 7 | var img_path = AFRAME.utils.getUrlParameter("img_path"); 8 | 9 | // check if img_path parameter was found 10 | if (img_path === "") 11 | console.error("img_path parameter not found in url"); 12 | else 13 | { 14 | // modify texture path - set src property in material component 15 | this.el.setAttribute("material", {src: img_path}); 16 | } 17 | } 18 | }); 19 | 20 | AFRAME.registerComponent("vr-material", { 21 | /* 22 | Sets entity's shader to vr-map and sets layout and stereo properties on material (used by vr-map shader) 23 | */ 24 | init: function () { 25 | // get parameters from url 26 | var layout = AFRAME.utils.getUrlParameter("layout"); 27 | var stereo = AFRAME.utils.getUrlParameter("stereo"); 28 | var exposure = AFRAME.utils.getUrlParameter("exposure"); 29 | var gamma_correct = AFRAME.utils.getUrlParameter("gamma_correct"); 30 | 31 | // set shader to vr-map 32 | var el = this.el; 33 | el.setAttribute("material", "shader", "vr-map"); 34 | 35 | // check if parameters were found and modify material properties 36 | if (stereo === "") 37 | { 38 | console.warn("stereo parameter was not found in the url, assuming mono mode (the same as stereo=0)"); 39 | el.setAttribute("material", "stereo", 0); 40 | } 41 | else 42 | el.setAttribute("material", "stereo", stereo); 43 | 44 | if (stereo === "1" && layout === "" ) 45 | console.warn("stereo is enabled and layout parameter was not found in the url"); 46 | else 47 | { 48 | el.setAttribute("material", "layout", layout); 49 | } 50 | 51 | if (exposure !== "") 52 | el.setAttribute("material", "exposure", parseFloat(exposure)); 53 | 54 | if (gamma_correct !== "") 55 | el.setAttribute("material", "gamma_correct", parseInt(gamma_correct)); 56 | 57 | } 58 | }); 59 | 60 | AFRAME.registerComponent("stereo", { 61 | /* 62 | Sets eye property on material (used by vr-map shader) for corresponding eye and assigns it to the expected layer (used by stereo camera rig in VR mode) 63 | */ 64 | schema: { 65 | eye: {type: "string", default: "left"} 66 | }, 67 | update: function (old_data) { 68 | var data = this.data; 69 | var el = this.el; 70 | 71 | if (data.eye !== old_data.eye) 72 | { 73 | var object_3D = this.el.object3D.children[0]; 74 | 75 | // move (and disable all other layers, e.g. default 0) to layer 1 for left eye and 2 for right eye 76 | if (data.eye === "left") 77 | { 78 | object_3D.layers.set(1); 79 | el.setAttribute("material", "eye", 0); 80 | } 81 | else if (data.eye === "right") 82 | { 83 | object_3D.layers.set(2); 84 | el.setAttribute("material", "eye", 1); 85 | } 86 | 87 | //console.log(`Eye set on "${this.el.id}" to "${this.data.eye}"`); 88 | } 89 | } 90 | }); 91 | 92 | AFRAME.registerComponent("stereo-cam", { 93 | /* 94 | Assigns mono camera (in non-VR mode) to layers - lets you enable which objects to see 95 | */ 96 | init: function () { 97 | var children_types = []; 98 | 99 | this.el.object3D.children.forEach( function (item, index, array) { 100 | children_types[index] = item.type; 101 | }); 102 | 103 | var root_index = children_types.indexOf("PerspectiveCamera"); 104 | var rootCam = this.el.object3D.children[root_index]; 105 | 106 | // add layers 1 and 2 to the camera (0 is on by default) 107 | rootCam.layers.enable(1); 108 | rootCam.layers.enable(2); 109 | } 110 | }); 111 | 112 | AFRAME.registerComponent("ui", { 113 | /* 114 | Displays dat.gui interface which controls shader properties of left and right eye spheres 115 | */ 116 | init: function () { 117 | // fetch sphere entities 118 | var sphere_left = document.querySelector('#sphere_left'); 119 | var sphere_right = document.querySelector('#sphere_right'); 120 | var link = document.querySelector('#refresh_link'); 121 | 122 | var gui = new dat.GUI(); 123 | 124 | // create controls object containing parameters 125 | var controls = { 126 | exposure: 0, 127 | gamma_correct: true, 128 | auto_refresh: false, 129 | reset: function() { 130 | this.exposure = 0; 131 | this.gamma_correct = true; 132 | this.auto_refresh = false; 133 | } 134 | }; 135 | 136 | // read attribs from shader 137 | mat_obj = sphere_left.getAttribute("material"); 138 | controls.exposure = mat_obj["exposure"]; 139 | controls.gamma_correct = Boolean(mat_obj["gamma_correct"]); 140 | 141 | // fetch auto_refresh param from url and set initial value in ui 142 | var auto_refresh_parm = AFRAME.utils.getUrlParameter("auto_refresh"); 143 | if (auto_refresh_parm === "1") 144 | controls.auto_refresh = true; 145 | 146 | gui.add(controls, "exposure").min(-8).max(8).name("Exposure").step(0.01).onChange(function() { 147 | sphere_left.setAttribute("material", "exposure", controls.exposure); 148 | sphere_right.setAttribute("material", "exposure", controls.exposure); 149 | link.setAttribute("auto-refresh", "exposure", controls.exposure); 150 | }); 151 | 152 | gui.add(controls, "gamma_correct").name("Gamma Correct").onChange(function() { 153 | sphere_left.setAttribute("material", "gamma_correct", +controls.gamma_correct); 154 | sphere_right.setAttribute("material", "gamma_correct", +controls.gamma_correct); 155 | link.setAttribute("auto-refresh", "gamma_correct", controls.gamma_correct); 156 | }); 157 | 158 | gui.add(controls, "auto_refresh").name("Auto Refresh").onChange(function() { 159 | link.setAttribute("auto-refresh", "enabled", controls.auto_refresh); 160 | }); 161 | 162 | gui.add(controls, "reset").name("Reset").onFinishChange(function() { 163 | // set properties on spheres entities and link 164 | sphere_left.setAttribute("material", { 165 | exposure: controls.exposure, 166 | gamma_correct: +controls.gamma_correct 167 | }); 168 | 169 | sphere_right.setAttribute("material", { 170 | exposure: controls.exposure, 171 | gamma_correct: +controls.gamma_correct 172 | }); 173 | 174 | link.setAttribute("auto-refresh", { 175 | enabled: controls.auto_refresh, 176 | exposure: controls.exposure, 177 | gamma_correct: controls.gamma_correct 178 | }); 179 | 180 | // update ui display 181 | for (var i in gui.__controllers) { 182 | gui.__controllers[i].updateDisplay(); 183 | } 184 | }); 185 | 186 | // collapse by default 187 | //gui.close(); 188 | } 189 | }); 190 | 191 | AFRAME.registerComponent("auto-refresh", { 192 | /* 193 | Component to automatically refresh this web page every N seconds (this.refresh_rate) 194 | */ 195 | schema: { 196 | enabled: {type: "boolean", default: "false"}, 197 | save_interval: {type: "int", default: 5}, 198 | exposure: {type: "number", default: 0}, 199 | gamma_correct: {type: "boolean", default: "true"} 200 | }, 201 | init: function () { 202 | this.link_comp = this.el.components.link; 203 | this.link_comp.data.href = window.location.pathname + window.location.search; 204 | 205 | this.last_tick = 0; 206 | 207 | // get auto_refresh, save_interval params from url and set data to it 208 | var param_enabled = AFRAME.utils.getUrlParameter("auto_refresh"); 209 | if (param_enabled === "1") 210 | this.data.enabled = true; 211 | 212 | var param_interval = AFRAME.utils.getUrlParameter("save_interval"); 213 | if (param_interval !== "") 214 | this.data.save_interval = parseInt(param_interval); 215 | 216 | var exposure = AFRAME.utils.getUrlParameter("exposure"); 217 | if (exposure !== "") 218 | this.data.exposure = parseFloat(exposure); 219 | 220 | var gamma_correct = AFRAME.utils.getUrlParameter("gamma_correct"); 221 | if (gamma_correct !== "") 222 | this.data.gamma_correct = Boolean(parseInt(gamma_correct)); 223 | 224 | }, 225 | update: function (old_data) { 226 | // update this.link_comp.data.href based on changes (e.g. from UI) 227 | if (this.data !== old_data) 228 | { 229 | // set url parm 230 | var parms = new URLSearchParams(window.location.search); 231 | 232 | parms.set("auto_refresh", +this.data.enabled); 233 | parms.set("gamma_correct", +this.data.gamma_correct); 234 | parms.set("exposure", this.data.exposure); 235 | 236 | var parms_string = parms.toString(); 237 | var new_url = window.location.pathname + "?" + decodeURIComponent(parms_string); 238 | 239 | // update window url and link data 240 | window.history.pushState("object or string", "Title", new_url); 241 | this.link_comp.data.href = new_url; 242 | } 243 | }, 244 | tick: function (time, timeDelta) { 245 | var time_sec = Math.round(time / 1000); 246 | 247 | if (this.data.enabled && time_sec % this.data.save_interval === 0 && this.last_tick !== time_sec) 248 | { 249 | this.last_tick = time_sec; 250 | console.log(`Time: "${time_sec}", refreshing..`); 251 | this.link_comp.navigate(); 252 | } 253 | } 254 | }); 255 | 256 | AFRAME.registerComponent("print-layers", { 257 | /* 258 | Prints layer mask, layers numbering is going from right to left 259 | */ 260 | update: function () { 261 | var object_3D = this.el.object3D.children[0]; 262 | console.log(`layer mask for "${this.el.id}" is "${(object_3D.layers.mask).toString(2)}"`); 263 | } 264 | }); 265 | 266 | AFRAME.registerComponent("set-name", { 267 | /* 268 | Sets Object3D name to ID of the entity, used for debugging 269 | */ 270 | init: function () { 271 | var object_3D = this.el.object3D.children[0]; 272 | object_3D.name = this.el.id; 273 | } 274 | }); 275 | 276 | AFRAME.registerComponent("print-scene-graph", { 277 | /* 278 | Prints scene graph 279 | */ 280 | update: function () { 281 | print_graph(this.el.object3D); 282 | } 283 | }); 284 | 285 | function print_graph(obj) { 286 | /* 287 | Traverses three scene and prints a scene graph 288 | */ 289 | console.group(obj.name + ' <%o> ', obj); 290 | obj.children.forEach( print_graph ); 291 | console.groupEnd(); 292 | }; 293 | -------------------------------------------------------------------------------- /web/dat.gui.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(e.dat={})}(this,function(e){"use strict";function t(e,t){var n=e.__state.conversionName.toString(),o=Math.round(e.r),i=Math.round(e.g),r=Math.round(e.b),s=e.a,a=Math.round(e.h),l=e.s.toFixed(1),d=e.v.toFixed(1);if(t||"THREE_CHAR_HEX"===n||"SIX_CHAR_HEX"===n){for(var c=e.hex.toString(16);c.length<6;)c="0"+c;return"#"+c}return"CSS_RGB"===n?"rgb("+o+","+i+","+r+")":"CSS_RGBA"===n?"rgba("+o+","+i+","+r+","+s+")":"HEX"===n?"0x"+e.hex.toString(16):"RGB_ARRAY"===n?"["+o+","+i+","+r+"]":"RGBA_ARRAY"===n?"["+o+","+i+","+r+","+s+"]":"RGB_OBJ"===n?"{r:"+o+",g:"+i+",b:"+r+"}":"RGBA_OBJ"===n?"{r:"+o+",g:"+i+",b:"+r+",a:"+s+"}":"HSV_OBJ"===n?"{h:"+a+",s:"+l+",v:"+d+"}":"HSVA_OBJ"===n?"{h:"+a+",s:"+l+",v:"+d+",a:"+s+"}":"unknown format"}function n(e,t,n){Object.defineProperty(e,t,{get:function(){return"RGB"===this.__state.space?this.__state[t]:(I.recalculateRGB(this,t,n),this.__state[t])},set:function(e){"RGB"!==this.__state.space&&(I.recalculateRGB(this,t,n),this.__state.space="RGB"),this.__state[t]=e}})}function o(e,t){Object.defineProperty(e,t,{get:function(){return"HSV"===this.__state.space?this.__state[t]:(I.recalculateHSV(this),this.__state[t])},set:function(e){"HSV"!==this.__state.space&&(I.recalculateHSV(this),this.__state.space="HSV"),this.__state[t]=e}})}function i(e){if("0"===e||S.isUndefined(e))return 0;var t=e.match(U);return S.isNull(t)?0:parseFloat(t[1])}function r(e){var t=e.toString();return t.indexOf(".")>-1?t.length-t.indexOf(".")-1:0}function s(e,t){var n=Math.pow(10,t);return Math.round(e*n)/n}function a(e,t,n,o,i){return o+(e-t)/(n-t)*(i-o)}function l(e,t,n,o){e.style.background="",S.each(ee,function(i){e.style.cssText+="background: "+i+"linear-gradient("+t+", "+n+" 0%, "+o+" 100%); "})}function d(e){e.style.background="",e.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);",e.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}function c(e,t,n){var o=document.createElement("li");return t&&o.appendChild(t),n?e.__ul.insertBefore(o,n):e.__ul.appendChild(o),e.onResize(),o}function u(e){X.unbind(window,"resize",e.__resizeHandler),e.saveToLocalStorageIfPossible&&X.unbind(window,"unload",e.saveToLocalStorageIfPossible)}function _(e,t){var n=e.__preset_select[e.__preset_select.selectedIndex];n.innerHTML=t?n.value+"*":n.value}function h(e,t,n){if(n.__li=t,n.__gui=e,S.extend(n,{options:function(t){if(arguments.length>1){var o=n.__li.nextElementSibling;return n.remove(),f(e,n.object,n.property,{before:o,factoryArgs:[S.toArray(arguments)]})}if(S.isArray(t)||S.isObject(t)){var i=n.__li.nextElementSibling;return n.remove(),f(e,n.object,n.property,{before:i,factoryArgs:[t]})}},name:function(e){return n.__li.firstElementChild.firstElementChild.innerHTML=e,n},listen:function(){return n.__gui.listen(n),n},remove:function(){return n.__gui.remove(n),n}}),n instanceof q){var o=new Q(n.object,n.property,{min:n.__min,max:n.__max,step:n.__step});S.each(["updateDisplay","onChange","onFinishChange","step","min","max"],function(e){var t=n[e],i=o[e];n[e]=o[e]=function(){var e=Array.prototype.slice.call(arguments);return i.apply(o,e),t.apply(n,e)}}),X.addClass(t,"has-slider"),n.domElement.insertBefore(o.domElement,n.domElement.firstElementChild)}else if(n instanceof Q){var i=function(t){if(S.isNumber(n.__min)&&S.isNumber(n.__max)){var o=n.__li.firstElementChild.firstElementChild.innerHTML,i=n.__gui.__listening.indexOf(n)>-1;n.remove();var r=f(e,n.object,n.property,{before:n.__li.nextElementSibling,factoryArgs:[n.__min,n.__max,n.__step]});return r.name(o),i&&r.listen(),r}return t};n.min=S.compose(i,n.min),n.max=S.compose(i,n.max)}else n instanceof K?(X.bind(t,"click",function(){X.fakeEvent(n.__checkbox,"click")}),X.bind(n.__checkbox,"click",function(e){e.stopPropagation()})):n instanceof Z?(X.bind(t,"click",function(){X.fakeEvent(n.__button,"click")}),X.bind(t,"mouseover",function(){X.addClass(n.__button,"hover")}),X.bind(t,"mouseout",function(){X.removeClass(n.__button,"hover")})):n instanceof $&&(X.addClass(t,"color"),n.updateDisplay=S.compose(function(e){return t.style.borderLeftColor=n.__color.toString(),e},n.updateDisplay),n.updateDisplay());n.setValue=S.compose(function(t){return e.getRoot().__preset_select&&n.isModified()&&_(e.getRoot(),!0),t},n.setValue)}function p(e,t){var n=e.getRoot(),o=n.__rememberedObjects.indexOf(t.object);if(-1!==o){var i=n.__rememberedObjectIndecesToControllers[o];if(void 0===i&&(i={},n.__rememberedObjectIndecesToControllers[o]=i),i[t.property]=t,n.load&&n.load.remembered){var r=n.load.remembered,s=void 0;if(r[e.preset])s=r[e.preset];else{if(!r[se])return;s=r[se]}if(s[o]&&void 0!==s[o][t.property]){var a=s[o][t.property];t.initialValue=a,t.setValue(a)}}}}function f(e,t,n,o){if(void 0===t[n])throw new Error('Object "'+t+'" has no property "'+n+'"');var i=void 0;if(o.color)i=new $(t,n);else{var r=[t,n].concat(o.factoryArgs);i=ne.apply(e,r)}o.before instanceof z&&(o.before=o.before.__li),p(e,i),X.addClass(i.domElement,"c");var s=document.createElement("span");X.addClass(s,"property-name"),s.innerHTML=i.property;var a=document.createElement("div");a.appendChild(s),a.appendChild(i.domElement);var l=c(e,a,o.before);return X.addClass(l,he.CLASS_CONTROLLER_ROW),i instanceof $?X.addClass(l,"color"):X.addClass(l,H(i.getValue())),h(e,l,i),e.__controllers.push(i),i}function m(e,t){return document.location.href+"."+t}function g(e,t,n){var o=document.createElement("option");o.innerHTML=t,o.value=t,e.__preset_select.appendChild(o),n&&(e.__preset_select.selectedIndex=e.__preset_select.length-1)}function b(e,t){t.style.display=e.useLocalStorage?"block":"none"}function v(e){var t=e.__save_row=document.createElement("li");X.addClass(e.domElement,"has-save"),e.__ul.insertBefore(t,e.__ul.firstChild),X.addClass(t,"save-row");var n=document.createElement("span");n.innerHTML=" ",X.addClass(n,"button gears");var o=document.createElement("span");o.innerHTML="Save",X.addClass(o,"button"),X.addClass(o,"save");var i=document.createElement("span");i.innerHTML="New",X.addClass(i,"button"),X.addClass(i,"save-as");var r=document.createElement("span");r.innerHTML="Revert",X.addClass(r,"button"),X.addClass(r,"revert");var s=e.__preset_select=document.createElement("select");if(e.load&&e.load.remembered?S.each(e.load.remembered,function(t,n){g(e,n,n===e.preset)}):g(e,se,!1),X.bind(s,"change",function(){for(var t=0;t=0;n--)t=[e[n].apply(this,t)];return t[0]}},each:function(e,t,n){if(e)if(A&&e.forEach&&e.forEach===A)e.forEach(t,n);else if(e.length===e.length+0){var o=void 0,i=void 0;for(o=0,i=e.length;o1?S.toArray(arguments):arguments[0];return S.each(O,function(t){if(t.litmus(e))return S.each(t.conversions,function(t,n){if(T=t.read(e),!1===L&&!1!==T)return L=T,T.conversionName=n,T.conversion=t,S.BREAK}),S.BREAK}),L},B=void 0,N={hsv_to_rgb:function(e,t,n){var o=Math.floor(e/60)%6,i=e/60-Math.floor(e/60),r=n*(1-t),s=n*(1-i*t),a=n*(1-(1-i)*t),l=[[n,a,r],[s,n,r],[r,n,a],[r,s,n],[a,r,n],[n,r,s]][o];return{r:255*l[0],g:255*l[1],b:255*l[2]}},rgb_to_hsv:function(e,t,n){var o=Math.min(e,t,n),i=Math.max(e,t,n),r=i-o,s=void 0,a=void 0;return 0===i?{h:NaN,s:0,v:0}:(a=r/i,s=e===i?(t-n)/r:t===i?2+(n-e)/r:4+(e-t)/r,(s/=6)<0&&(s+=1),{h:360*s,s:a,v:i/255})},rgb_to_hex:function(e,t,n){var o=this.hex_with_component(0,2,e);return o=this.hex_with_component(o,1,t),o=this.hex_with_component(o,0,n)},component_from_hex:function(e,t){return e>>8*t&255},hex_with_component:function(e,t,n){return n<<(B=8*t)|e&~(255<this.__max&&(n=this.__max),void 0!==this.__step&&n%this.__step!=0&&(n=Math.round(n/this.__step)*this.__step),j(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"setValue",this).call(this,n)}},{key:"min",value:function(e){return this.__min=e,this}},{key:"max",value:function(e){return this.__max=e,this}},{key:"step",value:function(e){return this.__step=e,this.__impliedStep=e,this.__precision=r(e),this}}]),t}(),Q=function(e){function t(e,n,o){function i(){l.__onFinishChange&&l.__onFinishChange.call(l,l.getValue())}function r(e){var t=d-e.clientY;l.setValue(l.getValue()+t*l.__impliedStep),d=e.clientY}function s(){X.unbind(window,"mousemove",r),X.unbind(window,"mouseup",s),i()}F(this,t);var a=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n,o));a.__truncationSuspended=!1;var l=a,d=void 0;return a.__input=document.createElement("input"),a.__input.setAttribute("type","text"),X.bind(a.__input,"change",function(){var e=parseFloat(l.__input.value);S.isNaN(e)||l.setValue(e)}),X.bind(a.__input,"blur",function(){i()}),X.bind(a.__input,"mousedown",function(e){X.bind(window,"mousemove",r),X.bind(window,"mouseup",s),d=e.clientY}),X.bind(a.__input,"keydown",function(e){13===e.keyCode&&(l.__truncationSuspended=!0,this.blur(),l.__truncationSuspended=!1,i())}),a.updateDisplay(),a.domElement.appendChild(a.__input),a}return D(t,W),P(t,[{key:"updateDisplay",value:function(){return this.__input.value=this.__truncationSuspended?this.getValue():s(this.getValue(),this.__precision),j(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"updateDisplay",this).call(this)}}]),t}(),q=function(e){function t(e,n,o,i,r){function s(e){e.preventDefault();var t=_.__background.getBoundingClientRect();return _.setValue(a(e.clientX,t.left,t.right,_.__min,_.__max)),!1}function l(){X.unbind(window,"mousemove",s),X.unbind(window,"mouseup",l),_.__onFinishChange&&_.__onFinishChange.call(_,_.getValue())}function d(e){var t=e.touches[0].clientX,n=_.__background.getBoundingClientRect();_.setValue(a(t,n.left,n.right,_.__min,_.__max))}function c(){X.unbind(window,"touchmove",d),X.unbind(window,"touchend",c),_.__onFinishChange&&_.__onFinishChange.call(_,_.getValue())}F(this,t);var u=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n,{min:o,max:i,step:r})),_=u;return u.__background=document.createElement("div"),u.__foreground=document.createElement("div"),X.bind(u.__background,"mousedown",function(e){document.activeElement.blur(),X.bind(window,"mousemove",s),X.bind(window,"mouseup",l),s(e)}),X.bind(u.__background,"touchstart",function(e){1===e.touches.length&&(X.bind(window,"touchmove",d),X.bind(window,"touchend",c),d(e))}),X.addClass(u.__background,"slider"),X.addClass(u.__foreground,"slider-fg"),u.updateDisplay(),u.__background.appendChild(u.__foreground),u.domElement.appendChild(u.__background),u}return D(t,W),P(t,[{key:"updateDisplay",value:function(){var e=(this.getValue()-this.__min)/(this.__max-this.__min);return this.__foreground.style.width=100*e+"%",j(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"updateDisplay",this).call(this)}}]),t}(),Z=function(e){function t(e,n,o){F(this,t);var i=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n)),r=i;return i.__button=document.createElement("div"),i.__button.innerHTML=void 0===o?"Fire":o,X.bind(i.__button,"click",function(e){return e.preventDefault(),r.fire(),!1}),X.addClass(i.__button,"button"),i.domElement.appendChild(i.__button),i}return D(t,z),P(t,[{key:"fire",value:function(){this.__onChange&&this.__onChange.call(this),this.getValue().call(this.object),this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}}]),t}(),$=function(e){function t(e,n){function o(e){u(e),X.bind(window,"mousemove",u),X.bind(window,"touchmove",u),X.bind(window,"mouseup",r),X.bind(window,"touchend",r)}function i(e){_(e),X.bind(window,"mousemove",_),X.bind(window,"touchmove",_),X.bind(window,"mouseup",s),X.bind(window,"touchend",s)}function r(){X.unbind(window,"mousemove",u),X.unbind(window,"touchmove",u),X.unbind(window,"mouseup",r),X.unbind(window,"touchend",r),c()}function s(){X.unbind(window,"mousemove",_),X.unbind(window,"touchmove",_),X.unbind(window,"mouseup",s),X.unbind(window,"touchend",s),c()}function a(){var e=R(this.value);!1!==e?(p.__color.__state=e,p.setValue(p.__color.toOriginal())):this.value=p.__color.toString()}function c(){p.__onFinishChange&&p.__onFinishChange.call(p,p.__color.toOriginal())}function u(e){-1===e.type.indexOf("touch")&&e.preventDefault();var t=p.__saturation_field.getBoundingClientRect(),n=e.touches&&e.touches[0]||e,o=n.clientX,i=n.clientY,r=(o-t.left)/(t.right-t.left),s=1-(i-t.top)/(t.bottom-t.top);return s>1?s=1:s<0&&(s=0),r>1?r=1:r<0&&(r=0),p.__color.v=s,p.__color.s=r,p.setValue(p.__color.toOriginal()),!1}function _(e){-1===e.type.indexOf("touch")&&e.preventDefault();var t=p.__hue_field.getBoundingClientRect(),n=1-((e.touches&&e.touches[0]||e).clientY-t.top)/(t.bottom-t.top);return n>1?n=1:n<0&&(n=0),p.__color.h=360*n,p.setValue(p.__color.toOriginal()),!1}F(this,t);var h=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n));h.__color=new I(h.getValue()),h.__temp=new I(0);var p=h;h.domElement=document.createElement("div"),X.makeSelectable(h.domElement,!1),h.__selector=document.createElement("div"),h.__selector.className="selector",h.__saturation_field=document.createElement("div"),h.__saturation_field.className="saturation-field",h.__field_knob=document.createElement("div"),h.__field_knob.className="field-knob",h.__field_knob_border="2px solid ",h.__hue_knob=document.createElement("div"),h.__hue_knob.className="hue-knob",h.__hue_field=document.createElement("div"),h.__hue_field.className="hue-field",h.__input=document.createElement("input"),h.__input.type="text",h.__input_textShadow="0 1px 1px ",X.bind(h.__input,"keydown",function(e){13===e.keyCode&&a.call(this)}),X.bind(h.__input,"blur",a),X.bind(h.__selector,"mousedown",function(){X.addClass(this,"drag").bind(window,"mouseup",function(){X.removeClass(p.__selector,"drag")})}),X.bind(h.__selector,"touchstart",function(){X.addClass(this,"drag").bind(window,"touchend",function(){X.removeClass(p.__selector,"drag")})});var f=document.createElement("div");return S.extend(h.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"}),S.extend(h.__field_knob.style,{position:"absolute",width:"12px",height:"12px",border:h.__field_knob_border+(h.__color.v<.5?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1}),S.extend(h.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1}),S.extend(h.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"}),S.extend(f.style,{width:"100%",height:"100%",background:"none"}),l(f,"top","rgba(0,0,0,0)","#000"),S.extend(h.__hue_field.style,{width:"15px",height:"100px",border:"1px solid #555",cursor:"ns-resize",position:"absolute",top:"3px",right:"3px"}),d(h.__hue_field),S.extend(h.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:h.__input_textShadow+"rgba(0,0,0,0.7)"}),X.bind(h.__saturation_field,"mousedown",o),X.bind(h.__saturation_field,"touchstart",o),X.bind(h.__field_knob,"mousedown",o),X.bind(h.__field_knob,"touchstart",o),X.bind(h.__hue_field,"mousedown",i),X.bind(h.__hue_field,"touchstart",i),h.__saturation_field.appendChild(f),h.__selector.appendChild(h.__field_knob),h.__selector.appendChild(h.__saturation_field),h.__selector.appendChild(h.__hue_field),h.__hue_field.appendChild(h.__hue_knob),h.domElement.appendChild(h.__input),h.domElement.appendChild(h.__selector),h.updateDisplay(),h}return D(t,z),P(t,[{key:"updateDisplay",value:function(){var e=R(this.getValue());if(!1!==e){var t=!1;S.each(I.COMPONENTS,function(n){if(!S.isUndefined(e[n])&&!S.isUndefined(this.__color.__state[n])&&e[n]!==this.__color.__state[n])return t=!0,{}},this),t&&S.extend(this.__color.__state,e)}S.extend(this.__temp.__state,this.__color.__state),this.__temp.a=1;var n=this.__color.v<.5||this.__color.s>.5?255:0,o=255-n;S.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toHexString(),border:this.__field_knob_border+"rgb("+n+","+n+","+n+")"}),this.__hue_knob.style.marginTop=100*(1-this.__color.h/360)+"px",this.__temp.s=1,this.__temp.v=1,l(this.__saturation_field,"left","#fff",this.__temp.toHexString()),this.__input.value=this.__color.toString(),S.extend(this.__input.style,{backgroundColor:this.__color.toHexString(),color:"rgb("+n+","+n+","+n+")",textShadow:this.__input_textShadow+"rgba("+o+","+o+","+o+",.7)"})}}]),t}(),ee=["-moz-","-o-","-webkit-","-ms-",""],te={load:function(e,t){var n=t||document,o=n.createElement("link");o.type="text/css",o.rel="stylesheet",o.href=e,n.getElementsByTagName("head")[0].appendChild(o)},inject:function(e,t){var n=t||document,o=document.createElement("style");o.type="text/css",o.innerHTML=e;var i=n.getElementsByTagName("head")[0];try{i.appendChild(o)}catch(e){}}},ne=function(e,t){var n=e[t];return S.isArray(arguments[2])||S.isObject(arguments[2])?new Y(e,t,arguments[2]):S.isNumber(n)?S.isNumber(arguments[2])&&S.isNumber(arguments[3])?S.isNumber(arguments[4])?new q(e,t,arguments[2],arguments[3],arguments[4]):new q(e,t,arguments[2],arguments[3]):S.isNumber(arguments[4])?new Q(e,t,{min:arguments[2],max:arguments[3],step:arguments[4]}):new Q(e,t,{min:arguments[2],max:arguments[3]}):S.isString(n)?new J(e,t):S.isFunction(n)?new Z(e,t,""):S.isBoolean(n)?new K(e,t):null},oe=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){setTimeout(e,1e3/60)},ie=function(){function e(){F(this,e),this.backgroundElement=document.createElement("div"),S.extend(this.backgroundElement.style,{backgroundColor:"rgba(0,0,0,0.8)",top:0,left:0,display:"none",zIndex:"1000",opacity:0,WebkitTransition:"opacity 0.2s linear",transition:"opacity 0.2s linear"}),X.makeFullscreen(this.backgroundElement),this.backgroundElement.style.position="fixed",this.domElement=document.createElement("div"),S.extend(this.domElement.style,{position:"fixed",display:"none",zIndex:"1001",opacity:0,WebkitTransition:"-webkit-transform 0.2s ease-out, opacity 0.2s linear",transition:"transform 0.2s ease-out, opacity 0.2s linear"}),document.body.appendChild(this.backgroundElement),document.body.appendChild(this.domElement);var t=this;X.bind(this.backgroundElement,"click",function(){t.hide()})}return P(e,[{key:"show",value:function(){var e=this;this.backgroundElement.style.display="block",this.domElement.style.display="block",this.domElement.style.opacity=0,this.domElement.style.webkitTransform="scale(1.1)",this.layout(),S.defer(function(){e.backgroundElement.style.opacity=1,e.domElement.style.opacity=1,e.domElement.style.webkitTransform="scale(1)"})}},{key:"hide",value:function(){var e=this,t=function t(){e.domElement.style.display="none",e.backgroundElement.style.display="none",X.unbind(e.domElement,"webkitTransitionEnd",t),X.unbind(e.domElement,"transitionend",t),X.unbind(e.domElement,"oTransitionEnd",t)};X.bind(this.domElement,"webkitTransitionEnd",t),X.bind(this.domElement,"transitionend",t),X.bind(this.domElement,"oTransitionEnd",t),this.backgroundElement.style.opacity=0,this.domElement.style.opacity=0,this.domElement.style.webkitTransform="scale(1.1)"}},{key:"layout",value:function(){this.domElement.style.left=window.innerWidth/2-X.getWidth(this.domElement)/2+"px",this.domElement.style.top=window.innerHeight/2-X.getHeight(this.domElement)/2+"px"}}]),e}(),re=function(e){if(e&&"undefined"!=typeof window){var t=document.createElement("style");return t.setAttribute("type","text/css"),t.innerHTML=e,document.head.appendChild(t),e}}(".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear;border:0;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button.close-top{position:relative}.dg.main .close-button.close-bottom{position:absolute}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-y:visible}.dg.a.has-save>ul.close-top{margin-top:0}.dg.a.has-save>ul.close-bottom{margin-top:27px}.dg.a.has-save>ul.closed{margin-top:0}.dg.a .save-row{top:0;z-index:1002}.dg.a .save-row.close-top{position:relative}.dg.a .save-row.close-bottom{position:fixed}.dg li{-webkit-transition:height .1s ease-out;-o-transition:height .1s ease-out;-moz-transition:height .1s ease-out;transition:height .1s ease-out;-webkit-transition:overflow .1s linear;-o-transition:overflow .1s linear;-moz-transition:overflow .1s linear;transition:overflow .1s linear}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li>*{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px;overflow:hidden}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%;position:relative}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:7px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .cr.color{overflow:visible}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.color{border-left:3px solid}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2FA1D6}.dg .cr.number input[type=text]{color:#2FA1D6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2FA1D6;max-width:100%}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n");te.inject(re);var se="Default",ae=function(){try{return!!window.localStorage}catch(e){return!1}}(),le=void 0,de=!0,ce=void 0,ue=!1,_e=[],he=function e(t){var n=this,o=t||{};this.domElement=document.createElement("div"),this.__ul=document.createElement("ul"),this.domElement.appendChild(this.__ul),X.addClass(this.domElement,"dg"),this.__folders={},this.__controllers=[],this.__rememberedObjects=[],this.__rememberedObjectIndecesToControllers=[],this.__listening=[],o=S.defaults(o,{closeOnTop:!1,autoPlace:!0,width:e.DEFAULT_WIDTH}),o=S.defaults(o,{resizable:o.autoPlace,hideable:o.autoPlace}),S.isUndefined(o.load)?o.load={preset:se}:o.preset&&(o.load.preset=o.preset),S.isUndefined(o.parent)&&o.hideable&&_e.push(this),o.resizable=S.isUndefined(o.parent)&&o.resizable,o.autoPlace&&S.isUndefined(o.scrollable)&&(o.scrollable=!0);var i=ae&&"true"===localStorage.getItem(m(this,"isLocal")),r=void 0,s=void 0;if(Object.defineProperties(this,{parent:{get:function(){return o.parent}},scrollable:{get:function(){return o.scrollable}},autoPlace:{get:function(){return o.autoPlace}},closeOnTop:{get:function(){return o.closeOnTop}},preset:{get:function(){return n.parent?n.getRoot().preset:o.load.preset},set:function(e){n.parent?n.getRoot().preset=e:o.load.preset=e,E(this),n.revert()}},width:{get:function(){return o.width},set:function(e){o.width=e,w(n,e)}},name:{get:function(){return o.name},set:function(e){o.name=e,s&&(s.innerHTML=o.name)}},closed:{get:function(){return o.closed},set:function(t){o.closed=t,o.closed?X.addClass(n.__ul,e.CLASS_CLOSED):X.removeClass(n.__ul,e.CLASS_CLOSED),this.onResize(),n.__closeButton&&(n.__closeButton.innerHTML=t?e.TEXT_OPEN:e.TEXT_CLOSED)}},load:{get:function(){return o.load}},useLocalStorage:{get:function(){return i},set:function(e){ae&&(i=e,e?X.bind(window,"unload",r):X.unbind(window,"unload",r),localStorage.setItem(m(n,"isLocal"),e))}}}),S.isUndefined(o.parent)){if(this.closed=o.closed||!1,X.addClass(this.domElement,e.CLASS_MAIN),X.makeSelectable(this.domElement,!1),ae&&i){n.useLocalStorage=!0;var a=localStorage.getItem(m(this,"gui"));a&&(o.load=JSON.parse(a))}this.__closeButton=document.createElement("div"),this.__closeButton.innerHTML=e.TEXT_CLOSED,X.addClass(this.__closeButton,e.CLASS_CLOSE_BUTTON),o.closeOnTop?(X.addClass(this.__closeButton,e.CLASS_CLOSE_TOP),this.domElement.insertBefore(this.__closeButton,this.domElement.childNodes[0])):(X.addClass(this.__closeButton,e.CLASS_CLOSE_BOTTOM),this.domElement.appendChild(this.__closeButton)),X.bind(this.__closeButton,"click",function(){n.closed=!n.closed})}else{void 0===o.closed&&(o.closed=!0);var l=document.createTextNode(o.name);X.addClass(l,"controller-name"),s=c(n,l);X.addClass(this.__ul,e.CLASS_CLOSED),X.addClass(s,"title"),X.bind(s,"click",function(e){return e.preventDefault(),n.closed=!n.closed,!1}),o.closed||(this.closed=!1)}o.autoPlace&&(S.isUndefined(o.parent)&&(de&&(ce=document.createElement("div"),X.addClass(ce,"dg"),X.addClass(ce,e.CLASS_AUTO_PLACE_CONTAINER),document.body.appendChild(ce),de=!1),ce.appendChild(this.domElement),X.addClass(this.domElement,e.CLASS_AUTO_PLACE)),this.parent||w(n,o.width)),this.__resizeHandler=function(){n.onResizeDebounced()},X.bind(window,"resize",this.__resizeHandler),X.bind(this.__ul,"webkitTransitionEnd",this.__resizeHandler),X.bind(this.__ul,"transitionend",this.__resizeHandler),X.bind(this.__ul,"oTransitionEnd",this.__resizeHandler),this.onResize(),o.resizable&&y(this),r=function(){ae&&"true"===localStorage.getItem(m(n,"isLocal"))&&localStorage.setItem(m(n,"gui"),JSON.stringify(n.getSaveObject()))},this.saveToLocalStorageIfPossible=r,o.parent||function(){var e=n.getRoot();e.width+=1,S.defer(function(){e.width-=1})}()};he.toggleHide=function(){ue=!ue,S.each(_e,function(e){e.domElement.style.display=ue?"none":""})},he.CLASS_AUTO_PLACE="a",he.CLASS_AUTO_PLACE_CONTAINER="ac",he.CLASS_MAIN="main",he.CLASS_CONTROLLER_ROW="cr",he.CLASS_TOO_TALL="taller-than-window",he.CLASS_CLOSED="closed",he.CLASS_CLOSE_BUTTON="close-button",he.CLASS_CLOSE_TOP="close-top",he.CLASS_CLOSE_BOTTOM="close-bottom",he.CLASS_DRAG="drag",he.DEFAULT_WIDTH=245,he.TEXT_CLOSED="Close Controls",he.TEXT_OPEN="Open Controls",he._keydownHandler=function(e){"text"===document.activeElement.type||72!==e.which&&72!==e.keyCode||he.toggleHide()},X.bind(window,"keydown",he._keydownHandler,!1),S.extend(he.prototype,{add:function(e,t){return f(this,e,t,{factoryArgs:Array.prototype.slice.call(arguments,2)})},addColor:function(e,t){return f(this,e,t,{color:!0})},remove:function(e){this.__ul.removeChild(e.__li),this.__controllers.splice(this.__controllers.indexOf(e),1);var t=this;S.defer(function(){t.onResize()})},destroy:function(){if(this.parent)throw new Error("Only the root GUI should be removed with .destroy(). For subfolders, use gui.removeFolder(folder) instead.");this.autoPlace&&ce.removeChild(this.domElement);var e=this;S.each(this.__folders,function(t){e.removeFolder(t)}),X.unbind(window,"keydown",he._keydownHandler,!1),u(this)},addFolder:function(e){if(void 0!==this.__folders[e])throw new Error('You already have a folder in this GUI by the name "'+e+'"');var t={name:e,parent:this};t.autoPlace=this.autoPlace,this.load&&this.load.folders&&this.load.folders[e]&&(t.closed=this.load.folders[e].closed,t.load=this.load.folders[e]);var n=new he(t);this.__folders[e]=n;var o=c(this,n.domElement);return X.addClass(o,"folder"),n},removeFolder:function(e){this.__ul.removeChild(e.domElement.parentElement),delete this.__folders[e.name],this.load&&this.load.folders&&this.load.folders[e.name]&&delete this.load.folders[e.name],u(e);var t=this;S.each(e.__folders,function(t){e.removeFolder(t)}),S.defer(function(){t.onResize()})},open:function(){this.closed=!1},close:function(){this.closed=!0},onResize:function(){var e=this.getRoot();if(e.scrollable){var t=X.getOffset(e.__ul).top,n=0;S.each(e.__ul.childNodes,function(t){e.autoPlace&&t===e.__save_row||(n+=X.getHeight(t))}),window.innerHeight-t-20GUI\'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
\n\n
\n\n'),this.parent)throw new Error("You can only call remember on a top level GUI.");var e=this;S.each(Array.prototype.slice.call(arguments),function(t){0===e.__rememberedObjects.length&&v(e),-1===e.__rememberedObjects.indexOf(t)&&e.__rememberedObjects.push(t)}),this.autoPlace&&w(this,this.width)},getRoot:function(){for(var e=this;e.parent;)e=e.parent;return e},getSaveObject:function(){var e=this.load;return e.closed=this.closed,this.__rememberedObjects.length>0&&(e.preset=this.preset,e.remembered||(e.remembered={}),e.remembered[this.preset]=x(this)),e.folders={},S.each(this.__folders,function(t,n){e.folders[n]=t.getSaveObject()}),e},save:function(){this.load.remembered||(this.load.remembered={}),this.load.remembered[this.preset]=x(this),_(this,!1),this.saveToLocalStorageIfPossible()},saveAs:function(e){this.load.remembered||(this.load.remembered={},this.load.remembered[se]=x(this,!0)),this.load.remembered[e]=x(this),this.preset=e,g(this,e,!0),this.saveToLocalStorageIfPossible()},revert:function(e){S.each(this.__controllers,function(t){this.getRoot().load.remembered?p(e||this.getRoot(),t):t.setValue(t.initialValue),t.__onFinishChange&&t.__onFinishChange.call(t,t.getValue())},this),S.each(this.__folders,function(e){e.revert(e)}),e||_(this.getRoot(),!1)},listen:function(e){var t=0===this.__listening.length;this.__listening.push(e),t&&C(this.__listening)},updateDisplay:function(){S.each(this.__controllers,function(e){e.updateDisplay()}),S.each(this.__folders,function(e){e.updateDisplay()})}});var pe={Color:I,math:N,interpret:R},fe={Controller:z,BooleanController:K,OptionController:Y,StringController:J,NumberController:W,NumberControllerBox:Q,NumberControllerSlider:q,FunctionController:Z,ColorController:$},me={dom:X},ge={GUI:he},be=he,ve={color:pe,controllers:fe,dom:me,gui:ge,GUI:be};e.color=pe,e.controllers=fe,e.dom=me,e.gui=ge,e.GUI=be,e.default=ve,Object.defineProperty(e,"__esModule",{value:!0})}); 14 | --------------------------------------------------------------------------------