├── README.md ├── assets ├── css │ └── common.css └── js │ ├── lib │ ├── TrackballControls.js │ ├── TweenMax.js │ ├── dat.gui.js │ ├── jquery.js │ ├── three.js │ └── webfontloader.js │ ├── step1 │ ├── MainVisual.js │ └── index.js │ ├── step2 │ ├── FloatingChars.js │ ├── FloatingCharsGeometry.js │ ├── MainVisual.js │ └── index.js │ ├── step3 │ ├── FloatingChars.js │ ├── FloatingCharsGeometry.js │ ├── MainVisual.js │ └── index.js │ ├── step4 │ ├── FloatingChars.js │ ├── FloatingCharsGeometry.js │ ├── MainVisual.js │ └── index.js │ └── step5 │ ├── FloatingChars.js │ ├── FloatingCharsGeometry.js │ ├── MainVisual.js │ └── index.js ├── step1 └── index.html ├── step2 └── index.html ├── step3 └── index.html ├── step4 └── index.html └── step5 └── index.html /README.md: -------------------------------------------------------------------------------- 1 | # 2016.12.03 GLSL Workshop 2 | 2016.12.03 GLSL Workshopの資料です。 3 | 4 | https://tkmh.me/study/glslWorkshop20161203/slide/ 5 | 6 | ## サンプル 7 | https://tkmh.me/study/glslWorkshop20161203/sample/step1/ 8 | https://tkmh.me/study/glslWorkshop20161203/sample/step2/ 9 | https://tkmh.me/study/glslWorkshop20161203/sample/step3/ 10 | https://tkmh.me/study/glslWorkshop20161203/sample/step4/ 11 | https://tkmh.me/study/glslWorkshop20161203/sample/step5/ 12 | 13 | ## three.jsの基礎  14 | https://html5experts.jp/yomotsu/5225/ 15 | http://qiita.com/nogson/items/e5e4a5a09f7d594eabf8 16 | 17 | ## UV座標 18 | https://wgld.org/d/webgl/w026.html 19 | 20 | ## three.js document 21 | https://threejs.org/docs/ 22 | 23 | ### PlaneGeometry 24 | https://threejs.org/docs/#Reference/Geometries/PlaneGeometry 25 | 26 | ### BoxGeometry 27 | https://threejs.org/docs/#Reference/Geometries/BoxGeometry 28 | 29 | ### BufferGeometry 30 | https://threejs.org/docs/#Reference/Core/BufferGeometry 31 | 32 | ### RawShaderMaterial 33 | https://threejs.org/docs/#Reference/Materials/RawShaderMaterial 34 | -------------------------------------------------------------------------------- /assets/css/common.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | #main { 7 | position: absolute; 8 | top: 0; 9 | left: 0; 10 | width: 100%; 11 | height: 100%; 12 | background-color: #fff; 13 | } 14 | 15 | #main canvas { 16 | position: absolute; 17 | } 18 | -------------------------------------------------------------------------------- /assets/js/lib/TrackballControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eberhard Graether / http://egraether.com/ 3 | * @author Mark Lundin / http://mark-lundin.com 4 | * @author Simone Manini / http://daron1337.github.io 5 | * @author Luca Antiga / http://lantiga.github.io 6 | */ 7 | 8 | THREE.TrackballControls = function ( object, domElement ) { 9 | 10 | var _this = this; 11 | var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; 12 | 13 | this.object = object; 14 | this.domElement = ( domElement !== undefined ) ? domElement : document; 15 | 16 | // API 17 | 18 | this.enabled = true; 19 | 20 | this.screen = { left: 0, top: 0, width: 0, height: 0 }; 21 | 22 | this.rotateSpeed = 1.0; 23 | this.zoomSpeed = 1.2; 24 | this.panSpeed = 0.3; 25 | 26 | this.noRotate = false; 27 | this.noZoom = false; 28 | this.noPan = false; 29 | 30 | this.staticMoving = false; 31 | this.dynamicDampingFactor = 0.2; 32 | 33 | this.minDistance = 0; 34 | this.maxDistance = Infinity; 35 | 36 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 37 | 38 | // internals 39 | 40 | this.target = new THREE.Vector3(); 41 | 42 | var EPS = 0.000001; 43 | 44 | var lastPosition = new THREE.Vector3(); 45 | 46 | var _state = STATE.NONE, 47 | _prevState = STATE.NONE, 48 | 49 | _eye = new THREE.Vector3(), 50 | 51 | _movePrev = new THREE.Vector2(), 52 | _moveCurr = new THREE.Vector2(), 53 | 54 | _lastAxis = new THREE.Vector3(), 55 | _lastAngle = 0, 56 | 57 | _zoomStart = new THREE.Vector2(), 58 | _zoomEnd = new THREE.Vector2(), 59 | 60 | _touchZoomDistanceStart = 0, 61 | _touchZoomDistanceEnd = 0, 62 | 63 | _panStart = new THREE.Vector2(), 64 | _panEnd = new THREE.Vector2(); 65 | 66 | // for reset 67 | 68 | this.target0 = this.target.clone(); 69 | this.position0 = this.object.position.clone(); 70 | this.up0 = this.object.up.clone(); 71 | 72 | // events 73 | 74 | var changeEvent = { type: 'change' }; 75 | var startEvent = { type: 'start' }; 76 | var endEvent = { type: 'end' }; 77 | 78 | 79 | // methods 80 | 81 | this.handleResize = function () { 82 | 83 | if ( this.domElement === document ) { 84 | 85 | this.screen.left = 0; 86 | this.screen.top = 0; 87 | this.screen.width = window.innerWidth; 88 | this.screen.height = window.innerHeight; 89 | 90 | } else { 91 | 92 | var box = this.domElement.getBoundingClientRect(); 93 | // adjustments come from similar code in the jquery offset() function 94 | var d = this.domElement.ownerDocument.documentElement; 95 | this.screen.left = box.left + window.pageXOffset - d.clientLeft; 96 | this.screen.top = box.top + window.pageYOffset - d.clientTop; 97 | this.screen.width = box.width; 98 | this.screen.height = box.height; 99 | 100 | } 101 | 102 | }; 103 | 104 | this.handleEvent = function ( event ) { 105 | 106 | if ( typeof this[ event.type ] == 'function' ) { 107 | 108 | this[ event.type ]( event ); 109 | 110 | } 111 | 112 | }; 113 | 114 | var getMouseOnScreen = ( function () { 115 | 116 | var vector = new THREE.Vector2(); 117 | 118 | return function getMouseOnScreen( pageX, pageY ) { 119 | 120 | vector.set( 121 | ( pageX - _this.screen.left ) / _this.screen.width, 122 | ( pageY - _this.screen.top ) / _this.screen.height 123 | ); 124 | 125 | return vector; 126 | 127 | }; 128 | 129 | }() ); 130 | 131 | var getMouseOnCircle = ( function () { 132 | 133 | var vector = new THREE.Vector2(); 134 | 135 | return function getMouseOnCircle( pageX, pageY ) { 136 | 137 | vector.set( 138 | ( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ), 139 | ( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional 140 | ); 141 | 142 | return vector; 143 | 144 | }; 145 | 146 | }() ); 147 | 148 | this.rotateCamera = ( function() { 149 | 150 | var axis = new THREE.Vector3(), 151 | quaternion = new THREE.Quaternion(), 152 | eyeDirection = new THREE.Vector3(), 153 | objectUpDirection = new THREE.Vector3(), 154 | objectSidewaysDirection = new THREE.Vector3(), 155 | moveDirection = new THREE.Vector3(), 156 | angle; 157 | 158 | return function rotateCamera() { 159 | 160 | moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); 161 | angle = moveDirection.length(); 162 | 163 | if ( angle ) { 164 | 165 | _eye.copy( _this.object.position ).sub( _this.target ); 166 | 167 | eyeDirection.copy( _eye ).normalize(); 168 | objectUpDirection.copy( _this.object.up ).normalize(); 169 | objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize(); 170 | 171 | objectUpDirection.setLength( _moveCurr.y - _movePrev.y ); 172 | objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x ); 173 | 174 | moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) ); 175 | 176 | axis.crossVectors( moveDirection, _eye ).normalize(); 177 | 178 | angle *= _this.rotateSpeed; 179 | quaternion.setFromAxisAngle( axis, angle ); 180 | 181 | _eye.applyQuaternion( quaternion ); 182 | _this.object.up.applyQuaternion( quaternion ); 183 | 184 | _lastAxis.copy( axis ); 185 | _lastAngle = angle; 186 | 187 | } else if ( ! _this.staticMoving && _lastAngle ) { 188 | 189 | _lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor ); 190 | _eye.copy( _this.object.position ).sub( _this.target ); 191 | quaternion.setFromAxisAngle( _lastAxis, _lastAngle ); 192 | _eye.applyQuaternion( quaternion ); 193 | _this.object.up.applyQuaternion( quaternion ); 194 | 195 | } 196 | 197 | _movePrev.copy( _moveCurr ); 198 | 199 | }; 200 | 201 | }() ); 202 | 203 | 204 | this.zoomCamera = function () { 205 | 206 | var factor; 207 | 208 | if ( _state === STATE.TOUCH_ZOOM_PAN ) { 209 | 210 | factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; 211 | _touchZoomDistanceStart = _touchZoomDistanceEnd; 212 | _eye.multiplyScalar( factor ); 213 | 214 | } else { 215 | 216 | factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; 217 | 218 | if ( factor !== 1.0 && factor > 0.0 ) { 219 | 220 | _eye.multiplyScalar( factor ); 221 | 222 | } 223 | 224 | if ( _this.staticMoving ) { 225 | 226 | _zoomStart.copy( _zoomEnd ); 227 | 228 | } else { 229 | 230 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; 231 | 232 | } 233 | 234 | } 235 | 236 | }; 237 | 238 | this.panCamera = ( function() { 239 | 240 | var mouseChange = new THREE.Vector2(), 241 | objectUp = new THREE.Vector3(), 242 | pan = new THREE.Vector3(); 243 | 244 | return function panCamera() { 245 | 246 | mouseChange.copy( _panEnd ).sub( _panStart ); 247 | 248 | if ( mouseChange.lengthSq() ) { 249 | 250 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); 251 | 252 | pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x ); 253 | pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) ); 254 | 255 | _this.object.position.add( pan ); 256 | _this.target.add( pan ); 257 | 258 | if ( _this.staticMoving ) { 259 | 260 | _panStart.copy( _panEnd ); 261 | 262 | } else { 263 | 264 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); 265 | 266 | } 267 | 268 | } 269 | 270 | }; 271 | 272 | }() ); 273 | 274 | this.checkDistances = function () { 275 | 276 | if ( ! _this.noZoom || ! _this.noPan ) { 277 | 278 | if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) { 279 | 280 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) ); 281 | _zoomStart.copy( _zoomEnd ); 282 | 283 | } 284 | 285 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { 286 | 287 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); 288 | _zoomStart.copy( _zoomEnd ); 289 | 290 | } 291 | 292 | } 293 | 294 | }; 295 | 296 | this.update = function () { 297 | 298 | _eye.subVectors( _this.object.position, _this.target ); 299 | 300 | if ( ! _this.noRotate ) { 301 | 302 | _this.rotateCamera(); 303 | 304 | } 305 | 306 | if ( ! _this.noZoom ) { 307 | 308 | _this.zoomCamera(); 309 | 310 | } 311 | 312 | if ( ! _this.noPan ) { 313 | 314 | _this.panCamera(); 315 | 316 | } 317 | 318 | _this.object.position.addVectors( _this.target, _eye ); 319 | 320 | _this.checkDistances(); 321 | 322 | _this.object.lookAt( _this.target ); 323 | 324 | if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) { 325 | 326 | _this.dispatchEvent( changeEvent ); 327 | 328 | lastPosition.copy( _this.object.position ); 329 | 330 | } 331 | 332 | }; 333 | 334 | this.reset = function () { 335 | 336 | _state = STATE.NONE; 337 | _prevState = STATE.NONE; 338 | 339 | _this.target.copy( _this.target0 ); 340 | _this.object.position.copy( _this.position0 ); 341 | _this.object.up.copy( _this.up0 ); 342 | 343 | _eye.subVectors( _this.object.position, _this.target ); 344 | 345 | _this.object.lookAt( _this.target ); 346 | 347 | _this.dispatchEvent( changeEvent ); 348 | 349 | lastPosition.copy( _this.object.position ); 350 | 351 | }; 352 | 353 | // listeners 354 | 355 | function keydown( event ) { 356 | 357 | if ( _this.enabled === false ) return; 358 | 359 | window.removeEventListener( 'keydown', keydown ); 360 | 361 | _prevState = _state; 362 | 363 | if ( _state !== STATE.NONE ) { 364 | 365 | return; 366 | 367 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) { 368 | 369 | _state = STATE.ROTATE; 370 | 371 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) { 372 | 373 | _state = STATE.ZOOM; 374 | 375 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) { 376 | 377 | _state = STATE.PAN; 378 | 379 | } 380 | 381 | } 382 | 383 | function keyup( event ) { 384 | 385 | if ( _this.enabled === false ) return; 386 | 387 | _state = _prevState; 388 | 389 | window.addEventListener( 'keydown', keydown, false ); 390 | 391 | } 392 | 393 | function mousedown( event ) { 394 | 395 | if ( _this.enabled === false ) return; 396 | 397 | event.preventDefault(); 398 | event.stopPropagation(); 399 | 400 | if ( _state === STATE.NONE ) { 401 | 402 | _state = event.button; 403 | 404 | } 405 | 406 | if ( _state === STATE.ROTATE && ! _this.noRotate ) { 407 | 408 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); 409 | _movePrev.copy( _moveCurr ); 410 | 411 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) { 412 | 413 | _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 414 | _zoomEnd.copy( _zoomStart ); 415 | 416 | } else if ( _state === STATE.PAN && ! _this.noPan ) { 417 | 418 | _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 419 | _panEnd.copy( _panStart ); 420 | 421 | } 422 | 423 | document.addEventListener( 'mousemove', mousemove, false ); 424 | document.addEventListener( 'mouseup', mouseup, false ); 425 | 426 | _this.dispatchEvent( startEvent ); 427 | 428 | } 429 | 430 | function mousemove( event ) { 431 | 432 | if ( _this.enabled === false ) return; 433 | 434 | event.preventDefault(); 435 | event.stopPropagation(); 436 | 437 | if ( _state === STATE.ROTATE && ! _this.noRotate ) { 438 | 439 | _movePrev.copy( _moveCurr ); 440 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); 441 | 442 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) { 443 | 444 | _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 445 | 446 | } else if ( _state === STATE.PAN && ! _this.noPan ) { 447 | 448 | _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 449 | 450 | } 451 | 452 | } 453 | 454 | function mouseup( event ) { 455 | 456 | if ( _this.enabled === false ) return; 457 | 458 | event.preventDefault(); 459 | event.stopPropagation(); 460 | 461 | _state = STATE.NONE; 462 | 463 | document.removeEventListener( 'mousemove', mousemove ); 464 | document.removeEventListener( 'mouseup', mouseup ); 465 | _this.dispatchEvent( endEvent ); 466 | 467 | } 468 | 469 | function mousewheel( event ) { 470 | 471 | if ( _this.enabled === false ) return; 472 | 473 | event.preventDefault(); 474 | event.stopPropagation(); 475 | 476 | _zoomStart.y -= event.deltaY * 0.01; 477 | 478 | _this.dispatchEvent( startEvent ); 479 | _this.dispatchEvent( endEvent ); 480 | 481 | } 482 | 483 | function touchstart( event ) { 484 | 485 | if ( _this.enabled === false ) return; 486 | 487 | switch ( event.touches.length ) { 488 | 489 | case 1: 490 | _state = STATE.TOUCH_ROTATE; 491 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 492 | _movePrev.copy( _moveCurr ); 493 | break; 494 | 495 | default: // 2 or more 496 | _state = STATE.TOUCH_ZOOM_PAN; 497 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 498 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 499 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); 500 | 501 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 502 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 503 | _panStart.copy( getMouseOnScreen( x, y ) ); 504 | _panEnd.copy( _panStart ); 505 | break; 506 | 507 | } 508 | 509 | _this.dispatchEvent( startEvent ); 510 | 511 | } 512 | 513 | function touchmove( event ) { 514 | 515 | if ( _this.enabled === false ) return; 516 | 517 | event.preventDefault(); 518 | event.stopPropagation(); 519 | 520 | switch ( event.touches.length ) { 521 | 522 | case 1: 523 | _movePrev.copy( _moveCurr ); 524 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 525 | break; 526 | 527 | default: // 2 or more 528 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 529 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 530 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); 531 | 532 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 533 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 534 | _panEnd.copy( getMouseOnScreen( x, y ) ); 535 | break; 536 | 537 | } 538 | 539 | } 540 | 541 | function touchend( event ) { 542 | 543 | if ( _this.enabled === false ) return; 544 | 545 | switch ( event.touches.length ) { 546 | 547 | case 0: 548 | _state = STATE.NONE; 549 | break; 550 | 551 | case 1: 552 | _state = STATE.TOUCH_ROTATE; 553 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 554 | _movePrev.copy( _moveCurr ); 555 | break; 556 | 557 | } 558 | 559 | _this.dispatchEvent( endEvent ); 560 | 561 | } 562 | 563 | function contextmenu( event ) { 564 | 565 | event.preventDefault(); 566 | 567 | } 568 | 569 | this.dispose = function() { 570 | 571 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false ); 572 | this.domElement.removeEventListener( 'mousedown', mousedown, false ); 573 | this.domElement.removeEventListener( 'wheel', mousewheel, false ); 574 | 575 | this.domElement.removeEventListener( 'touchstart', touchstart, false ); 576 | this.domElement.removeEventListener( 'touchend', touchend, false ); 577 | this.domElement.removeEventListener( 'touchmove', touchmove, false ); 578 | 579 | document.removeEventListener( 'mousemove', mousemove, false ); 580 | document.removeEventListener( 'mouseup', mouseup, false ); 581 | 582 | window.removeEventListener( 'keydown', keydown, false ); 583 | window.removeEventListener( 'keyup', keyup, false ); 584 | 585 | }; 586 | 587 | this.domElement.addEventListener( 'contextmenu', contextmenu, false ); 588 | this.domElement.addEventListener( 'mousedown', mousedown, false ); 589 | this.domElement.addEventListener( 'wheel', mousewheel, false ); 590 | 591 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 592 | this.domElement.addEventListener( 'touchend', touchend, false ); 593 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 594 | 595 | window.addEventListener( 'keydown', keydown, false ); 596 | window.addEventListener( 'keyup', keyup, false ); 597 | 598 | this.handleResize(); 599 | 600 | // force an update at start 601 | this.update(); 602 | 603 | }; 604 | 605 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 606 | THREE.TrackballControls.prototype.constructor = THREE.TrackballControls; 607 | -------------------------------------------------------------------------------- /assets/js/lib/dat.gui.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 | 14 | /** @namespace */ 15 | var dat = dat || {}; 16 | 17 | /** @namespace */ 18 | dat.gui = dat.gui || {}; 19 | 20 | /** @namespace */ 21 | dat.utils = dat.utils || {}; 22 | 23 | /** @namespace */ 24 | dat.controllers = dat.controllers || {}; 25 | 26 | /** @namespace */ 27 | dat.dom = dat.dom || {}; 28 | 29 | /** @namespace */ 30 | dat.color = dat.color || {}; 31 | 32 | dat.utils.css = (function () { 33 | return { 34 | load: function (url, doc) { 35 | doc = doc || document; 36 | var link = doc.createElement('link'); 37 | link.type = 'text/css'; 38 | link.rel = 'stylesheet'; 39 | link.href = url; 40 | doc.getElementsByTagName('head')[0].appendChild(link); 41 | }, 42 | inject: function(css, doc) { 43 | doc = doc || document; 44 | var injected = document.createElement('style'); 45 | injected.type = 'text/css'; 46 | injected.innerHTML = css; 47 | doc.getElementsByTagName('head')[0].appendChild(injected); 48 | } 49 | } 50 | })(); 51 | 52 | 53 | dat.utils.common = (function () { 54 | 55 | var ARR_EACH = Array.prototype.forEach; 56 | var ARR_SLICE = Array.prototype.slice; 57 | 58 | /** 59 | * Band-aid methods for things that should be a lot easier in JavaScript. 60 | * Implementation and structure inspired by underscore.js 61 | * http://documentcloud.github.com/underscore/ 62 | */ 63 | 64 | return { 65 | 66 | BREAK: {}, 67 | 68 | extend: function(target) { 69 | 70 | this.each(ARR_SLICE.call(arguments, 1), function(obj) { 71 | 72 | for (var key in obj) 73 | if (!this.isUndefined(obj[key])) 74 | target[key] = obj[key]; 75 | 76 | }, this); 77 | 78 | return target; 79 | 80 | }, 81 | 82 | defaults: function(target) { 83 | 84 | this.each(ARR_SLICE.call(arguments, 1), function(obj) { 85 | 86 | for (var key in obj) 87 | if (this.isUndefined(target[key])) 88 | target[key] = obj[key]; 89 | 90 | }, this); 91 | 92 | return target; 93 | 94 | }, 95 | 96 | compose: function() { 97 | var toCall = ARR_SLICE.call(arguments); 98 | return function() { 99 | var args = ARR_SLICE.call(arguments); 100 | for (var i = toCall.length -1; i >= 0; i--) { 101 | args = [toCall[i].apply(this, args)]; 102 | } 103 | return args[0]; 104 | } 105 | }, 106 | 107 | each: function(obj, itr, scope) { 108 | 109 | 110 | if (ARR_EACH && obj.forEach === ARR_EACH) { 111 | 112 | obj.forEach(itr, scope); 113 | 114 | } else if (obj.length === obj.length + 0) { // Is number but not NaN 115 | 116 | for (var key = 0, l = obj.length; key < l; key++) 117 | if (key in obj && itr.call(scope, obj[key], key) === this.BREAK) 118 | return; 119 | 120 | } else { 121 | 122 | for (var key in obj) 123 | if (itr.call(scope, obj[key], key) === this.BREAK) 124 | return; 125 | 126 | } 127 | 128 | }, 129 | 130 | defer: function(fnc) { 131 | setTimeout(fnc, 0); 132 | }, 133 | 134 | toArray: function(obj) { 135 | if (obj.toArray) return obj.toArray(); 136 | return ARR_SLICE.call(obj); 137 | }, 138 | 139 | isUndefined: function(obj) { 140 | return obj === undefined; 141 | }, 142 | 143 | isNull: function(obj) { 144 | return obj === null; 145 | }, 146 | 147 | isNaN: function(obj) { 148 | return obj !== obj; 149 | }, 150 | 151 | isArray: Array.isArray || function(obj) { 152 | return obj.constructor === Array; 153 | }, 154 | 155 | isObject: function(obj) { 156 | return obj === Object(obj); 157 | }, 158 | 159 | isNumber: function(obj) { 160 | return obj === obj+0; 161 | }, 162 | 163 | isString: function(obj) { 164 | return obj === obj+''; 165 | }, 166 | 167 | isBoolean: function(obj) { 168 | return obj === false || obj === true; 169 | }, 170 | 171 | isFunction: function(obj) { 172 | return Object.prototype.toString.call(obj) === '[object Function]'; 173 | } 174 | 175 | }; 176 | 177 | })(); 178 | 179 | 180 | dat.controllers.Controller = (function (common) { 181 | 182 | /** 183 | * @class An "abstract" class that represents a given property of an object. 184 | * 185 | * @param {Object} object The object to be manipulated 186 | * @param {string} property The name of the property to be manipulated 187 | * 188 | * @member dat.controllers 189 | */ 190 | var Controller = function(object, property) { 191 | 192 | this.initialValue = object[property]; 193 | 194 | /** 195 | * Those who extend this class will put their DOM elements in here. 196 | * @type {DOMElement} 197 | */ 198 | this.domElement = document.createElement('div'); 199 | 200 | /** 201 | * The object to manipulate 202 | * @type {Object} 203 | */ 204 | this.object = object; 205 | 206 | /** 207 | * The name of the property to manipulate 208 | * @type {String} 209 | */ 210 | this.property = property; 211 | 212 | /** 213 | * The function to be called on change. 214 | * @type {Function} 215 | * @ignore 216 | */ 217 | this.__onChange = undefined; 218 | 219 | /** 220 | * The function to be called on finishing change. 221 | * @type {Function} 222 | * @ignore 223 | */ 224 | this.__onFinishChange = undefined; 225 | 226 | }; 227 | 228 | common.extend( 229 | 230 | Controller.prototype, 231 | 232 | /** @lends dat.controllers.Controller.prototype */ 233 | { 234 | 235 | /** 236 | * Specify that a function fire every time someone changes the value with 237 | * this Controller. 238 | * 239 | * @param {Function} fnc This function will be called whenever the value 240 | * is modified via this Controller. 241 | * @returns {dat.controllers.Controller} this 242 | */ 243 | onChange: function(fnc) { 244 | this.__onChange = fnc; 245 | return this; 246 | }, 247 | 248 | /** 249 | * Specify that a function fire every time someone "finishes" changing 250 | * the value wih this Controller. Useful for values that change 251 | * incrementally like numbers or strings. 252 | * 253 | * @param {Function} fnc This function will be called whenever 254 | * someone "finishes" changing the value via this Controller. 255 | * @returns {dat.controllers.Controller} this 256 | */ 257 | onFinishChange: function(fnc) { 258 | this.__onFinishChange = fnc; 259 | return this; 260 | }, 261 | 262 | /** 263 | * Change the value of object[property] 264 | * 265 | * @param {Object} newValue The new value of object[property] 266 | */ 267 | setValue: function(newValue) { 268 | this.object[this.property] = newValue; 269 | if (this.__onChange) { 270 | this.__onChange.call(this, newValue); 271 | } 272 | this.updateDisplay(); 273 | return this; 274 | }, 275 | 276 | /** 277 | * Gets the value of object[property] 278 | * 279 | * @returns {Object} The current value of object[property] 280 | */ 281 | getValue: function() { 282 | return this.object[this.property]; 283 | }, 284 | 285 | /** 286 | * Refreshes the visual display of a Controller in order to keep sync 287 | * with the object's current value. 288 | * @returns {dat.controllers.Controller} this 289 | */ 290 | updateDisplay: function() { 291 | return this; 292 | }, 293 | 294 | /** 295 | * @returns {Boolean} true if the value has deviated from initialValue 296 | */ 297 | isModified: function() { 298 | return this.initialValue !== this.getValue() 299 | } 300 | 301 | } 302 | 303 | ); 304 | 305 | return Controller; 306 | 307 | 308 | })(dat.utils.common); 309 | 310 | 311 | dat.dom.dom = (function (common) { 312 | 313 | var EVENT_MAP = { 314 | 'HTMLEvents': ['change'], 315 | 'MouseEvents': ['click','mousemove','mousedown','mouseup', 'mouseover'], 316 | 'KeyboardEvents': ['keydown'] 317 | }; 318 | 319 | var EVENT_MAP_INV = {}; 320 | common.each(EVENT_MAP, function(v, k) { 321 | common.each(v, function(e) { 322 | EVENT_MAP_INV[e] = k; 323 | }); 324 | }); 325 | 326 | var CSS_VALUE_PIXELS = /(\d+(\.\d+)?)px/; 327 | 328 | function cssValueToPixels(val) { 329 | 330 | if (val === '0' || common.isUndefined(val)) return 0; 331 | 332 | var match = val.match(CSS_VALUE_PIXELS); 333 | 334 | if (!common.isNull(match)) { 335 | return parseFloat(match[1]); 336 | } 337 | 338 | // TODO ...ems? %? 339 | 340 | return 0; 341 | 342 | } 343 | 344 | /** 345 | * @namespace 346 | * @member dat.dom 347 | */ 348 | var dom = { 349 | 350 | /** 351 | * 352 | * @param elem 353 | * @param selectable 354 | */ 355 | makeSelectable: function(elem, selectable) { 356 | 357 | if (elem === undefined || elem.style === undefined) return; 358 | 359 | elem.onselectstart = selectable ? function() { 360 | return false; 361 | } : function() { 362 | }; 363 | 364 | elem.style.MozUserSelect = selectable ? 'auto' : 'none'; 365 | elem.style.KhtmlUserSelect = selectable ? 'auto' : 'none'; 366 | elem.unselectable = selectable ? 'on' : 'off'; 367 | 368 | }, 369 | 370 | /** 371 | * 372 | * @param elem 373 | * @param horizontal 374 | * @param vertical 375 | */ 376 | makeFullscreen: function(elem, horizontal, vertical) { 377 | 378 | if (common.isUndefined(horizontal)) horizontal = true; 379 | if (common.isUndefined(vertical)) vertical = true; 380 | 381 | elem.style.position = 'absolute'; 382 | 383 | if (horizontal) { 384 | elem.style.left = 0; 385 | elem.style.right = 0; 386 | } 387 | if (vertical) { 388 | elem.style.top = 0; 389 | elem.style.bottom = 0; 390 | } 391 | 392 | }, 393 | 394 | /** 395 | * 396 | * @param elem 397 | * @param eventType 398 | * @param params 399 | */ 400 | fakeEvent: function(elem, eventType, params, aux) { 401 | params = params || {}; 402 | var className = EVENT_MAP_INV[eventType]; 403 | if (!className) { 404 | throw new Error('Event type ' + eventType + ' not supported.'); 405 | } 406 | var evt = document.createEvent(className); 407 | switch (className) { 408 | case 'MouseEvents': 409 | var clientX = params.x || params.clientX || 0; 410 | var clientY = params.y || params.clientY || 0; 411 | evt.initMouseEvent(eventType, params.bubbles || false, 412 | params.cancelable || true, window, params.clickCount || 1, 413 | 0, //screen X 414 | 0, //screen Y 415 | clientX, //client X 416 | clientY, //client Y 417 | false, false, false, false, 0, null); 418 | break; 419 | case 'KeyboardEvents': 420 | var init = evt.initKeyboardEvent || evt.initKeyEvent; // webkit || moz 421 | common.defaults(params, { 422 | cancelable: true, 423 | ctrlKey: false, 424 | altKey: false, 425 | shiftKey: false, 426 | metaKey: false, 427 | keyCode: undefined, 428 | charCode: undefined 429 | }); 430 | init(eventType, params.bubbles || false, 431 | params.cancelable, window, 432 | params.ctrlKey, params.altKey, 433 | params.shiftKey, params.metaKey, 434 | params.keyCode, params.charCode); 435 | break; 436 | default: 437 | evt.initEvent(eventType, params.bubbles || false, 438 | params.cancelable || true); 439 | break; 440 | } 441 | common.defaults(evt, aux); 442 | elem.dispatchEvent(evt); 443 | }, 444 | 445 | /** 446 | * 447 | * @param elem 448 | * @param event 449 | * @param func 450 | * @param bool 451 | */ 452 | bind: function(elem, event, func, bool) { 453 | bool = bool || false; 454 | if (elem.addEventListener) 455 | elem.addEventListener(event, func, bool); 456 | else if (elem.attachEvent) 457 | elem.attachEvent('on' + event, func); 458 | return dom; 459 | }, 460 | 461 | /** 462 | * 463 | * @param elem 464 | * @param event 465 | * @param func 466 | * @param bool 467 | */ 468 | unbind: function(elem, event, func, bool) { 469 | bool = bool || false; 470 | if (elem.removeEventListener) 471 | elem.removeEventListener(event, func, bool); 472 | else if (elem.detachEvent) 473 | elem.detachEvent('on' + event, func); 474 | return dom; 475 | }, 476 | 477 | /** 478 | * 479 | * @param elem 480 | * @param className 481 | */ 482 | addClass: function(elem, className) { 483 | if (elem.className === undefined) { 484 | elem.className = className; 485 | } else if (elem.className !== className) { 486 | var classes = elem.className.split(/ +/); 487 | if (classes.indexOf(className) == -1) { 488 | classes.push(className); 489 | elem.className = classes.join(' ').replace(/^\s+/, '').replace(/\s+$/, ''); 490 | } 491 | } 492 | return dom; 493 | }, 494 | 495 | /** 496 | * 497 | * @param elem 498 | * @param className 499 | */ 500 | removeClass: function(elem, className) { 501 | if (className) { 502 | if (elem.className === undefined) { 503 | // elem.className = className; 504 | } else if (elem.className === className) { 505 | elem.removeAttribute('class'); 506 | } else { 507 | var classes = elem.className.split(/ +/); 508 | var index = classes.indexOf(className); 509 | if (index != -1) { 510 | classes.splice(index, 1); 511 | elem.className = classes.join(' '); 512 | } 513 | } 514 | } else { 515 | elem.className = undefined; 516 | } 517 | return dom; 518 | }, 519 | 520 | hasClass: function(elem, className) { 521 | return new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)').test(elem.className) || false; 522 | }, 523 | 524 | /** 525 | * 526 | * @param elem 527 | */ 528 | getWidth: function(elem) { 529 | 530 | var style = getComputedStyle(elem); 531 | 532 | return cssValueToPixels(style['border-left-width']) + 533 | cssValueToPixels(style['border-right-width']) + 534 | cssValueToPixels(style['padding-left']) + 535 | cssValueToPixels(style['padding-right']) + 536 | cssValueToPixels(style['width']); 537 | }, 538 | 539 | /** 540 | * 541 | * @param elem 542 | */ 543 | getHeight: function(elem) { 544 | 545 | var style = getComputedStyle(elem); 546 | 547 | return cssValueToPixels(style['border-top-width']) + 548 | cssValueToPixels(style['border-bottom-width']) + 549 | cssValueToPixels(style['padding-top']) + 550 | cssValueToPixels(style['padding-bottom']) + 551 | cssValueToPixels(style['height']); 552 | }, 553 | 554 | /** 555 | * 556 | * @param elem 557 | */ 558 | getOffset: function(elem) { 559 | var offset = {left: 0, top:0}; 560 | if (elem.offsetParent) { 561 | do { 562 | offset.left += elem.offsetLeft; 563 | offset.top += elem.offsetTop; 564 | } while (elem = elem.offsetParent); 565 | } 566 | return offset; 567 | }, 568 | 569 | // http://stackoverflow.com/posts/2684561/revisions 570 | /** 571 | * 572 | * @param elem 573 | */ 574 | isActive: function(elem) { 575 | return elem === document.activeElement && ( elem.type || elem.href ); 576 | } 577 | 578 | }; 579 | 580 | return dom; 581 | 582 | })(dat.utils.common); 583 | 584 | 585 | dat.controllers.OptionController = (function (Controller, dom, common) { 586 | 587 | /** 588 | * @class Provides a select input to alter the property of an object, using a 589 | * list of accepted values. 590 | * 591 | * @extends dat.controllers.Controller 592 | * 593 | * @param {Object} object The object to be manipulated 594 | * @param {string} property The name of the property to be manipulated 595 | * @param {Object|string[]} options A map of labels to acceptable values, or 596 | * a list of acceptable string values. 597 | * 598 | * @member dat.controllers 599 | */ 600 | var OptionController = function(object, property, options) { 601 | 602 | OptionController.superclass.call(this, object, property); 603 | 604 | var _this = this; 605 | 606 | /** 607 | * The drop down menu 608 | * @ignore 609 | */ 610 | this.__select = document.createElement('select'); 611 | 612 | if (common.isArray(options)) { 613 | var map = {}; 614 | common.each(options, function(element) { 615 | map[element] = element; 616 | }); 617 | options = map; 618 | } 619 | 620 | common.each(options, function(value, key) { 621 | 622 | var opt = document.createElement('option'); 623 | opt.innerHTML = key; 624 | opt.setAttribute('value', value); 625 | _this.__select.appendChild(opt); 626 | 627 | }); 628 | 629 | // Acknowledge original value 630 | this.updateDisplay(); 631 | 632 | dom.bind(this.__select, 'change', function() { 633 | var desiredValue = this.options[this.selectedIndex].value; 634 | _this.setValue(desiredValue); 635 | }); 636 | 637 | this.domElement.appendChild(this.__select); 638 | 639 | }; 640 | 641 | OptionController.superclass = Controller; 642 | 643 | common.extend( 644 | 645 | OptionController.prototype, 646 | Controller.prototype, 647 | 648 | { 649 | 650 | setValue: function(v) { 651 | var toReturn = OptionController.superclass.prototype.setValue.call(this, v); 652 | if (this.__onFinishChange) { 653 | this.__onFinishChange.call(this, this.getValue()); 654 | } 655 | return toReturn; 656 | }, 657 | 658 | updateDisplay: function() { 659 | this.__select.value = this.getValue(); 660 | return OptionController.superclass.prototype.updateDisplay.call(this); 661 | } 662 | 663 | } 664 | 665 | ); 666 | 667 | return OptionController; 668 | 669 | })(dat.controllers.Controller, 670 | dat.dom.dom, 671 | dat.utils.common); 672 | 673 | 674 | dat.controllers.NumberController = (function (Controller, common) { 675 | 676 | /** 677 | * @class Represents a given property of an object that is a number. 678 | * 679 | * @extends dat.controllers.Controller 680 | * 681 | * @param {Object} object The object to be manipulated 682 | * @param {string} property The name of the property to be manipulated 683 | * @param {Object} [params] Optional parameters 684 | * @param {Number} [params.min] Minimum allowed value 685 | * @param {Number} [params.max] Maximum allowed value 686 | * @param {Number} [params.step] Increment by which to change value 687 | * 688 | * @member dat.controllers 689 | */ 690 | var NumberController = function(object, property, params) { 691 | 692 | NumberController.superclass.call(this, object, property); 693 | 694 | params = params || {}; 695 | 696 | this.__min = params.min; 697 | this.__max = params.max; 698 | this.__step = params.step; 699 | 700 | if (common.isUndefined(this.__step)) { 701 | 702 | if (this.initialValue == 0) { 703 | this.__impliedStep = 1; // What are we, psychics? 704 | } else { 705 | // Hey Doug, check this out. 706 | this.__impliedStep = Math.pow(10, Math.floor(Math.log(this.initialValue)/Math.LN10))/10; 707 | } 708 | 709 | } else { 710 | 711 | this.__impliedStep = this.__step; 712 | 713 | } 714 | 715 | this.__precision = numDecimals(this.__impliedStep); 716 | 717 | 718 | }; 719 | 720 | NumberController.superclass = Controller; 721 | 722 | common.extend( 723 | 724 | NumberController.prototype, 725 | Controller.prototype, 726 | 727 | /** @lends dat.controllers.NumberController.prototype */ 728 | { 729 | 730 | setValue: function(v) { 731 | 732 | if (this.__min !== undefined && v < this.__min) { 733 | v = this.__min; 734 | } else if (this.__max !== undefined && v > this.__max) { 735 | v = this.__max; 736 | } 737 | 738 | if (this.__step !== undefined && v % this.__step != 0) { 739 | v = Math.round(v / this.__step) * this.__step; 740 | } 741 | 742 | return NumberController.superclass.prototype.setValue.call(this, v); 743 | 744 | }, 745 | 746 | /** 747 | * Specify a minimum value for object[property]. 748 | * 749 | * @param {Number} minValue The minimum value for 750 | * object[property] 751 | * @returns {dat.controllers.NumberController} this 752 | */ 753 | min: function(v) { 754 | this.__min = v; 755 | return this; 756 | }, 757 | 758 | /** 759 | * Specify a maximum value for object[property]. 760 | * 761 | * @param {Number} maxValue The maximum value for 762 | * object[property] 763 | * @returns {dat.controllers.NumberController} this 764 | */ 765 | max: function(v) { 766 | this.__max = v; 767 | return this; 768 | }, 769 | 770 | /** 771 | * Specify a step value that dat.controllers.NumberController 772 | * increments by. 773 | * 774 | * @param {Number} stepValue The step value for 775 | * dat.controllers.NumberController 776 | * @default if minimum and maximum specified increment is 1% of the 777 | * difference otherwise stepValue is 1 778 | * @returns {dat.controllers.NumberController} this 779 | */ 780 | step: function(v) { 781 | this.__step = v; 782 | return this; 783 | } 784 | 785 | } 786 | 787 | ); 788 | 789 | function numDecimals(x) { 790 | x = x.toString(); 791 | if (x.indexOf('.') > -1) { 792 | return x.length - x.indexOf('.') - 1; 793 | } else { 794 | return 0; 795 | } 796 | } 797 | 798 | return NumberController; 799 | 800 | })(dat.controllers.Controller, 801 | dat.utils.common); 802 | 803 | 804 | dat.controllers.NumberControllerBox = (function (NumberController, dom, common) { 805 | 806 | /** 807 | * @class Represents a given property of an object that is a number and 808 | * provides an input element with which to manipulate it. 809 | * 810 | * @extends dat.controllers.Controller 811 | * @extends dat.controllers.NumberController 812 | * 813 | * @param {Object} object The object to be manipulated 814 | * @param {string} property The name of the property to be manipulated 815 | * @param {Object} [params] Optional parameters 816 | * @param {Number} [params.min] Minimum allowed value 817 | * @param {Number} [params.max] Maximum allowed value 818 | * @param {Number} [params.step] Increment by which to change value 819 | * 820 | * @member dat.controllers 821 | */ 822 | var NumberControllerBox = function(object, property, params) { 823 | 824 | this.__truncationSuspended = false; 825 | 826 | NumberControllerBox.superclass.call(this, object, property, params); 827 | 828 | var _this = this; 829 | 830 | /** 831 | * {Number} Previous mouse y position 832 | * @ignore 833 | */ 834 | var prev_y; 835 | 836 | this.__input = document.createElement('input'); 837 | this.__input.setAttribute('type', 'text'); 838 | 839 | // Makes it so manually specified values are not truncated. 840 | 841 | dom.bind(this.__input, 'change', onChange); 842 | dom.bind(this.__input, 'blur', onBlur); 843 | dom.bind(this.__input, 'mousedown', onMouseDown); 844 | dom.bind(this.__input, 'keydown', function(e) { 845 | 846 | // When pressing entire, you can be as precise as you want. 847 | if (e.keyCode === 13) { 848 | _this.__truncationSuspended = true; 849 | this.blur(); 850 | _this.__truncationSuspended = false; 851 | } 852 | 853 | }); 854 | 855 | function onChange() { 856 | var attempted = parseFloat(_this.__input.value); 857 | if (!common.isNaN(attempted)) _this.setValue(attempted); 858 | } 859 | 860 | function onBlur() { 861 | onChange(); 862 | if (_this.__onFinishChange) { 863 | _this.__onFinishChange.call(_this, _this.getValue()); 864 | } 865 | } 866 | 867 | function onMouseDown(e) { 868 | dom.bind(window, 'mousemove', onMouseDrag); 869 | dom.bind(window, 'mouseup', onMouseUp); 870 | prev_y = e.clientY; 871 | } 872 | 873 | function onMouseDrag(e) { 874 | 875 | var diff = prev_y - e.clientY; 876 | _this.setValue(_this.getValue() + diff * _this.__impliedStep); 877 | 878 | prev_y = e.clientY; 879 | 880 | } 881 | 882 | function onMouseUp() { 883 | dom.unbind(window, 'mousemove', onMouseDrag); 884 | dom.unbind(window, 'mouseup', onMouseUp); 885 | } 886 | 887 | this.updateDisplay(); 888 | 889 | this.domElement.appendChild(this.__input); 890 | 891 | }; 892 | 893 | NumberControllerBox.superclass = NumberController; 894 | 895 | common.extend( 896 | 897 | NumberControllerBox.prototype, 898 | NumberController.prototype, 899 | 900 | { 901 | 902 | updateDisplay: function() { 903 | 904 | this.__input.value = this.__truncationSuspended ? this.getValue() : roundToDecimal(this.getValue(), this.__precision); 905 | return NumberControllerBox.superclass.prototype.updateDisplay.call(this); 906 | } 907 | 908 | } 909 | 910 | ); 911 | 912 | function roundToDecimal(value, decimals) { 913 | var tenTo = Math.pow(10, decimals); 914 | return Math.round(value * tenTo) / tenTo; 915 | } 916 | 917 | return NumberControllerBox; 918 | 919 | })(dat.controllers.NumberController, 920 | dat.dom.dom, 921 | dat.utils.common); 922 | 923 | 924 | dat.controllers.NumberControllerSlider = (function (NumberController, dom, css, common, styleSheet) { 925 | 926 | /** 927 | * @class Represents a given property of an object that is a number, contains 928 | * a minimum and maximum, and provides a slider element with which to 929 | * manipulate it. It should be noted that the slider element is made up of 930 | * <div> tags, not the html5 931 | * <slider> element. 932 | * 933 | * @extends dat.controllers.Controller 934 | * @extends dat.controllers.NumberController 935 | * 936 | * @param {Object} object The object to be manipulated 937 | * @param {string} property The name of the property to be manipulated 938 | * @param {Number} minValue Minimum allowed value 939 | * @param {Number} maxValue Maximum allowed value 940 | * @param {Number} stepValue Increment by which to change value 941 | * 942 | * @member dat.controllers 943 | */ 944 | var NumberControllerSlider = function(object, property, min, max, step) { 945 | 946 | NumberControllerSlider.superclass.call(this, object, property, { min: min, max: max, step: step }); 947 | 948 | var _this = this; 949 | 950 | this.__background = document.createElement('div'); 951 | this.__foreground = document.createElement('div'); 952 | 953 | 954 | 955 | dom.bind(this.__background, 'mousedown', onMouseDown); 956 | 957 | dom.addClass(this.__background, 'slider'); 958 | dom.addClass(this.__foreground, 'slider-fg'); 959 | 960 | function onMouseDown(e) { 961 | 962 | dom.bind(window, 'mousemove', onMouseDrag); 963 | dom.bind(window, 'mouseup', onMouseUp); 964 | 965 | onMouseDrag(e); 966 | } 967 | 968 | function onMouseDrag(e) { 969 | 970 | e.preventDefault(); 971 | 972 | var offset = dom.getOffset(_this.__background); 973 | var width = dom.getWidth(_this.__background); 974 | 975 | _this.setValue( 976 | map(e.clientX, offset.left, offset.left + width, _this.__min, _this.__max) 977 | ); 978 | 979 | return false; 980 | 981 | } 982 | 983 | function onMouseUp() { 984 | dom.unbind(window, 'mousemove', onMouseDrag); 985 | dom.unbind(window, 'mouseup', onMouseUp); 986 | if (_this.__onFinishChange) { 987 | _this.__onFinishChange.call(_this, _this.getValue()); 988 | } 989 | } 990 | 991 | this.updateDisplay(); 992 | 993 | this.__background.appendChild(this.__foreground); 994 | this.domElement.appendChild(this.__background); 995 | 996 | }; 997 | 998 | NumberControllerSlider.superclass = NumberController; 999 | 1000 | /** 1001 | * Injects default stylesheet for slider elements. 1002 | */ 1003 | NumberControllerSlider.useDefaultStyles = function() { 1004 | css.inject(styleSheet); 1005 | }; 1006 | 1007 | common.extend( 1008 | 1009 | NumberControllerSlider.prototype, 1010 | NumberController.prototype, 1011 | 1012 | { 1013 | 1014 | updateDisplay: function() { 1015 | var pct = (this.getValue() - this.__min)/(this.__max - this.__min); 1016 | this.__foreground.style.width = pct*100+'%'; 1017 | return NumberControllerSlider.superclass.prototype.updateDisplay.call(this); 1018 | } 1019 | 1020 | } 1021 | 1022 | 1023 | 1024 | ); 1025 | 1026 | function map(v, i1, i2, o1, o2) { 1027 | return o1 + (o2 - o1) * ((v - i1) / (i2 - i1)); 1028 | } 1029 | 1030 | return NumberControllerSlider; 1031 | 1032 | })(dat.controllers.NumberController, 1033 | dat.dom.dom, 1034 | dat.utils.css, 1035 | dat.utils.common, 1036 | ".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}"); 1037 | 1038 | 1039 | dat.controllers.FunctionController = (function (Controller, dom, common) { 1040 | 1041 | /** 1042 | * @class Provides a GUI interface to fire a specified method, a property of an object. 1043 | * 1044 | * @extends dat.controllers.Controller 1045 | * 1046 | * @param {Object} object The object to be manipulated 1047 | * @param {string} property The name of the property to be manipulated 1048 | * 1049 | * @member dat.controllers 1050 | */ 1051 | var FunctionController = function(object, property, text) { 1052 | 1053 | FunctionController.superclass.call(this, object, property); 1054 | 1055 | var _this = this; 1056 | 1057 | this.__button = document.createElement('div'); 1058 | this.__button.innerHTML = text === undefined ? 'Fire' : text; 1059 | dom.bind(this.__button, 'click', function(e) { 1060 | e.preventDefault(); 1061 | _this.fire(); 1062 | return false; 1063 | }); 1064 | 1065 | dom.addClass(this.__button, 'button'); 1066 | 1067 | this.domElement.appendChild(this.__button); 1068 | 1069 | 1070 | }; 1071 | 1072 | FunctionController.superclass = Controller; 1073 | 1074 | common.extend( 1075 | 1076 | FunctionController.prototype, 1077 | Controller.prototype, 1078 | { 1079 | 1080 | fire: function() { 1081 | if (this.__onChange) { 1082 | this.__onChange.call(this); 1083 | } 1084 | if (this.__onFinishChange) { 1085 | this.__onFinishChange.call(this, this.getValue()); 1086 | } 1087 | this.getValue().call(this.object); 1088 | } 1089 | } 1090 | 1091 | ); 1092 | 1093 | return FunctionController; 1094 | 1095 | })(dat.controllers.Controller, 1096 | dat.dom.dom, 1097 | dat.utils.common); 1098 | 1099 | 1100 | dat.controllers.BooleanController = (function (Controller, dom, common) { 1101 | 1102 | /** 1103 | * @class Provides a checkbox input to alter the boolean property of an object. 1104 | * @extends dat.controllers.Controller 1105 | * 1106 | * @param {Object} object The object to be manipulated 1107 | * @param {string} property The name of the property to be manipulated 1108 | * 1109 | * @member dat.controllers 1110 | */ 1111 | var BooleanController = function(object, property) { 1112 | 1113 | BooleanController.superclass.call(this, object, property); 1114 | 1115 | var _this = this; 1116 | this.__prev = this.getValue(); 1117 | 1118 | this.__checkbox = document.createElement('input'); 1119 | this.__checkbox.setAttribute('type', 'checkbox'); 1120 | 1121 | 1122 | dom.bind(this.__checkbox, 'change', onChange, false); 1123 | 1124 | this.domElement.appendChild(this.__checkbox); 1125 | 1126 | // Match original value 1127 | this.updateDisplay(); 1128 | 1129 | function onChange() { 1130 | _this.setValue(!_this.__prev); 1131 | } 1132 | 1133 | }; 1134 | 1135 | BooleanController.superclass = Controller; 1136 | 1137 | common.extend( 1138 | 1139 | BooleanController.prototype, 1140 | Controller.prototype, 1141 | 1142 | { 1143 | 1144 | setValue: function(v) { 1145 | var toReturn = BooleanController.superclass.prototype.setValue.call(this, v); 1146 | if (this.__onFinishChange) { 1147 | this.__onFinishChange.call(this, this.getValue()); 1148 | } 1149 | this.__prev = this.getValue(); 1150 | return toReturn; 1151 | }, 1152 | 1153 | updateDisplay: function() { 1154 | 1155 | if (this.getValue() === true) { 1156 | this.__checkbox.setAttribute('checked', 'checked'); 1157 | this.__checkbox.checked = true; 1158 | } else { 1159 | this.__checkbox.checked = false; 1160 | } 1161 | 1162 | return BooleanController.superclass.prototype.updateDisplay.call(this); 1163 | 1164 | } 1165 | 1166 | 1167 | } 1168 | 1169 | ); 1170 | 1171 | return BooleanController; 1172 | 1173 | })(dat.controllers.Controller, 1174 | dat.dom.dom, 1175 | dat.utils.common); 1176 | 1177 | 1178 | dat.color.toString = (function (common) { 1179 | 1180 | return function(color) { 1181 | 1182 | if (color.a == 1 || common.isUndefined(color.a)) { 1183 | 1184 | var s = color.hex.toString(16); 1185 | while (s.length < 6) { 1186 | s = '0' + s; 1187 | } 1188 | 1189 | return '#' + s; 1190 | 1191 | } else { 1192 | 1193 | return 'rgba(' + Math.round(color.r) + ',' + Math.round(color.g) + ',' + Math.round(color.b) + ',' + color.a + ')'; 1194 | 1195 | } 1196 | 1197 | } 1198 | 1199 | })(dat.utils.common); 1200 | 1201 | 1202 | dat.color.interpret = (function (toString, common) { 1203 | 1204 | var result, toReturn; 1205 | 1206 | var interpret = function() { 1207 | 1208 | toReturn = false; 1209 | 1210 | var original = arguments.length > 1 ? common.toArray(arguments) : arguments[0]; 1211 | 1212 | common.each(INTERPRETATIONS, function(family) { 1213 | 1214 | if (family.litmus(original)) { 1215 | 1216 | common.each(family.conversions, function(conversion, conversionName) { 1217 | 1218 | result = conversion.read(original); 1219 | 1220 | if (toReturn === false && result !== false) { 1221 | toReturn = result; 1222 | result.conversionName = conversionName; 1223 | result.conversion = conversion; 1224 | return common.BREAK; 1225 | 1226 | } 1227 | 1228 | }); 1229 | 1230 | return common.BREAK; 1231 | 1232 | } 1233 | 1234 | }); 1235 | 1236 | return toReturn; 1237 | 1238 | }; 1239 | 1240 | var INTERPRETATIONS = [ 1241 | 1242 | // Strings 1243 | { 1244 | 1245 | litmus: common.isString, 1246 | 1247 | conversions: { 1248 | 1249 | THREE_CHAR_HEX: { 1250 | 1251 | read: function(original) { 1252 | 1253 | var test = original.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i); 1254 | if (test === null) return false; 1255 | 1256 | return { 1257 | space: 'HEX', 1258 | hex: parseInt( 1259 | '0x' + 1260 | test[1].toString() + test[1].toString() + 1261 | test[2].toString() + test[2].toString() + 1262 | test[3].toString() + test[3].toString()) 1263 | }; 1264 | 1265 | }, 1266 | 1267 | write: toString 1268 | 1269 | }, 1270 | 1271 | SIX_CHAR_HEX: { 1272 | 1273 | read: function(original) { 1274 | 1275 | var test = original.match(/^#([A-F0-9]{6})$/i); 1276 | if (test === null) return false; 1277 | 1278 | return { 1279 | space: 'HEX', 1280 | hex: parseInt('0x' + test[1].toString()) 1281 | }; 1282 | 1283 | }, 1284 | 1285 | write: toString 1286 | 1287 | }, 1288 | 1289 | CSS_RGB: { 1290 | 1291 | read: function(original) { 1292 | 1293 | var test = original.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 1294 | if (test === null) return false; 1295 | 1296 | return { 1297 | space: 'RGB', 1298 | r: parseFloat(test[1]), 1299 | g: parseFloat(test[2]), 1300 | b: parseFloat(test[3]) 1301 | }; 1302 | 1303 | }, 1304 | 1305 | write: toString 1306 | 1307 | }, 1308 | 1309 | CSS_RGBA: { 1310 | 1311 | read: function(original) { 1312 | 1313 | var test = original.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/); 1314 | if (test === null) return false; 1315 | 1316 | return { 1317 | space: 'RGB', 1318 | r: parseFloat(test[1]), 1319 | g: parseFloat(test[2]), 1320 | b: parseFloat(test[3]), 1321 | a: parseFloat(test[4]) 1322 | }; 1323 | 1324 | }, 1325 | 1326 | write: toString 1327 | 1328 | } 1329 | 1330 | } 1331 | 1332 | }, 1333 | 1334 | // Numbers 1335 | { 1336 | 1337 | litmus: common.isNumber, 1338 | 1339 | conversions: { 1340 | 1341 | HEX: { 1342 | read: function(original) { 1343 | return { 1344 | space: 'HEX', 1345 | hex: original, 1346 | conversionName: 'HEX' 1347 | } 1348 | }, 1349 | 1350 | write: function(color) { 1351 | return color.hex; 1352 | } 1353 | } 1354 | 1355 | } 1356 | 1357 | }, 1358 | 1359 | // Arrays 1360 | { 1361 | 1362 | litmus: common.isArray, 1363 | 1364 | conversions: { 1365 | 1366 | RGB_ARRAY: { 1367 | read: function(original) { 1368 | if (original.length != 3) return false; 1369 | return { 1370 | space: 'RGB', 1371 | r: original[0], 1372 | g: original[1], 1373 | b: original[2] 1374 | }; 1375 | }, 1376 | 1377 | write: function(color) { 1378 | return [color.r, color.g, color.b]; 1379 | } 1380 | 1381 | }, 1382 | 1383 | RGBA_ARRAY: { 1384 | read: function(original) { 1385 | if (original.length != 4) return false; 1386 | return { 1387 | space: 'RGB', 1388 | r: original[0], 1389 | g: original[1], 1390 | b: original[2], 1391 | a: original[3] 1392 | }; 1393 | }, 1394 | 1395 | write: function(color) { 1396 | return [color.r, color.g, color.b, color.a]; 1397 | } 1398 | 1399 | } 1400 | 1401 | } 1402 | 1403 | }, 1404 | 1405 | // Objects 1406 | { 1407 | 1408 | litmus: common.isObject, 1409 | 1410 | conversions: { 1411 | 1412 | RGBA_OBJ: { 1413 | read: function(original) { 1414 | if (common.isNumber(original.r) && 1415 | common.isNumber(original.g) && 1416 | common.isNumber(original.b) && 1417 | common.isNumber(original.a)) { 1418 | return { 1419 | space: 'RGB', 1420 | r: original.r, 1421 | g: original.g, 1422 | b: original.b, 1423 | a: original.a 1424 | } 1425 | } 1426 | return false; 1427 | }, 1428 | 1429 | write: function(color) { 1430 | return { 1431 | r: color.r, 1432 | g: color.g, 1433 | b: color.b, 1434 | a: color.a 1435 | } 1436 | } 1437 | }, 1438 | 1439 | RGB_OBJ: { 1440 | read: function(original) { 1441 | if (common.isNumber(original.r) && 1442 | common.isNumber(original.g) && 1443 | common.isNumber(original.b)) { 1444 | return { 1445 | space: 'RGB', 1446 | r: original.r, 1447 | g: original.g, 1448 | b: original.b 1449 | } 1450 | } 1451 | return false; 1452 | }, 1453 | 1454 | write: function(color) { 1455 | return { 1456 | r: color.r, 1457 | g: color.g, 1458 | b: color.b 1459 | } 1460 | } 1461 | }, 1462 | 1463 | HSVA_OBJ: { 1464 | read: function(original) { 1465 | if (common.isNumber(original.h) && 1466 | common.isNumber(original.s) && 1467 | common.isNumber(original.v) && 1468 | common.isNumber(original.a)) { 1469 | return { 1470 | space: 'HSV', 1471 | h: original.h, 1472 | s: original.s, 1473 | v: original.v, 1474 | a: original.a 1475 | } 1476 | } 1477 | return false; 1478 | }, 1479 | 1480 | write: function(color) { 1481 | return { 1482 | h: color.h, 1483 | s: color.s, 1484 | v: color.v, 1485 | a: color.a 1486 | } 1487 | } 1488 | }, 1489 | 1490 | HSV_OBJ: { 1491 | read: function(original) { 1492 | if (common.isNumber(original.h) && 1493 | common.isNumber(original.s) && 1494 | common.isNumber(original.v)) { 1495 | return { 1496 | space: 'HSV', 1497 | h: original.h, 1498 | s: original.s, 1499 | v: original.v 1500 | } 1501 | } 1502 | return false; 1503 | }, 1504 | 1505 | write: function(color) { 1506 | return { 1507 | h: color.h, 1508 | s: color.s, 1509 | v: color.v 1510 | } 1511 | } 1512 | 1513 | } 1514 | 1515 | } 1516 | 1517 | } 1518 | 1519 | 1520 | ]; 1521 | 1522 | return interpret; 1523 | 1524 | 1525 | })(dat.color.toString, 1526 | dat.utils.common); 1527 | 1528 | 1529 | dat.GUI = dat.gui.GUI = (function (css, saveDialogueContents, styleSheet, controllerFactory, Controller, BooleanController, FunctionController, NumberControllerBox, NumberControllerSlider, OptionController, ColorController, requestAnimationFrame, CenteredDiv, dom, common) { 1530 | 1531 | css.inject(styleSheet); 1532 | 1533 | /** Outer-most className for GUI's */ 1534 | var CSS_NAMESPACE = 'dg'; 1535 | 1536 | var HIDE_KEY_CODE = 72; 1537 | 1538 | /** The only value shared between the JS and SCSS. Use caution. */ 1539 | var CLOSE_BUTTON_HEIGHT = 20; 1540 | 1541 | var DEFAULT_DEFAULT_PRESET_NAME = 'Default'; 1542 | 1543 | var SUPPORTS_LOCAL_STORAGE = (function() { 1544 | try { 1545 | return 'localStorage' in window && window['localStorage'] !== null; 1546 | } catch (e) { 1547 | return false; 1548 | } 1549 | })(); 1550 | 1551 | var SAVE_DIALOGUE; 1552 | 1553 | /** Have we yet to create an autoPlace GUI? */ 1554 | var auto_place_virgin = true; 1555 | 1556 | /** Fixed position div that auto place GUI's go inside */ 1557 | var auto_place_container; 1558 | 1559 | /** Are we hiding the GUI's ? */ 1560 | var hide = false; 1561 | 1562 | /** GUI's which should be hidden */ 1563 | var hideable_guis = []; 1564 | 1565 | /** 1566 | * A lightweight controller library for JavaScript. It allows you to easily 1567 | * manipulate variables and fire functions on the fly. 1568 | * @class 1569 | * 1570 | * @member dat.gui 1571 | * 1572 | * @param {Object} [params] 1573 | * @param {String} [params.name] The name of this GUI. 1574 | * @param {Object} [params.load] JSON object representing the saved state of 1575 | * this GUI. 1576 | * @param {Boolean} [params.auto=true] 1577 | * @param {dat.gui.GUI} [params.parent] The GUI I'm nested in. 1578 | * @param {Boolean} [params.closed] If true, starts closed 1579 | */ 1580 | var GUI = function(params) { 1581 | 1582 | var _this = this; 1583 | 1584 | /** 1585 | * Outermost DOM Element 1586 | * @type DOMElement 1587 | */ 1588 | this.domElement = document.createElement('div'); 1589 | this.__ul = document.createElement('ul'); 1590 | this.domElement.appendChild(this.__ul); 1591 | 1592 | dom.addClass(this.domElement, CSS_NAMESPACE); 1593 | 1594 | /** 1595 | * Nested GUI's by name 1596 | * @ignore 1597 | */ 1598 | this.__folders = {}; 1599 | 1600 | this.__controllers = []; 1601 | 1602 | /** 1603 | * List of objects I'm remembering for save, only used in top level GUI 1604 | * @ignore 1605 | */ 1606 | this.__rememberedObjects = []; 1607 | 1608 | /** 1609 | * Maps the index of remembered objects to a map of controllers, only used 1610 | * in top level GUI. 1611 | * 1612 | * @private 1613 | * @ignore 1614 | * 1615 | * @example 1616 | * [ 1617 | * { 1618 | * propertyName: Controller, 1619 | * anotherPropertyName: Controller 1620 | * }, 1621 | * { 1622 | * propertyName: Controller 1623 | * } 1624 | * ] 1625 | */ 1626 | this.__rememberedObjectIndecesToControllers = []; 1627 | 1628 | this.__listening = []; 1629 | 1630 | params = params || {}; 1631 | 1632 | // Default parameters 1633 | params = common.defaults(params, { 1634 | autoPlace: true, 1635 | width: GUI.DEFAULT_WIDTH 1636 | }); 1637 | 1638 | params = common.defaults(params, { 1639 | resizable: params.autoPlace, 1640 | hideable: params.autoPlace 1641 | }); 1642 | 1643 | 1644 | if (!common.isUndefined(params.load)) { 1645 | 1646 | // Explicit preset 1647 | if (params.preset) params.load.preset = params.preset; 1648 | 1649 | } else { 1650 | 1651 | params.load = { preset: DEFAULT_DEFAULT_PRESET_NAME }; 1652 | 1653 | } 1654 | 1655 | if (common.isUndefined(params.parent) && params.hideable) { 1656 | hideable_guis.push(this); 1657 | } 1658 | 1659 | // Only root level GUI's are resizable. 1660 | params.resizable = common.isUndefined(params.parent) && params.resizable; 1661 | 1662 | 1663 | if (params.autoPlace && common.isUndefined(params.scrollable)) { 1664 | params.scrollable = true; 1665 | } 1666 | // params.scrollable = common.isUndefined(params.parent) && params.scrollable === true; 1667 | 1668 | // Not part of params because I don't want people passing this in via 1669 | // constructor. Should be a 'remembered' value. 1670 | var use_local_storage = 1671 | SUPPORTS_LOCAL_STORAGE && 1672 | localStorage.getItem(getLocalStorageHash(this, 'isLocal')) === 'true'; 1673 | 1674 | Object.defineProperties(this, 1675 | 1676 | /** @lends dat.gui.GUI.prototype */ 1677 | { 1678 | 1679 | /** 1680 | * The parent GUI 1681 | * @type dat.gui.GUI 1682 | */ 1683 | parent: { 1684 | get: function() { 1685 | return params.parent; 1686 | } 1687 | }, 1688 | 1689 | scrollable: { 1690 | get: function() { 1691 | return params.scrollable; 1692 | } 1693 | }, 1694 | 1695 | /** 1696 | * Handles GUI's element placement for you 1697 | * @type Boolean 1698 | */ 1699 | autoPlace: { 1700 | get: function() { 1701 | return params.autoPlace; 1702 | } 1703 | }, 1704 | 1705 | /** 1706 | * The identifier for a set of saved values 1707 | * @type String 1708 | */ 1709 | preset: { 1710 | 1711 | get: function() { 1712 | if (_this.parent) { 1713 | return _this.getRoot().preset; 1714 | } else { 1715 | return params.load.preset; 1716 | } 1717 | }, 1718 | 1719 | set: function(v) { 1720 | if (_this.parent) { 1721 | _this.getRoot().preset = v; 1722 | } else { 1723 | params.load.preset = v; 1724 | } 1725 | setPresetSelectIndex(this); 1726 | _this.revert(); 1727 | } 1728 | 1729 | }, 1730 | 1731 | /** 1732 | * The width of GUI element 1733 | * @type Number 1734 | */ 1735 | width: { 1736 | get: function() { 1737 | return params.width; 1738 | }, 1739 | set: function(v) { 1740 | params.width = v; 1741 | setWidth(_this, v); 1742 | } 1743 | }, 1744 | 1745 | /** 1746 | * The name of GUI. Used for folders. i.e 1747 | * a folder's name 1748 | * @type String 1749 | */ 1750 | name: { 1751 | get: function() { 1752 | return params.name; 1753 | }, 1754 | set: function(v) { 1755 | // TODO Check for collisions among sibling folders 1756 | params.name = v; 1757 | if (title_row_name) { 1758 | title_row_name.innerHTML = params.name; 1759 | } 1760 | } 1761 | }, 1762 | 1763 | /** 1764 | * Whether the GUI is collapsed or not 1765 | * @type Boolean 1766 | */ 1767 | closed: { 1768 | get: function() { 1769 | return params.closed; 1770 | }, 1771 | set: function(v) { 1772 | params.closed = v; 1773 | if (params.closed) { 1774 | dom.addClass(_this.__ul, GUI.CLASS_CLOSED); 1775 | } else { 1776 | dom.removeClass(_this.__ul, GUI.CLASS_CLOSED); 1777 | } 1778 | // For browsers that aren't going to respect the CSS transition, 1779 | // Lets just check our height against the window height right off 1780 | // the bat. 1781 | this.onResize(); 1782 | 1783 | if (_this.__closeButton) { 1784 | _this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN : GUI.TEXT_CLOSED; 1785 | } 1786 | } 1787 | }, 1788 | 1789 | /** 1790 | * Contains all presets 1791 | * @type Object 1792 | */ 1793 | load: { 1794 | get: function() { 1795 | return params.load; 1796 | } 1797 | }, 1798 | 1799 | /** 1800 | * Determines whether or not to use localStorage as the means for 1801 | * remembering 1802 | * @type Boolean 1803 | */ 1804 | useLocalStorage: { 1805 | 1806 | get: function() { 1807 | return use_local_storage; 1808 | }, 1809 | set: function(bool) { 1810 | if (SUPPORTS_LOCAL_STORAGE) { 1811 | use_local_storage = bool; 1812 | if (bool) { 1813 | dom.bind(window, 'unload', saveToLocalStorage); 1814 | } else { 1815 | dom.unbind(window, 'unload', saveToLocalStorage); 1816 | } 1817 | localStorage.setItem(getLocalStorageHash(_this, 'isLocal'), bool); 1818 | } 1819 | } 1820 | 1821 | } 1822 | 1823 | }); 1824 | 1825 | // Are we a root level GUI? 1826 | if (common.isUndefined(params.parent)) { 1827 | 1828 | params.closed = false; 1829 | 1830 | dom.addClass(this.domElement, GUI.CLASS_MAIN); 1831 | dom.makeSelectable(this.domElement, false); 1832 | 1833 | // Are we supposed to be loading locally? 1834 | if (SUPPORTS_LOCAL_STORAGE) { 1835 | 1836 | if (use_local_storage) { 1837 | 1838 | _this.useLocalStorage = true; 1839 | 1840 | var saved_gui = localStorage.getItem(getLocalStorageHash(this, 'gui')); 1841 | 1842 | if (saved_gui) { 1843 | params.load = JSON.parse(saved_gui); 1844 | } 1845 | 1846 | } 1847 | 1848 | } 1849 | 1850 | this.__closeButton = document.createElement('div'); 1851 | this.__closeButton.innerHTML = GUI.TEXT_CLOSED; 1852 | dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON); 1853 | this.domElement.appendChild(this.__closeButton); 1854 | 1855 | dom.bind(this.__closeButton, 'click', function() { 1856 | 1857 | _this.closed = !_this.closed; 1858 | 1859 | 1860 | }); 1861 | 1862 | 1863 | // Oh, you're a nested GUI! 1864 | } else { 1865 | 1866 | if (params.closed === undefined) { 1867 | params.closed = true; 1868 | } 1869 | 1870 | var title_row_name = document.createTextNode(params.name); 1871 | dom.addClass(title_row_name, 'controller-name'); 1872 | 1873 | var title_row = addRow(_this, title_row_name); 1874 | 1875 | var on_click_title = function(e) { 1876 | e.preventDefault(); 1877 | _this.closed = !_this.closed; 1878 | return false; 1879 | }; 1880 | 1881 | dom.addClass(this.__ul, GUI.CLASS_CLOSED); 1882 | 1883 | dom.addClass(title_row, 'title'); 1884 | dom.bind(title_row, 'click', on_click_title); 1885 | 1886 | if (!params.closed) { 1887 | this.closed = false; 1888 | } 1889 | 1890 | } 1891 | 1892 | if (params.autoPlace) { 1893 | 1894 | if (common.isUndefined(params.parent)) { 1895 | 1896 | if (auto_place_virgin) { 1897 | auto_place_container = document.createElement('div'); 1898 | dom.addClass(auto_place_container, CSS_NAMESPACE); 1899 | dom.addClass(auto_place_container, GUI.CLASS_AUTO_PLACE_CONTAINER); 1900 | document.body.appendChild(auto_place_container); 1901 | auto_place_virgin = false; 1902 | } 1903 | 1904 | // Put it in the dom for you. 1905 | auto_place_container.appendChild(this.domElement); 1906 | 1907 | // Apply the auto styles 1908 | dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE); 1909 | 1910 | } 1911 | 1912 | 1913 | // Make it not elastic. 1914 | if (!this.parent) setWidth(_this, params.width); 1915 | 1916 | } 1917 | 1918 | dom.bind(window, 'resize', function() { _this.onResize() }); 1919 | dom.bind(this.__ul, 'webkitTransitionEnd', function() { _this.onResize(); }); 1920 | dom.bind(this.__ul, 'transitionend', function() { _this.onResize() }); 1921 | dom.bind(this.__ul, 'oTransitionEnd', function() { _this.onResize() }); 1922 | this.onResize(); 1923 | 1924 | 1925 | if (params.resizable) { 1926 | addResizeHandle(this); 1927 | } 1928 | 1929 | function saveToLocalStorage() { 1930 | localStorage.setItem(getLocalStorageHash(_this, 'gui'), JSON.stringify(_this.getSaveObject())); 1931 | } 1932 | 1933 | var root = _this.getRoot(); 1934 | function resetWidth() { 1935 | var root = _this.getRoot(); 1936 | root.width += 1; 1937 | common.defer(function() { 1938 | root.width -= 1; 1939 | }); 1940 | } 1941 | 1942 | if (!params.parent) { 1943 | resetWidth(); 1944 | } 1945 | 1946 | }; 1947 | 1948 | GUI.toggleHide = function() { 1949 | 1950 | hide = !hide; 1951 | common.each(hideable_guis, function(gui) { 1952 | gui.domElement.style.zIndex = hide ? -999 : 999; 1953 | gui.domElement.style.opacity = hide ? 0 : 1; 1954 | }); 1955 | }; 1956 | 1957 | GUI.CLASS_AUTO_PLACE = 'a'; 1958 | GUI.CLASS_AUTO_PLACE_CONTAINER = 'ac'; 1959 | GUI.CLASS_MAIN = 'main'; 1960 | GUI.CLASS_CONTROLLER_ROW = 'cr'; 1961 | GUI.CLASS_TOO_TALL = 'taller-than-window'; 1962 | GUI.CLASS_CLOSED = 'closed'; 1963 | GUI.CLASS_CLOSE_BUTTON = 'close-button'; 1964 | GUI.CLASS_DRAG = 'drag'; 1965 | 1966 | GUI.DEFAULT_WIDTH = 245; 1967 | GUI.TEXT_CLOSED = 'Close Controls'; 1968 | GUI.TEXT_OPEN = 'Open Controls'; 1969 | 1970 | dom.bind(window, 'keydown', function(e) { 1971 | 1972 | if (document.activeElement.type !== 'text' && 1973 | (e.which === HIDE_KEY_CODE || e.keyCode == HIDE_KEY_CODE)) { 1974 | GUI.toggleHide(); 1975 | } 1976 | 1977 | }, false); 1978 | 1979 | common.extend( 1980 | 1981 | GUI.prototype, 1982 | 1983 | /** @lends dat.gui.GUI */ 1984 | { 1985 | 1986 | /** 1987 | * @param object 1988 | * @param property 1989 | * @returns {dat.controllers.Controller} The new controller that was added. 1990 | * @instance 1991 | */ 1992 | add: function(object, property) { 1993 | 1994 | return add( 1995 | this, 1996 | object, 1997 | property, 1998 | { 1999 | factoryArgs: Array.prototype.slice.call(arguments, 2) 2000 | } 2001 | ); 2002 | 2003 | }, 2004 | 2005 | /** 2006 | * @param object 2007 | * @param property 2008 | * @returns {dat.controllers.ColorController} The new controller that was added. 2009 | * @instance 2010 | */ 2011 | addColor: function(object, property) { 2012 | 2013 | return add( 2014 | this, 2015 | object, 2016 | property, 2017 | { 2018 | color: true 2019 | } 2020 | ); 2021 | 2022 | }, 2023 | 2024 | /** 2025 | * @param controller 2026 | * @instance 2027 | */ 2028 | remove: function(controller) { 2029 | 2030 | // TODO listening? 2031 | this.__ul.removeChild(controller.__li); 2032 | this.__controllers.slice(this.__controllers.indexOf(controller), 1); 2033 | var _this = this; 2034 | common.defer(function() { 2035 | _this.onResize(); 2036 | }); 2037 | 2038 | }, 2039 | 2040 | destroy: function() { 2041 | 2042 | if (this.autoPlace) { 2043 | auto_place_container.removeChild(this.domElement); 2044 | } 2045 | 2046 | }, 2047 | 2048 | /** 2049 | * @param name 2050 | * @returns {dat.gui.GUI} The new folder. 2051 | * @throws {Error} if this GUI already has a folder by the specified 2052 | * name 2053 | * @instance 2054 | */ 2055 | addFolder: function(name) { 2056 | 2057 | // We have to prevent collisions on names in order to have a key 2058 | // by which to remember saved values 2059 | if (this.__folders[name] !== undefined) { 2060 | throw new Error('You already have a folder in this GUI by the' + 2061 | ' name "' + name + '"'); 2062 | } 2063 | 2064 | var new_gui_params = { name: name, parent: this }; 2065 | 2066 | // We need to pass down the autoPlace trait so that we can 2067 | // attach event listeners to open/close folder actions to 2068 | // ensure that a scrollbar appears if the window is too short. 2069 | new_gui_params.autoPlace = this.autoPlace; 2070 | 2071 | // Do we have saved appearance data for this folder? 2072 | 2073 | if (this.load && // Anything loaded? 2074 | this.load.folders && // Was my parent a dead-end? 2075 | this.load.folders[name]) { // Did daddy remember me? 2076 | 2077 | // Start me closed if I was closed 2078 | new_gui_params.closed = this.load.folders[name].closed; 2079 | 2080 | // Pass down the loaded data 2081 | new_gui_params.load = this.load.folders[name]; 2082 | 2083 | } 2084 | 2085 | var gui = new GUI(new_gui_params); 2086 | this.__folders[name] = gui; 2087 | 2088 | var li = addRow(this, gui.domElement); 2089 | dom.addClass(li, 'folder'); 2090 | return gui; 2091 | 2092 | }, 2093 | 2094 | open: function() { 2095 | this.closed = false; 2096 | }, 2097 | 2098 | close: function() { 2099 | this.closed = true; 2100 | }, 2101 | 2102 | onResize: function() { 2103 | 2104 | var root = this.getRoot(); 2105 | 2106 | if (root.scrollable) { 2107 | 2108 | var top = dom.getOffset(root.__ul).top; 2109 | var h = 0; 2110 | 2111 | common.each(root.__ul.childNodes, function(node) { 2112 | if (! (root.autoPlace && node === root.__save_row)) 2113 | h += dom.getHeight(node); 2114 | }); 2115 | 2116 | if (window.innerHeight - top - CLOSE_BUTTON_HEIGHT < h) { 2117 | dom.addClass(root.domElement, GUI.CLASS_TOO_TALL); 2118 | root.__ul.style.height = window.innerHeight - top - CLOSE_BUTTON_HEIGHT + 'px'; 2119 | } else { 2120 | dom.removeClass(root.domElement, GUI.CLASS_TOO_TALL); 2121 | root.__ul.style.height = 'auto'; 2122 | } 2123 | 2124 | } 2125 | 2126 | if (root.__resize_handle) { 2127 | common.defer(function() { 2128 | root.__resize_handle.style.height = root.__ul.offsetHeight + 'px'; 2129 | }); 2130 | } 2131 | 2132 | if (root.__closeButton) { 2133 | root.__closeButton.style.width = root.width + 'px'; 2134 | } 2135 | 2136 | }, 2137 | 2138 | /** 2139 | * Mark objects for saving. The order of these objects cannot change as 2140 | * the GUI grows. When remembering new objects, append them to the end 2141 | * of the list. 2142 | * 2143 | * @param {Object...} objects 2144 | * @throws {Error} if not called on a top level GUI. 2145 | * @instance 2146 | */ 2147 | remember: function() { 2148 | 2149 | if (common.isUndefined(SAVE_DIALOGUE)) { 2150 | SAVE_DIALOGUE = new CenteredDiv(); 2151 | SAVE_DIALOGUE.domElement.innerHTML = saveDialogueContents; 2152 | } 2153 | 2154 | if (this.parent) { 2155 | throw new Error("You can only call remember on a top level GUI."); 2156 | } 2157 | 2158 | var _this = this; 2159 | 2160 | common.each(Array.prototype.slice.call(arguments), function(object) { 2161 | if (_this.__rememberedObjects.length == 0) { 2162 | addSaveMenu(_this); 2163 | } 2164 | if (_this.__rememberedObjects.indexOf(object) == -1) { 2165 | _this.__rememberedObjects.push(object); 2166 | } 2167 | }); 2168 | 2169 | if (this.autoPlace) { 2170 | // Set save row width 2171 | setWidth(this, this.width); 2172 | } 2173 | 2174 | }, 2175 | 2176 | /** 2177 | * @returns {dat.gui.GUI} the topmost parent GUI of a nested GUI. 2178 | * @instance 2179 | */ 2180 | getRoot: function() { 2181 | var gui = this; 2182 | while (gui.parent) { 2183 | gui = gui.parent; 2184 | } 2185 | return gui; 2186 | }, 2187 | 2188 | /** 2189 | * @returns {Object} a JSON object representing the current state of 2190 | * this GUI as well as its remembered properties. 2191 | * @instance 2192 | */ 2193 | getSaveObject: function() { 2194 | 2195 | var toReturn = this.load; 2196 | 2197 | toReturn.closed = this.closed; 2198 | 2199 | // Am I remembering any values? 2200 | if (this.__rememberedObjects.length > 0) { 2201 | 2202 | toReturn.preset = this.preset; 2203 | 2204 | if (!toReturn.remembered) { 2205 | toReturn.remembered = {}; 2206 | } 2207 | 2208 | toReturn.remembered[this.preset] = getCurrentPreset(this); 2209 | 2210 | } 2211 | 2212 | toReturn.folders = {}; 2213 | common.each(this.__folders, function(element, key) { 2214 | toReturn.folders[key] = element.getSaveObject(); 2215 | }); 2216 | 2217 | return toReturn; 2218 | 2219 | }, 2220 | 2221 | save: function() { 2222 | 2223 | if (!this.load.remembered) { 2224 | this.load.remembered = {}; 2225 | } 2226 | 2227 | this.load.remembered[this.preset] = getCurrentPreset(this); 2228 | markPresetModified(this, false); 2229 | 2230 | }, 2231 | 2232 | saveAs: function(presetName) { 2233 | 2234 | if (!this.load.remembered) { 2235 | 2236 | // Retain default values upon first save 2237 | this.load.remembered = {}; 2238 | this.load.remembered[DEFAULT_DEFAULT_PRESET_NAME] = getCurrentPreset(this, true); 2239 | 2240 | } 2241 | 2242 | this.load.remembered[presetName] = getCurrentPreset(this); 2243 | this.preset = presetName; 2244 | addPresetOption(this, presetName, true); 2245 | 2246 | }, 2247 | 2248 | revert: function(gui) { 2249 | 2250 | common.each(this.__controllers, function(controller) { 2251 | // Make revert work on Default. 2252 | if (!this.getRoot().load.remembered) { 2253 | controller.setValue(controller.initialValue); 2254 | } else { 2255 | recallSavedValue(gui || this.getRoot(), controller); 2256 | } 2257 | }, this); 2258 | 2259 | common.each(this.__folders, function(folder) { 2260 | folder.revert(folder); 2261 | }); 2262 | 2263 | if (!gui) { 2264 | markPresetModified(this.getRoot(), false); 2265 | } 2266 | 2267 | 2268 | }, 2269 | 2270 | listen: function(controller) { 2271 | 2272 | var init = this.__listening.length == 0; 2273 | this.__listening.push(controller); 2274 | if (init) updateDisplays(this.__listening); 2275 | 2276 | } 2277 | 2278 | } 2279 | 2280 | ); 2281 | 2282 | function add(gui, object, property, params) { 2283 | 2284 | if (object[property] === undefined) { 2285 | throw new Error("Object " + object + " has no property \"" + property + "\""); 2286 | } 2287 | 2288 | var controller; 2289 | 2290 | if (params.color) { 2291 | 2292 | controller = new ColorController(object, property); 2293 | 2294 | } else { 2295 | 2296 | var factoryArgs = [object,property].concat(params.factoryArgs); 2297 | controller = controllerFactory.apply(gui, factoryArgs); 2298 | 2299 | } 2300 | 2301 | if (params.before instanceof Controller) { 2302 | params.before = params.before.__li; 2303 | } 2304 | 2305 | recallSavedValue(gui, controller); 2306 | 2307 | dom.addClass(controller.domElement, 'c'); 2308 | 2309 | var name = document.createElement('span'); 2310 | dom.addClass(name, 'property-name'); 2311 | name.innerHTML = controller.property; 2312 | 2313 | var container = document.createElement('div'); 2314 | container.appendChild(name); 2315 | container.appendChild(controller.domElement); 2316 | 2317 | var li = addRow(gui, container, params.before); 2318 | 2319 | dom.addClass(li, GUI.CLASS_CONTROLLER_ROW); 2320 | dom.addClass(li, typeof controller.getValue()); 2321 | 2322 | augmentController(gui, li, controller); 2323 | 2324 | gui.__controllers.push(controller); 2325 | 2326 | return controller; 2327 | 2328 | } 2329 | 2330 | /** 2331 | * Add a row to the end of the GUI or before another row. 2332 | * 2333 | * @param gui 2334 | * @param [dom] If specified, inserts the dom content in the new row 2335 | * @param [liBefore] If specified, places the new row before another row 2336 | */ 2337 | function addRow(gui, dom, liBefore) { 2338 | var li = document.createElement('li'); 2339 | if (dom) li.appendChild(dom); 2340 | if (liBefore) { 2341 | gui.__ul.insertBefore(li, params.before); 2342 | } else { 2343 | gui.__ul.appendChild(li); 2344 | } 2345 | gui.onResize(); 2346 | return li; 2347 | } 2348 | 2349 | function augmentController(gui, li, controller) { 2350 | 2351 | controller.__li = li; 2352 | controller.__gui = gui; 2353 | 2354 | common.extend(controller, { 2355 | 2356 | options: function(options) { 2357 | 2358 | if (arguments.length > 1) { 2359 | controller.remove(); 2360 | 2361 | return add( 2362 | gui, 2363 | controller.object, 2364 | controller.property, 2365 | { 2366 | before: controller.__li.nextElementSibling, 2367 | factoryArgs: [common.toArray(arguments)] 2368 | } 2369 | ); 2370 | 2371 | } 2372 | 2373 | if (common.isArray(options) || common.isObject(options)) { 2374 | controller.remove(); 2375 | 2376 | return add( 2377 | gui, 2378 | controller.object, 2379 | controller.property, 2380 | { 2381 | before: controller.__li.nextElementSibling, 2382 | factoryArgs: [options] 2383 | } 2384 | ); 2385 | 2386 | } 2387 | 2388 | }, 2389 | 2390 | name: function(v) { 2391 | controller.__li.firstElementChild.firstElementChild.innerHTML = v; 2392 | return controller; 2393 | }, 2394 | 2395 | listen: function() { 2396 | controller.__gui.listen(controller); 2397 | return controller; 2398 | }, 2399 | 2400 | remove: function() { 2401 | controller.__gui.remove(controller); 2402 | return controller; 2403 | } 2404 | 2405 | }); 2406 | 2407 | // All sliders should be accompanied by a box. 2408 | if (controller instanceof NumberControllerSlider) { 2409 | 2410 | var box = new NumberControllerBox(controller.object, controller.property, 2411 | { min: controller.__min, max: controller.__max, step: controller.__step }); 2412 | 2413 | common.each(['updateDisplay', 'onChange', 'onFinishChange'], function(method) { 2414 | var pc = controller[method]; 2415 | var pb = box[method]; 2416 | controller[method] = box[method] = function() { 2417 | var args = Array.prototype.slice.call(arguments); 2418 | pc.apply(controller, args); 2419 | return pb.apply(box, args); 2420 | } 2421 | }); 2422 | 2423 | dom.addClass(li, 'has-slider'); 2424 | controller.domElement.insertBefore(box.domElement, controller.domElement.firstElementChild); 2425 | 2426 | } 2427 | else if (controller instanceof NumberControllerBox) { 2428 | 2429 | var r = function(returned) { 2430 | 2431 | // Have we defined both boundaries? 2432 | if (common.isNumber(controller.__min) && common.isNumber(controller.__max)) { 2433 | 2434 | // Well, then lets just replace this with a slider. 2435 | controller.remove(); 2436 | return add( 2437 | gui, 2438 | controller.object, 2439 | controller.property, 2440 | { 2441 | before: controller.__li.nextElementSibling, 2442 | factoryArgs: [controller.__min, controller.__max, controller.__step] 2443 | }); 2444 | 2445 | } 2446 | 2447 | return returned; 2448 | 2449 | }; 2450 | 2451 | controller.min = common.compose(r, controller.min); 2452 | controller.max = common.compose(r, controller.max); 2453 | 2454 | } 2455 | else if (controller instanceof BooleanController) { 2456 | 2457 | dom.bind(li, 'click', function() { 2458 | dom.fakeEvent(controller.__checkbox, 'click'); 2459 | }); 2460 | 2461 | dom.bind(controller.__checkbox, 'click', function(e) { 2462 | e.stopPropagation(); // Prevents double-toggle 2463 | }) 2464 | 2465 | } 2466 | else if (controller instanceof FunctionController) { 2467 | 2468 | dom.bind(li, 'click', function() { 2469 | dom.fakeEvent(controller.__button, 'click'); 2470 | }); 2471 | 2472 | dom.bind(li, 'mouseover', function() { 2473 | dom.addClass(controller.__button, 'hover'); 2474 | }); 2475 | 2476 | dom.bind(li, 'mouseout', function() { 2477 | dom.removeClass(controller.__button, 'hover'); 2478 | }); 2479 | 2480 | } 2481 | else if (controller instanceof ColorController) { 2482 | 2483 | dom.addClass(li, 'color'); 2484 | controller.updateDisplay = common.compose(function(r) { 2485 | li.style.borderLeftColor = controller.__color.toString(); 2486 | return r; 2487 | }, controller.updateDisplay); 2488 | 2489 | controller.updateDisplay(); 2490 | 2491 | } 2492 | 2493 | controller.setValue = common.compose(function(r) { 2494 | if (gui.getRoot().__preset_select && controller.isModified()) { 2495 | markPresetModified(gui.getRoot(), true); 2496 | } 2497 | return r; 2498 | }, controller.setValue); 2499 | 2500 | } 2501 | 2502 | function recallSavedValue(gui, controller) { 2503 | 2504 | // Find the topmost GUI, that's where remembered objects live. 2505 | var root = gui.getRoot(); 2506 | 2507 | // Does the object we're controlling match anything we've been told to 2508 | // remember? 2509 | var matched_index = root.__rememberedObjects.indexOf(controller.object); 2510 | 2511 | // Why yes, it does! 2512 | if (matched_index != -1) { 2513 | 2514 | // Let me fetch a map of controllers for thcommon.isObject. 2515 | var controller_map = 2516 | root.__rememberedObjectIndecesToControllers[matched_index]; 2517 | 2518 | // Ohp, I believe this is the first controller we've created for this 2519 | // object. Lets make the map fresh. 2520 | if (controller_map === undefined) { 2521 | controller_map = {}; 2522 | root.__rememberedObjectIndecesToControllers[matched_index] = 2523 | controller_map; 2524 | } 2525 | 2526 | // Keep track of this controller 2527 | controller_map[controller.property] = controller; 2528 | 2529 | // Okay, now have we saved any values for this controller? 2530 | if (root.load && root.load.remembered) { 2531 | 2532 | var preset_map = root.load.remembered; 2533 | 2534 | // Which preset are we trying to load? 2535 | var preset; 2536 | 2537 | if (preset_map[gui.preset]) { 2538 | 2539 | preset = preset_map[gui.preset]; 2540 | 2541 | } else if (preset_map[DEFAULT_DEFAULT_PRESET_NAME]) { 2542 | 2543 | // Uhh, you can have the default instead? 2544 | preset = preset_map[DEFAULT_DEFAULT_PRESET_NAME]; 2545 | 2546 | } else { 2547 | 2548 | // Nada. 2549 | 2550 | return; 2551 | 2552 | } 2553 | 2554 | 2555 | // Did the loaded object remember thcommon.isObject? 2556 | if (preset[matched_index] && 2557 | 2558 | // Did we remember this particular property? 2559 | preset[matched_index][controller.property] !== undefined) { 2560 | 2561 | // We did remember something for this guy ... 2562 | var value = preset[matched_index][controller.property]; 2563 | 2564 | // And that's what it is. 2565 | controller.initialValue = value; 2566 | controller.setValue(value); 2567 | 2568 | } 2569 | 2570 | } 2571 | 2572 | } 2573 | 2574 | } 2575 | 2576 | function getLocalStorageHash(gui, key) { 2577 | // TODO how does this deal with multiple GUI's? 2578 | return document.location.href + '.' + key; 2579 | 2580 | } 2581 | 2582 | function addSaveMenu(gui) { 2583 | 2584 | var div = gui.__save_row = document.createElement('li'); 2585 | 2586 | dom.addClass(gui.domElement, 'has-save'); 2587 | 2588 | gui.__ul.insertBefore(div, gui.__ul.firstChild); 2589 | 2590 | dom.addClass(div, 'save-row'); 2591 | 2592 | var gears = document.createElement('span'); 2593 | gears.innerHTML = ' '; 2594 | dom.addClass(gears, 'button gears'); 2595 | 2596 | // TODO replace with FunctionController 2597 | var button = document.createElement('span'); 2598 | button.innerHTML = 'Save'; 2599 | dom.addClass(button, 'button'); 2600 | dom.addClass(button, 'save'); 2601 | 2602 | var button2 = document.createElement('span'); 2603 | button2.innerHTML = 'New'; 2604 | dom.addClass(button2, 'button'); 2605 | dom.addClass(button2, 'save-as'); 2606 | 2607 | var button3 = document.createElement('span'); 2608 | button3.innerHTML = 'Revert'; 2609 | dom.addClass(button3, 'button'); 2610 | dom.addClass(button3, 'revert'); 2611 | 2612 | var select = gui.__preset_select = document.createElement('select'); 2613 | 2614 | if (gui.load && gui.load.remembered) { 2615 | 2616 | common.each(gui.load.remembered, function(value, key) { 2617 | addPresetOption(gui, key, key == gui.preset); 2618 | }); 2619 | 2620 | } else { 2621 | addPresetOption(gui, DEFAULT_DEFAULT_PRESET_NAME, false); 2622 | } 2623 | 2624 | dom.bind(select, 'change', function() { 2625 | 2626 | 2627 | for (var index = 0; index < gui.__preset_select.length; index++) { 2628 | gui.__preset_select[index].innerHTML = gui.__preset_select[index].value; 2629 | } 2630 | 2631 | gui.preset = this.value; 2632 | 2633 | }); 2634 | 2635 | div.appendChild(select); 2636 | div.appendChild(gears); 2637 | div.appendChild(button); 2638 | div.appendChild(button2); 2639 | div.appendChild(button3); 2640 | 2641 | if (SUPPORTS_LOCAL_STORAGE) { 2642 | 2643 | var saveLocally = document.getElementById('dg-save-locally'); 2644 | var explain = document.getElementById('dg-local-explain'); 2645 | 2646 | saveLocally.style.display = 'block'; 2647 | 2648 | var localStorageCheckBox = document.getElementById('dg-local-storage'); 2649 | 2650 | if (localStorage.getItem(getLocalStorageHash(gui, 'isLocal')) === 'true') { 2651 | localStorageCheckBox.setAttribute('checked', 'checked'); 2652 | } 2653 | 2654 | function showHideExplain() { 2655 | explain.style.display = gui.useLocalStorage ? 'block' : 'none'; 2656 | } 2657 | 2658 | showHideExplain(); 2659 | 2660 | // TODO: Use a boolean controller, fool! 2661 | dom.bind(localStorageCheckBox, 'change', function() { 2662 | gui.useLocalStorage = !gui.useLocalStorage; 2663 | showHideExplain(); 2664 | }); 2665 | 2666 | } 2667 | 2668 | var newConstructorTextArea = document.getElementById('dg-new-constructor'); 2669 | 2670 | dom.bind(newConstructorTextArea, 'keydown', function(e) { 2671 | if (e.metaKey && (e.which === 67 || e.keyCode == 67)) { 2672 | SAVE_DIALOGUE.hide(); 2673 | } 2674 | }); 2675 | 2676 | dom.bind(gears, 'click', function() { 2677 | newConstructorTextArea.innerHTML = JSON.stringify(gui.getSaveObject(), undefined, 2); 2678 | SAVE_DIALOGUE.show(); 2679 | newConstructorTextArea.focus(); 2680 | newConstructorTextArea.select(); 2681 | }); 2682 | 2683 | dom.bind(button, 'click', function() { 2684 | gui.save(); 2685 | }); 2686 | 2687 | dom.bind(button2, 'click', function() { 2688 | var presetName = prompt('Enter a new preset name.'); 2689 | if (presetName) gui.saveAs(presetName); 2690 | }); 2691 | 2692 | dom.bind(button3, 'click', function() { 2693 | gui.revert(); 2694 | }); 2695 | 2696 | // div.appendChild(button2); 2697 | 2698 | } 2699 | 2700 | function addResizeHandle(gui) { 2701 | 2702 | gui.__resize_handle = document.createElement('div'); 2703 | 2704 | common.extend(gui.__resize_handle.style, { 2705 | 2706 | width: '6px', 2707 | marginLeft: '-3px', 2708 | height: '200px', 2709 | cursor: 'ew-resize', 2710 | position: 'absolute' 2711 | // border: '1px solid blue' 2712 | 2713 | }); 2714 | 2715 | var pmouseX; 2716 | 2717 | dom.bind(gui.__resize_handle, 'mousedown', dragStart); 2718 | dom.bind(gui.__closeButton, 'mousedown', dragStart); 2719 | 2720 | gui.domElement.insertBefore(gui.__resize_handle, gui.domElement.firstElementChild); 2721 | 2722 | function dragStart(e) { 2723 | 2724 | e.preventDefault(); 2725 | 2726 | pmouseX = e.clientX; 2727 | 2728 | dom.addClass(gui.__closeButton, GUI.CLASS_DRAG); 2729 | dom.bind(window, 'mousemove', drag); 2730 | dom.bind(window, 'mouseup', dragStop); 2731 | 2732 | return false; 2733 | 2734 | } 2735 | 2736 | function drag(e) { 2737 | 2738 | e.preventDefault(); 2739 | 2740 | gui.width += pmouseX - e.clientX; 2741 | gui.onResize(); 2742 | pmouseX = e.clientX; 2743 | 2744 | return false; 2745 | 2746 | } 2747 | 2748 | function dragStop() { 2749 | 2750 | dom.removeClass(gui.__closeButton, GUI.CLASS_DRAG); 2751 | dom.unbind(window, 'mousemove', drag); 2752 | dom.unbind(window, 'mouseup', dragStop); 2753 | 2754 | } 2755 | 2756 | } 2757 | 2758 | function setWidth(gui, w) { 2759 | gui.domElement.style.width = w + 'px'; 2760 | // Auto placed save-rows are position fixed, so we have to 2761 | // set the width manually if we want it to bleed to the edge 2762 | if (gui.__save_row && gui.autoPlace) { 2763 | gui.__save_row.style.width = w + 'px'; 2764 | }if (gui.__closeButton) { 2765 | gui.__closeButton.style.width = w + 'px'; 2766 | } 2767 | } 2768 | 2769 | function getCurrentPreset(gui, useInitialValues) { 2770 | 2771 | var toReturn = {}; 2772 | 2773 | // For each object I'm remembering 2774 | common.each(gui.__rememberedObjects, function(val, index) { 2775 | 2776 | var saved_values = {}; 2777 | 2778 | // The controllers I've made for thcommon.isObject by property 2779 | var controller_map = 2780 | gui.__rememberedObjectIndecesToControllers[index]; 2781 | 2782 | // Remember each value for each property 2783 | common.each(controller_map, function(controller, property) { 2784 | saved_values[property] = useInitialValues ? controller.initialValue : controller.getValue(); 2785 | }); 2786 | 2787 | // Save the values for thcommon.isObject 2788 | toReturn[index] = saved_values; 2789 | 2790 | }); 2791 | 2792 | return toReturn; 2793 | 2794 | } 2795 | 2796 | function addPresetOption(gui, name, setSelected) { 2797 | var opt = document.createElement('option'); 2798 | opt.innerHTML = name; 2799 | opt.value = name; 2800 | gui.__preset_select.appendChild(opt); 2801 | if (setSelected) { 2802 | gui.__preset_select.selectedIndex = gui.__preset_select.length - 1; 2803 | } 2804 | } 2805 | 2806 | function setPresetSelectIndex(gui) { 2807 | for (var index = 0; index < gui.__preset_select.length; index++) { 2808 | if (gui.__preset_select[index].value == gui.preset) { 2809 | gui.__preset_select.selectedIndex = index; 2810 | } 2811 | } 2812 | } 2813 | 2814 | function markPresetModified(gui, modified) { 2815 | var opt = gui.__preset_select[gui.__preset_select.selectedIndex]; 2816 | // console.log('mark', modified, opt); 2817 | if (modified) { 2818 | opt.innerHTML = opt.value + "*"; 2819 | } else { 2820 | opt.innerHTML = opt.value; 2821 | } 2822 | } 2823 | 2824 | function updateDisplays(controllerArray) { 2825 | 2826 | 2827 | if (controllerArray.length != 0) { 2828 | 2829 | requestAnimationFrame(function() { 2830 | updateDisplays(controllerArray); 2831 | }); 2832 | 2833 | } 2834 | 2835 | common.each(controllerArray, function(c) { 2836 | c.updateDisplay(); 2837 | }); 2838 | 2839 | } 2840 | 2841 | return GUI; 2842 | 2843 | })(dat.utils.css, 2844 | "
\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
\n \n
\n\n
", 2845 | ".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 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.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 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear;border:0;position:absolute;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-x:hidden}.dg.a.has-save ul{margin-top:27px}.dg.a.has-save ul.closed{margin-top:0}.dg.a .save-row{position:fixed;top:0;z-index:1002}.dg li{-webkit-transition:height 0.1s ease-out;-o-transition:height 0.1s ease-out;-moz-transition:height 0.1s ease-out;transition:height 0.1s ease-out}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;overflow:hidden;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}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%}.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:9px}.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 .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.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}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n", 2846 | dat.controllers.factory = (function (OptionController, NumberControllerBox, NumberControllerSlider, StringController, FunctionController, BooleanController, common) { 2847 | 2848 | return function(object, property) { 2849 | 2850 | var initialValue = object[property]; 2851 | 2852 | // Providing options? 2853 | if (common.isArray(arguments[2]) || common.isObject(arguments[2])) { 2854 | return new OptionController(object, property, arguments[2]); 2855 | } 2856 | 2857 | // Providing a map? 2858 | 2859 | if (common.isNumber(initialValue)) { 2860 | 2861 | if (common.isNumber(arguments[2]) && common.isNumber(arguments[3])) { 2862 | 2863 | // Has min and max. 2864 | return new NumberControllerSlider(object, property, arguments[2], arguments[3]); 2865 | 2866 | } else { 2867 | 2868 | return new NumberControllerBox(object, property, { min: arguments[2], max: arguments[3] }); 2869 | 2870 | } 2871 | 2872 | } 2873 | 2874 | if (common.isString(initialValue)) { 2875 | return new StringController(object, property); 2876 | } 2877 | 2878 | if (common.isFunction(initialValue)) { 2879 | return new FunctionController(object, property, ''); 2880 | } 2881 | 2882 | if (common.isBoolean(initialValue)) { 2883 | return new BooleanController(object, property); 2884 | } 2885 | 2886 | } 2887 | 2888 | })(dat.controllers.OptionController, 2889 | dat.controllers.NumberControllerBox, 2890 | dat.controllers.NumberControllerSlider, 2891 | dat.controllers.StringController = (function (Controller, dom, common) { 2892 | 2893 | /** 2894 | * @class Provides a text input to alter the string property of an object. 2895 | * 2896 | * @extends dat.controllers.Controller 2897 | * 2898 | * @param {Object} object The object to be manipulated 2899 | * @param {string} property The name of the property to be manipulated 2900 | * 2901 | * @member dat.controllers 2902 | */ 2903 | var StringController = function(object, property) { 2904 | 2905 | StringController.superclass.call(this, object, property); 2906 | 2907 | var _this = this; 2908 | 2909 | this.__input = document.createElement('input'); 2910 | this.__input.setAttribute('type', 'text'); 2911 | 2912 | dom.bind(this.__input, 'keyup', onChange); 2913 | dom.bind(this.__input, 'change', onChange); 2914 | dom.bind(this.__input, 'blur', onBlur); 2915 | dom.bind(this.__input, 'keydown', function(e) { 2916 | if (e.keyCode === 13) { 2917 | this.blur(); 2918 | } 2919 | }); 2920 | 2921 | 2922 | function onChange() { 2923 | _this.setValue(_this.__input.value); 2924 | } 2925 | 2926 | function onBlur() { 2927 | if (_this.__onFinishChange) { 2928 | _this.__onFinishChange.call(_this, _this.getValue()); 2929 | } 2930 | } 2931 | 2932 | this.updateDisplay(); 2933 | 2934 | this.domElement.appendChild(this.__input); 2935 | 2936 | }; 2937 | 2938 | StringController.superclass = Controller; 2939 | 2940 | common.extend( 2941 | 2942 | StringController.prototype, 2943 | Controller.prototype, 2944 | 2945 | { 2946 | 2947 | updateDisplay: function() { 2948 | // Stops the caret from moving on account of: 2949 | // keyup -> setValue -> updateDisplay 2950 | if (!dom.isActive(this.__input)) { 2951 | this.__input.value = this.getValue(); 2952 | } 2953 | return StringController.superclass.prototype.updateDisplay.call(this); 2954 | } 2955 | 2956 | } 2957 | 2958 | ); 2959 | 2960 | return StringController; 2961 | 2962 | })(dat.controllers.Controller, 2963 | dat.dom.dom, 2964 | dat.utils.common), 2965 | dat.controllers.FunctionController, 2966 | dat.controllers.BooleanController, 2967 | dat.utils.common), 2968 | dat.controllers.Controller, 2969 | dat.controllers.BooleanController, 2970 | dat.controllers.FunctionController, 2971 | dat.controllers.NumberControllerBox, 2972 | dat.controllers.NumberControllerSlider, 2973 | dat.controllers.OptionController, 2974 | dat.controllers.ColorController = (function (Controller, dom, Color, interpret, common) { 2975 | 2976 | var ColorController = function(object, property) { 2977 | 2978 | ColorController.superclass.call(this, object, property); 2979 | 2980 | this.__color = new Color(this.getValue()); 2981 | this.__temp = new Color(0); 2982 | 2983 | var _this = this; 2984 | 2985 | this.domElement = document.createElement('div'); 2986 | 2987 | dom.makeSelectable(this.domElement, false); 2988 | 2989 | this.__selector = document.createElement('div'); 2990 | this.__selector.className = 'selector'; 2991 | 2992 | this.__saturation_field = document.createElement('div'); 2993 | this.__saturation_field.className = 'saturation-field'; 2994 | 2995 | this.__field_knob = document.createElement('div'); 2996 | this.__field_knob.className = 'field-knob'; 2997 | this.__field_knob_border = '2px solid '; 2998 | 2999 | this.__hue_knob = document.createElement('div'); 3000 | this.__hue_knob.className = 'hue-knob'; 3001 | 3002 | this.__hue_field = document.createElement('div'); 3003 | this.__hue_field.className = 'hue-field'; 3004 | 3005 | this.__input = document.createElement('input'); 3006 | this.__input.type = 'text'; 3007 | this.__input_textShadow = '0 1px 1px '; 3008 | 3009 | dom.bind(this.__input, 'keydown', function(e) { 3010 | if (e.keyCode === 13) { // on enter 3011 | onBlur.call(this); 3012 | } 3013 | }); 3014 | 3015 | dom.bind(this.__input, 'blur', onBlur); 3016 | 3017 | dom.bind(this.__selector, 'mousedown', function(e) { 3018 | 3019 | dom 3020 | .addClass(this, 'drag') 3021 | .bind(window, 'mouseup', function(e) { 3022 | dom.removeClass(_this.__selector, 'drag'); 3023 | }); 3024 | 3025 | }); 3026 | 3027 | var value_field = document.createElement('div'); 3028 | 3029 | common.extend(this.__selector.style, { 3030 | width: '122px', 3031 | height: '102px', 3032 | padding: '3px', 3033 | backgroundColor: '#222', 3034 | boxShadow: '0px 1px 3px rgba(0,0,0,0.3)' 3035 | }); 3036 | 3037 | common.extend(this.__field_knob.style, { 3038 | position: 'absolute', 3039 | width: '12px', 3040 | height: '12px', 3041 | border: this.__field_knob_border + (this.__color.v < .5 ? '#fff' : '#000'), 3042 | boxShadow: '0px 1px 3px rgba(0,0,0,0.5)', 3043 | borderRadius: '12px', 3044 | zIndex: 1 3045 | }); 3046 | 3047 | common.extend(this.__hue_knob.style, { 3048 | position: 'absolute', 3049 | width: '15px', 3050 | height: '2px', 3051 | borderRight: '4px solid #fff', 3052 | zIndex: 1 3053 | }); 3054 | 3055 | common.extend(this.__saturation_field.style, { 3056 | width: '100px', 3057 | height: '100px', 3058 | border: '1px solid #555', 3059 | marginRight: '3px', 3060 | display: 'inline-block', 3061 | cursor: 'pointer' 3062 | }); 3063 | 3064 | common.extend(value_field.style, { 3065 | width: '100%', 3066 | height: '100%', 3067 | background: 'none' 3068 | }); 3069 | 3070 | linearGradient(value_field, 'top', 'rgba(0,0,0,0)', '#000'); 3071 | 3072 | common.extend(this.__hue_field.style, { 3073 | width: '15px', 3074 | height: '100px', 3075 | display: 'inline-block', 3076 | border: '1px solid #555', 3077 | cursor: 'ns-resize' 3078 | }); 3079 | 3080 | hueGradient(this.__hue_field); 3081 | 3082 | common.extend(this.__input.style, { 3083 | outline: 'none', 3084 | // width: '120px', 3085 | textAlign: 'center', 3086 | // padding: '4px', 3087 | // marginBottom: '6px', 3088 | color: '#fff', 3089 | border: 0, 3090 | fontWeight: 'bold', 3091 | textShadow: this.__input_textShadow + 'rgba(0,0,0,0.7)' 3092 | }); 3093 | 3094 | dom.bind(this.__saturation_field, 'mousedown', fieldDown); 3095 | dom.bind(this.__field_knob, 'mousedown', fieldDown); 3096 | 3097 | dom.bind(this.__hue_field, 'mousedown', function(e) { 3098 | setH(e); 3099 | dom.bind(window, 'mousemove', setH); 3100 | dom.bind(window, 'mouseup', unbindH); 3101 | }); 3102 | 3103 | function fieldDown(e) { 3104 | setSV(e); 3105 | // document.body.style.cursor = 'none'; 3106 | dom.bind(window, 'mousemove', setSV); 3107 | dom.bind(window, 'mouseup', unbindSV); 3108 | } 3109 | 3110 | function unbindSV() { 3111 | dom.unbind(window, 'mousemove', setSV); 3112 | dom.unbind(window, 'mouseup', unbindSV); 3113 | // document.body.style.cursor = 'default'; 3114 | } 3115 | 3116 | function onBlur() { 3117 | var i = interpret(this.value); 3118 | if (i !== false) { 3119 | _this.__color.__state = i; 3120 | _this.setValue(_this.__color.toOriginal()); 3121 | } else { 3122 | this.value = _this.__color.toString(); 3123 | } 3124 | } 3125 | 3126 | function unbindH() { 3127 | dom.unbind(window, 'mousemove', setH); 3128 | dom.unbind(window, 'mouseup', unbindH); 3129 | } 3130 | 3131 | this.__saturation_field.appendChild(value_field); 3132 | this.__selector.appendChild(this.__field_knob); 3133 | this.__selector.appendChild(this.__saturation_field); 3134 | this.__selector.appendChild(this.__hue_field); 3135 | this.__hue_field.appendChild(this.__hue_knob); 3136 | 3137 | this.domElement.appendChild(this.__input); 3138 | this.domElement.appendChild(this.__selector); 3139 | 3140 | this.updateDisplay(); 3141 | 3142 | function setSV(e) { 3143 | 3144 | e.preventDefault(); 3145 | 3146 | var w = dom.getWidth(_this.__saturation_field); 3147 | var o = dom.getOffset(_this.__saturation_field); 3148 | var s = (e.clientX - o.left + document.body.scrollLeft) / w; 3149 | var v = 1 - (e.clientY - o.top + document.body.scrollTop) / w; 3150 | 3151 | if (v > 1) v = 1; 3152 | else if (v < 0) v = 0; 3153 | 3154 | if (s > 1) s = 1; 3155 | else if (s < 0) s = 0; 3156 | 3157 | _this.__color.v = v; 3158 | _this.__color.s = s; 3159 | 3160 | _this.setValue(_this.__color.toOriginal()); 3161 | 3162 | 3163 | return false; 3164 | 3165 | } 3166 | 3167 | function setH(e) { 3168 | 3169 | e.preventDefault(); 3170 | 3171 | var s = dom.getHeight(_this.__hue_field); 3172 | var o = dom.getOffset(_this.__hue_field); 3173 | var h = 1 - (e.clientY - o.top + document.body.scrollTop) / s; 3174 | 3175 | if (h > 1) h = 1; 3176 | else if (h < 0) h = 0; 3177 | 3178 | _this.__color.h = h * 360; 3179 | 3180 | _this.setValue(_this.__color.toOriginal()); 3181 | 3182 | return false; 3183 | 3184 | } 3185 | 3186 | }; 3187 | 3188 | ColorController.superclass = Controller; 3189 | 3190 | common.extend( 3191 | 3192 | ColorController.prototype, 3193 | Controller.prototype, 3194 | 3195 | { 3196 | 3197 | updateDisplay: function() { 3198 | 3199 | var i = interpret(this.getValue()); 3200 | 3201 | if (i !== false) { 3202 | 3203 | var mismatch = false; 3204 | 3205 | // Check for mismatch on the interpreted value. 3206 | 3207 | common.each(Color.COMPONENTS, function(component) { 3208 | if (!common.isUndefined(i[component]) && 3209 | !common.isUndefined(this.__color.__state[component]) && 3210 | i[component] !== this.__color.__state[component]) { 3211 | mismatch = true; 3212 | return {}; // break 3213 | } 3214 | }, this); 3215 | 3216 | // If nothing diverges, we keep our previous values 3217 | // for statefulness, otherwise we recalculate fresh 3218 | if (mismatch) { 3219 | common.extend(this.__color.__state, i); 3220 | } 3221 | 3222 | } 3223 | 3224 | common.extend(this.__temp.__state, this.__color.__state); 3225 | 3226 | this.__temp.a = 1; 3227 | 3228 | var flip = (this.__color.v < .5 || this.__color.s > .5) ? 255 : 0; 3229 | var _flip = 255 - flip; 3230 | 3231 | common.extend(this.__field_knob.style, { 3232 | marginLeft: 100 * this.__color.s - 7 + 'px', 3233 | marginTop: 100 * (1 - this.__color.v) - 7 + 'px', 3234 | backgroundColor: this.__temp.toString(), 3235 | border: this.__field_knob_border + 'rgb(' + flip + ',' + flip + ',' + flip +')' 3236 | }); 3237 | 3238 | this.__hue_knob.style.marginTop = (1 - this.__color.h / 360) * 100 + 'px' 3239 | 3240 | this.__temp.s = 1; 3241 | this.__temp.v = 1; 3242 | 3243 | linearGradient(this.__saturation_field, 'left', '#fff', this.__temp.toString()); 3244 | 3245 | common.extend(this.__input.style, { 3246 | backgroundColor: this.__input.value = this.__color.toString(), 3247 | color: 'rgb(' + flip + ',' + flip + ',' + flip +')', 3248 | textShadow: this.__input_textShadow + 'rgba(' + _flip + ',' + _flip + ',' + _flip +',.7)' 3249 | }); 3250 | 3251 | } 3252 | 3253 | } 3254 | 3255 | ); 3256 | 3257 | var vendors = ['-moz-','-o-','-webkit-','-ms-','']; 3258 | 3259 | function linearGradient(elem, x, a, b) { 3260 | elem.style.background = ''; 3261 | common.each(vendors, function(vendor) { 3262 | elem.style.cssText += 'background: ' + vendor + 'linear-gradient('+x+', '+a+' 0%, ' + b + ' 100%); '; 3263 | }); 3264 | } 3265 | 3266 | function hueGradient(elem) { 3267 | elem.style.background = ''; 3268 | elem.style.cssText += 'background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);' 3269 | elem.style.cssText += 'background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3270 | elem.style.cssText += 'background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3271 | elem.style.cssText += 'background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3272 | elem.style.cssText += 'background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3273 | } 3274 | 3275 | 3276 | return ColorController; 3277 | 3278 | })(dat.controllers.Controller, 3279 | dat.dom.dom, 3280 | dat.color.Color = (function (interpret, math, toString, common) { 3281 | 3282 | var Color = function() { 3283 | 3284 | this.__state = interpret.apply(this, arguments); 3285 | 3286 | if (this.__state === false) { 3287 | throw 'Failed to interpret color arguments'; 3288 | } 3289 | 3290 | this.__state.a = this.__state.a || 1; 3291 | 3292 | 3293 | }; 3294 | 3295 | Color.COMPONENTS = ['r','g','b','h','s','v','hex','a']; 3296 | 3297 | common.extend(Color.prototype, { 3298 | 3299 | toString: function() { 3300 | return toString(this); 3301 | }, 3302 | 3303 | toOriginal: function() { 3304 | return this.__state.conversion.write(this); 3305 | } 3306 | 3307 | }); 3308 | 3309 | defineRGBComponent(Color.prototype, 'r', 2); 3310 | defineRGBComponent(Color.prototype, 'g', 1); 3311 | defineRGBComponent(Color.prototype, 'b', 0); 3312 | 3313 | defineHSVComponent(Color.prototype, 'h'); 3314 | defineHSVComponent(Color.prototype, 's'); 3315 | defineHSVComponent(Color.prototype, 'v'); 3316 | 3317 | Object.defineProperty(Color.prototype, 'a', { 3318 | 3319 | get: function() { 3320 | return this.__state.a; 3321 | }, 3322 | 3323 | set: function(v) { 3324 | this.__state.a = v; 3325 | } 3326 | 3327 | }); 3328 | 3329 | Object.defineProperty(Color.prototype, 'hex', { 3330 | 3331 | get: function() { 3332 | 3333 | if (!this.__state.space !== 'HEX') { 3334 | this.__state.hex = math.rgb_to_hex(this.r, this.g, this.b); 3335 | } 3336 | 3337 | return this.__state.hex; 3338 | 3339 | }, 3340 | 3341 | set: function(v) { 3342 | 3343 | this.__state.space = 'HEX'; 3344 | this.__state.hex = v; 3345 | 3346 | } 3347 | 3348 | }); 3349 | 3350 | function defineRGBComponent(target, component, componentHexIndex) { 3351 | 3352 | Object.defineProperty(target, component, { 3353 | 3354 | get: function() { 3355 | 3356 | if (this.__state.space === 'RGB') { 3357 | return this.__state[component]; 3358 | } 3359 | 3360 | recalculateRGB(this, component, componentHexIndex); 3361 | 3362 | return this.__state[component]; 3363 | 3364 | }, 3365 | 3366 | set: function(v) { 3367 | 3368 | if (this.__state.space !== 'RGB') { 3369 | recalculateRGB(this, component, componentHexIndex); 3370 | this.__state.space = 'RGB'; 3371 | } 3372 | 3373 | this.__state[component] = v; 3374 | 3375 | } 3376 | 3377 | }); 3378 | 3379 | } 3380 | 3381 | function defineHSVComponent(target, component) { 3382 | 3383 | Object.defineProperty(target, component, { 3384 | 3385 | get: function() { 3386 | 3387 | if (this.__state.space === 'HSV') 3388 | return this.__state[component]; 3389 | 3390 | recalculateHSV(this); 3391 | 3392 | return this.__state[component]; 3393 | 3394 | }, 3395 | 3396 | set: function(v) { 3397 | 3398 | if (this.__state.space !== 'HSV') { 3399 | recalculateHSV(this); 3400 | this.__state.space = 'HSV'; 3401 | } 3402 | 3403 | this.__state[component] = v; 3404 | 3405 | } 3406 | 3407 | }); 3408 | 3409 | } 3410 | 3411 | function recalculateRGB(color, component, componentHexIndex) { 3412 | 3413 | if (color.__state.space === 'HEX') { 3414 | 3415 | color.__state[component] = math.component_from_hex(color.__state.hex, componentHexIndex); 3416 | 3417 | } else if (color.__state.space === 'HSV') { 3418 | 3419 | common.extend(color.__state, math.hsv_to_rgb(color.__state.h, color.__state.s, color.__state.v)); 3420 | 3421 | } else { 3422 | 3423 | throw 'Corrupted color state'; 3424 | 3425 | } 3426 | 3427 | } 3428 | 3429 | function recalculateHSV(color) { 3430 | 3431 | var result = math.rgb_to_hsv(color.r, color.g, color.b); 3432 | 3433 | common.extend(color.__state, 3434 | { 3435 | s: result.s, 3436 | v: result.v 3437 | } 3438 | ); 3439 | 3440 | if (!common.isNaN(result.h)) { 3441 | color.__state.h = result.h; 3442 | } else if (common.isUndefined(color.__state.h)) { 3443 | color.__state.h = 0; 3444 | } 3445 | 3446 | } 3447 | 3448 | return Color; 3449 | 3450 | })(dat.color.interpret, 3451 | dat.color.math = (function () { 3452 | 3453 | var tmpComponent; 3454 | 3455 | return { 3456 | 3457 | hsv_to_rgb: function(h, s, v) { 3458 | 3459 | var hi = Math.floor(h / 60) % 6; 3460 | 3461 | var f = h / 60 - Math.floor(h / 60); 3462 | var p = v * (1.0 - s); 3463 | var q = v * (1.0 - (f * s)); 3464 | var t = v * (1.0 - ((1.0 - f) * s)); 3465 | var c = [ 3466 | [v, t, p], 3467 | [q, v, p], 3468 | [p, v, t], 3469 | [p, q, v], 3470 | [t, p, v], 3471 | [v, p, q] 3472 | ][hi]; 3473 | 3474 | return { 3475 | r: c[0] * 255, 3476 | g: c[1] * 255, 3477 | b: c[2] * 255 3478 | }; 3479 | 3480 | }, 3481 | 3482 | rgb_to_hsv: function(r, g, b) { 3483 | 3484 | var min = Math.min(r, g, b), 3485 | max = Math.max(r, g, b), 3486 | delta = max - min, 3487 | h, s; 3488 | 3489 | if (max != 0) { 3490 | s = delta / max; 3491 | } else { 3492 | return { 3493 | h: NaN, 3494 | s: 0, 3495 | v: 0 3496 | }; 3497 | } 3498 | 3499 | if (r == max) { 3500 | h = (g - b) / delta; 3501 | } else if (g == max) { 3502 | h = 2 + (b - r) / delta; 3503 | } else { 3504 | h = 4 + (r - g) / delta; 3505 | } 3506 | h /= 6; 3507 | if (h < 0) { 3508 | h += 1; 3509 | } 3510 | 3511 | return { 3512 | h: h * 360, 3513 | s: s, 3514 | v: max / 255 3515 | }; 3516 | }, 3517 | 3518 | rgb_to_hex: function(r, g, b) { 3519 | var hex = this.hex_with_component(0, 2, r); 3520 | hex = this.hex_with_component(hex, 1, g); 3521 | hex = this.hex_with_component(hex, 0, b); 3522 | return hex; 3523 | }, 3524 | 3525 | component_from_hex: function(hex, componentIndex) { 3526 | return (hex >> (componentIndex * 8)) & 0xFF; 3527 | }, 3528 | 3529 | hex_with_component: function(hex, componentIndex, value) { 3530 | return value << (tmpComponent = componentIndex * 8) | (hex & ~ (0xFF << tmpComponent)); 3531 | } 3532 | 3533 | } 3534 | 3535 | })(), 3536 | dat.color.toString, 3537 | dat.utils.common), 3538 | dat.color.interpret, 3539 | dat.utils.common), 3540 | dat.utils.requestAnimationFrame = (function () { 3541 | 3542 | /** 3543 | * requirejs version of Paul Irish's RequestAnimationFrame 3544 | * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 3545 | */ 3546 | 3547 | return window.webkitRequestAnimationFrame || 3548 | window.mozRequestAnimationFrame || 3549 | window.oRequestAnimationFrame || 3550 | window.msRequestAnimationFrame || 3551 | function(callback, element) { 3552 | 3553 | window.setTimeout(callback, 1000 / 60); 3554 | 3555 | }; 3556 | })(), 3557 | dat.dom.CenteredDiv = (function (dom, common) { 3558 | 3559 | 3560 | var CenteredDiv = function() { 3561 | 3562 | this.backgroundElement = document.createElement('div'); 3563 | common.extend(this.backgroundElement.style, { 3564 | backgroundColor: 'rgba(0,0,0,0.8)', 3565 | top: 0, 3566 | left: 0, 3567 | display: 'none', 3568 | zIndex: '1000', 3569 | opacity: 0, 3570 | WebkitTransition: 'opacity 0.2s linear' 3571 | }); 3572 | 3573 | dom.makeFullscreen(this.backgroundElement); 3574 | this.backgroundElement.style.position = 'fixed'; 3575 | 3576 | this.domElement = document.createElement('div'); 3577 | common.extend(this.domElement.style, { 3578 | position: 'fixed', 3579 | display: 'none', 3580 | zIndex: '1001', 3581 | opacity: 0, 3582 | WebkitTransition: '-webkit-transform 0.2s ease-out, opacity 0.2s linear' 3583 | }); 3584 | 3585 | 3586 | document.body.appendChild(this.backgroundElement); 3587 | document.body.appendChild(this.domElement); 3588 | 3589 | var _this = this; 3590 | dom.bind(this.backgroundElement, 'click', function() { 3591 | _this.hide(); 3592 | }); 3593 | 3594 | 3595 | }; 3596 | 3597 | CenteredDiv.prototype.show = function() { 3598 | 3599 | var _this = this; 3600 | 3601 | 3602 | 3603 | this.backgroundElement.style.display = 'block'; 3604 | 3605 | this.domElement.style.display = 'block'; 3606 | this.domElement.style.opacity = 0; 3607 | // this.domElement.style.top = '52%'; 3608 | this.domElement.style.webkitTransform = 'scale(1.1)'; 3609 | 3610 | this.layout(); 3611 | 3612 | common.defer(function() { 3613 | _this.backgroundElement.style.opacity = 1; 3614 | _this.domElement.style.opacity = 1; 3615 | _this.domElement.style.webkitTransform = 'scale(1)'; 3616 | }); 3617 | 3618 | }; 3619 | 3620 | CenteredDiv.prototype.hide = function() { 3621 | 3622 | var _this = this; 3623 | 3624 | var hide = function() { 3625 | 3626 | _this.domElement.style.display = 'none'; 3627 | _this.backgroundElement.style.display = 'none'; 3628 | 3629 | dom.unbind(_this.domElement, 'webkitTransitionEnd', hide); 3630 | dom.unbind(_this.domElement, 'transitionend', hide); 3631 | dom.unbind(_this.domElement, 'oTransitionEnd', hide); 3632 | 3633 | }; 3634 | 3635 | dom.bind(this.domElement, 'webkitTransitionEnd', hide); 3636 | dom.bind(this.domElement, 'transitionend', hide); 3637 | dom.bind(this.domElement, 'oTransitionEnd', hide); 3638 | 3639 | this.backgroundElement.style.opacity = 0; 3640 | // this.domElement.style.top = '48%'; 3641 | this.domElement.style.opacity = 0; 3642 | this.domElement.style.webkitTransform = 'scale(1.1)'; 3643 | 3644 | }; 3645 | 3646 | CenteredDiv.prototype.layout = function() { 3647 | this.domElement.style.left = window.innerWidth/2 - dom.getWidth(this.domElement) / 2 + 'px'; 3648 | this.domElement.style.top = window.innerHeight/2 - dom.getHeight(this.domElement) / 2 + 'px'; 3649 | }; 3650 | 3651 | function lockScroll(e) { 3652 | console.log(e); 3653 | } 3654 | 3655 | return CenteredDiv; 3656 | 3657 | })(dat.dom.dom, 3658 | dat.utils.common), 3659 | dat.dom.dom, 3660 | dat.utils.common); 3661 | -------------------------------------------------------------------------------- /assets/js/lib/webfontloader.js: -------------------------------------------------------------------------------- 1 | /* Web Font Loader v1.6.26 - (c) Adobe Systems, Google. License: Apache 2.0 */(function(){function aa(a,b,c){return a.call.apply(a.bind,arguments)}function ba(a,b,c){if(!a)throw Error();if(2=b.f?e():a.fonts.load(fa(b.a),b.h).then(function(a){1<=a.length?d():setTimeout(k,25)},function(){e()})}k()}),e=new Promise(function(a,d){setTimeout(d,b.f)});Promise.race([e,d]).then(function(){b.g(b.a)},function(){b.j(b.a)})};function R(a,b,c,d,e,f,g){this.v=a;this.B=b;this.c=c;this.a=d;this.s=g||"BESbswy";this.f={};this.w=e||3E3;this.u=f||null;this.o=this.j=this.h=this.g=null;this.g=new N(this.c,this.s);this.h=new N(this.c,this.s);this.j=new N(this.c,this.s);this.o=new N(this.c,this.s);a=new H(this.a.c+",serif",K(this.a));a=P(a);this.g.a.style.cssText=a;a=new H(this.a.c+",sans-serif",K(this.a));a=P(a);this.h.a.style.cssText=a;a=new H("serif",K(this.a));a=P(a);this.j.a.style.cssText=a;a=new H("sans-serif",K(this.a));a= 7 | P(a);this.o.a.style.cssText=a;O(this.g);O(this.h);O(this.j);O(this.o)}var S={D:"serif",C:"sans-serif"},T=null;function U(){if(null===T){var a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent);T=!!a&&(536>parseInt(a[1],10)||536===parseInt(a[1],10)&&11>=parseInt(a[2],10))}return T}R.prototype.start=function(){this.f.serif=this.j.a.offsetWidth;this.f["sans-serif"]=this.o.a.offsetWidth;this.A=q();la(this)}; 8 | function ma(a,b,c){for(var d in S)if(S.hasOwnProperty(d)&&b===a.f[S[d]]&&c===a.f[S[d]])return!0;return!1}function la(a){var b=a.g.a.offsetWidth,c=a.h.a.offsetWidth,d;(d=b===a.f.serif&&c===a.f["sans-serif"])||(d=U()&&ma(a,b,c));d?q()-a.A>=a.w?U()&&ma(a,b,c)&&(null===a.u||a.u.hasOwnProperty(a.a.c))?V(a,a.v):V(a,a.B):na(a):V(a,a.v)}function na(a){setTimeout(p(function(){la(this)},a),50)}function V(a,b){setTimeout(p(function(){v(this.g.a);v(this.h.a);v(this.j.a);v(this.o.a);b(this.a)},a),0)};function W(a,b,c){this.c=a;this.a=b;this.f=0;this.o=this.j=!1;this.s=c}var X=null;W.prototype.g=function(a){var b=this.a;b.g&&w(b.f,[b.a.c("wf",a.c,K(a).toString(),"active")],[b.a.c("wf",a.c,K(a).toString(),"loading"),b.a.c("wf",a.c,K(a).toString(),"inactive")]);L(b,"fontactive",a);this.o=!0;oa(this)}; 9 | W.prototype.h=function(a){var b=this.a;if(b.g){var c=y(b.f,b.a.c("wf",a.c,K(a).toString(),"active")),d=[],e=[b.a.c("wf",a.c,K(a).toString(),"loading")];c||d.push(b.a.c("wf",a.c,K(a).toString(),"inactive"));w(b.f,d,e)}L(b,"fontinactive",a);oa(this)};function oa(a){0==--a.f&&a.j&&(a.o?(a=a.a,a.g&&w(a.f,[a.a.c("wf","active")],[a.a.c("wf","loading"),a.a.c("wf","inactive")]),L(a,"active")):M(a.a))};function pa(a){this.j=a;this.a=new ja;this.h=0;this.f=this.g=!0}pa.prototype.load=function(a){this.c=new ca(this.j,a.context||this.j);this.g=!1!==a.events;this.f=!1!==a.classes;qa(this,new ha(this.c,a),a)}; 10 | function ra(a,b,c,d,e){var f=0==--a.h;(a.f||a.g)&&setTimeout(function(){var a=e||null,k=d||null||{};if(0===c.length&&f)M(b.a);else{b.f+=c.length;f&&(b.j=f);var h,m=[];for(h=0;h,.', 'Cabin Sketch'); 110 | 111 | // シーンに追加 112 | self.scene.add(self.floatingChars); 113 | 114 | // dat.gui 115 | self.createDatGUIBox() 116 | 117 | resolve(); 118 | 119 | } 120 | }); 121 | 122 | }); 123 | } 124 | 125 | 126 | /** 127 | * dat.gui 128 | */ 129 | sample.MainVisual.prototype.createDatGUIBox = function() { 130 | var self = this; 131 | 132 | // dat.gui 133 | var gui = new dat.GUI(); 134 | 135 | var controler1 = gui.add(this, 'animationValue1', 0, 1).listen(); 136 | controler1.onChange(function(value) { 137 | self.floatingChars.setUniform('animationValue1', value); 138 | }); 139 | 140 | var controler2 = gui.add(this, 'animationValue2', 0, 1).listen(); 141 | controler2.onChange(function(value) { 142 | self.floatingChars.setUniform('animationValue2', value); 143 | }); 144 | 145 | var controler3 = gui.add(this, 'animationValue3', 0, 1).listen(); 146 | controler3.onChange(function(value) { 147 | self.floatingChars.setUniform('animationValue3', value); 148 | }); 149 | 150 | gui.add(this, 'animation1'); 151 | gui.add(this, 'animation2'); 152 | gui.add(this, 'animation3'); 153 | } 154 | 155 | 156 | /** 157 | * アニメーション開始 158 | */ 159 | sample.MainVisual.prototype.start = function() { 160 | var self = this; 161 | 162 | var enterFrameHandler = function() { 163 | requestAnimationFrame(enterFrameHandler); 164 | self.update(); 165 | }; 166 | 167 | enterFrameHandler(); 168 | } 169 | 170 | 171 | /** 172 | * アニメーションループ内で実行される 173 | */ 174 | sample.MainVisual.prototype.update = function() { 175 | this.controls.update(); 176 | this.floatingChars.update(this.camera); 177 | this.renderer.render(this.scene, this.camera); 178 | } 179 | 180 | 181 | /** 182 | * リサイズ処理 183 | * @param {jQuery.Event} e - jQueryのイベントオブジェクト 184 | */ 185 | sample.MainVisual.prototype.resize = function() { 186 | this.width = this.$window.width(); 187 | this.height = this.$window.height(); 188 | 189 | this.controls.handleResize(); 190 | 191 | this.camera.aspect = this.width / this.height; 192 | this.camera.updateProjectionMatrix(); 193 | 194 | this.renderer.setSize(this.width, this.height); 195 | } 196 | 197 | 198 | /** 199 | * animationValueを変更 200 | * @param {number} index - 1 | 2 | 3 (animationValue) 201 | */ 202 | sample.MainVisual.prototype.setAnimationValue = function(index) { 203 | this.animationValue1 = (index == 1)? 1: 0, 204 | this.animationValue2 = (index == 2)? 1: 0, 205 | this.animationValue3 = (index == 3)? 1: 0, 206 | this.floatingChars.setUniform('animationValue1', this.animationValue1); 207 | this.floatingChars.setUniform('animationValue2', this.animationValue2); 208 | this.floatingChars.setUniform('animationValue3', this.animationValue3); 209 | } 210 | 211 | sample.MainVisual.prototype.animation1 = function() { 212 | this.setAnimationValue(1); 213 | } 214 | sample.MainVisual.prototype.animation2 = function() { 215 | this.setAnimationValue(2); 216 | } 217 | sample.MainVisual.prototype.animation3 = function() { 218 | this.setAnimationValue(3); 219 | } 220 | 221 | 222 | })(); 223 | -------------------------------------------------------------------------------- /assets/js/step4/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var sample = window.sample || {}; 4 | window.sample = sample; 5 | 6 | $(function() { 7 | new sample.MainVisual(); 8 | }); 9 | 10 | })(); 11 | -------------------------------------------------------------------------------- /assets/js/step5/FloatingChars.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var sample = window.sample || {}; 4 | window.sample = sample; 5 | 6 | /** 7 | * THREE.Meshを拡張した独自3Dオブジェクトクラス 8 | * @param {number} numChars - 文字数 (正方形の数) 9 | * @param {number} charWidth - 文字の幅 [px] 10 | * @param {number} numTextureGridCols - テクスチャの1行文の文字列 11 | * @param {number} textureGridSize - テクスチャの1文字分の幅 12 | */ 13 | sample.FloatingChars = function(numChars, charWidth, numTextureGridCols, textureGridSize) { 14 | this.numChars = numChars; 15 | this.charWidth = charWidth; 16 | this.numTextureGridCols = numTextureGridCols; 17 | this.textureGridSize = textureGridSize; 18 | 19 | // カスタムジオメトリオブジェクトをインスタンス化 20 | geometry = new sample.FloatingCharsGeometry(this.numChars, this.charWidth); 21 | 22 | // RawShaderMaterial生成 23 | material = new THREE.RawShaderMaterial({ 24 | // 文字以外の部分は透過 25 | transparent: true, 26 | 27 | // 正方形の両面を描画 28 | side: THREE.DoubleSide, 29 | 30 | // シェーダに渡すuniform変数の定義 31 | uniforms: { 32 | // canvasに記述した文字から作ったtextureを渡す 33 | txtTexture: { type: 't' }, 34 | 35 | // 時間経過 updateメソッド内でフレームごとに加算していく 36 | time: { type: '1f', value: 0 }, 37 | 38 | // 文字数 = 正方形の数 39 | numChars: { type: '1f', value: this.numChars }, 40 | 41 | // Textureの横方向の文字数 42 | numTextureGridCols: { type: '1f', value: this.numTextureGridCols }, 43 | 44 | // Textureの縦方向の文字数 45 | numTextureGridRows: { type: '1f', value: 1 }, 46 | 47 | // テクスチャとして使用する文字数 (文字を何種類使うか) 48 | textureTxtLength: { type: '1f', value: 1 }, 49 | 50 | // アニメーション適用度 51 | animationValue1: { type: '1f', value: 1 }, 52 | animationValue2: { type: '1f', value: 0 }, 53 | animationValue3: { type: '1f', value: 0 } 54 | }, 55 | 56 | // 頂点シェーダのプログラムをindex.htmlのscript#vertexShaderから取得 57 | vertexShader: $('#vertexShader').text(), 58 | 59 | // フラグメントシェーダのプログラムをindex.htmlのscript#fragmentShaderから取得 60 | fragmentShader: $('#fragmentShader').text() 61 | }); 62 | 63 | // 継承元のTHREE.Meshのコンストラクタを実行 64 | THREE.Mesh.call(this, geometry, material); 65 | } 66 | 67 | sample.FloatingChars.prototype = Object.create(THREE.Mesh.prototype, { value: { constructor: THREE.Mesh }}); 68 | 69 | 70 | /** 71 | * 更新 72 | */ 73 | sample.FloatingChars.prototype.update = function() { 74 | // 経過時間を更新してシェーダに渡す 75 | this.material.uniforms.time.value += 0.001; 76 | } 77 | 78 | 79 | /** 80 | * テクスチャを生成 81 | * @param {string} txt - テクスチャとして使用したい文字列 82 | * @param {string} fontFamily - フォント名 83 | */ 84 | sample.FloatingChars.prototype.createTxtTexture = function(txt, fontFamily) { 85 | var textureTxtLength = txt.length; 86 | var numTextureGridRows = Math.ceil(textureTxtLength / this.numTextureGridCols); 87 | 88 | this.txtCanvas = document.createElement('canvas'); 89 | this.txtCanvasCtx = this.txtCanvas.getContext('2d'); 90 | this.txtCanvas.width = this.textureGridSize * this.numTextureGridCols; 91 | this.txtCanvas.height = this.textureGridSize * numTextureGridRows; 92 | 93 | 94 | // canvasのスタイルを設定 (グリッドのサイズの80%をfontSizeとする) 95 | this.txtCanvasCtx.font = 'normal ' + (this.textureGridSize * 0.8) + 'px ' + fontFamily; 96 | 97 | // グリッドの中心に描画 98 | this.txtCanvasCtx.textAlign = 'center'; 99 | 100 | // 文字色は白 101 | this.txtCanvasCtx.fillStyle = '#ffffff'; 102 | 103 | var colIndex; 104 | var rowIndex; 105 | 106 | for(var i = 0, l = textureTxtLength; i < l; i++) { 107 | // 横方向のインデックス 108 | colIndex = i % this.numTextureGridCols; 109 | 110 | // 縦方向のインデックス 111 | rowIndex = Math.floor(i / this.numTextureGridCols); 112 | 113 | // canvasに文字を描画 114 | this.txtCanvasCtx.fillText( 115 | txt.charAt(i), 116 | 117 | // textAlignをcenterに設定すると、 118 | // 基準位置が第一引数ので指定された文字列の中央になるので 119 | // 横方向は各グリッドの中心座標を指定する 120 | colIndex * this.textureGridSize + this.textureGridSize / 2, 121 | 122 | // 縦方向はベースラインの位置を指定する 123 | rowIndex * this.textureGridSize + this.textureGridSize * 0.8, 124 | 125 | this.textureGridSize 126 | ); 127 | } 128 | 129 | // canvasからthree.jsのテクスチャを生成 130 | this.txtTexture = new THREE.Texture(this.txtCanvas); 131 | this.txtTexture.flipY = false; // UVを反転しない (WebGLのデフォルトにする) 132 | this.txtTexture.needsUpdate = true; // テクスチャを更新 133 | 134 | // シェーダに渡す値をセット 135 | 136 | // テクスチャ 137 | this.material.uniforms.txtTexture.value = this.txtTexture; 138 | 139 | // テクスチャの縦の文字数 140 | this.material.uniforms.numTextureGridRows.value = numTextureGridRows; 141 | 142 | // テクスチャとして使う文字の種類 (txtは1文字ずつユニークである前提) 143 | this.material.uniforms.textureTxtLength.value = textureTxtLength; 144 | 145 | // document.body.appendChild(this.txtCanvas); 146 | // $(this.txtCanvas).css('background-color', '#000'); 147 | // $('#wrapper').remove(); 148 | } 149 | 150 | 151 | /** 152 | * uniformの値をセット 153 | */ 154 | sample.FloatingChars.prototype.setUniform = function(uniformKey, value) { 155 | this.material.uniforms[uniformKey].value = value; 156 | } 157 | 158 | 159 | 160 | 161 | })(); 162 | -------------------------------------------------------------------------------- /assets/js/step5/FloatingCharsGeometry.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var sample = window.sample || {}; 4 | window.sample = sample; 5 | 6 | /** 7 | * THREE.BufferGeometryを拡張した独自Geoemtryクラス 8 | * @param {number} numChars - 文字数 (正方形の数) 9 | * @param {number} charWidth - 文字の幅 [px] 10 | */ 11 | sample.FloatingCharsGeometry = function(numChars, charWidth) { 12 | THREE.BufferGeometry.call(this); 13 | this.numChars = numChars; 14 | this.charWidth = charWidth; 15 | this.init(); 16 | } 17 | 18 | sample.FloatingCharsGeometry.prototype = Object.create(THREE.BufferGeometry.prototype, { value: { constructor: THREE.BufferGeometry }}); 19 | 20 | /** 21 | * イニシャライズ 22 | */ 23 | sample.FloatingCharsGeometry.prototype.init = function() { 24 | // attributes用の配列を生成 25 | var vertices = []; // 頂点 26 | var charIndices = []; // 文字(正方形)のインデックス 27 | var randomValues = []; // 頂点計算等に使用するランダム値 28 | var uvs = []; // UV座標 29 | var indices = []; // インデックス 30 | 31 | var charHeight = this.charWidth; 32 | var charHalfWidth = this.charWidth / 2; 33 | var charHalfHeight = charHeight / 2; 34 | 35 | // this.numCharsの数だけ正方形を生成 36 | for(var i = 0; i < this.numChars; i++) { 37 | 38 | // GLSLで使用するランダムな値 39 | var randomValue = [ 40 | Math.random(), 41 | Math.random(), 42 | Math.random() 43 | ]; 44 | 45 | 46 | // 頂点データを生成 47 | 48 | // 左上 49 | vertices.push(-charHalfWidth); // x 50 | vertices.push(charHalfHeight); // y 51 | vertices.push(0); // z 52 | 53 | uvs.push(0); // u 54 | uvs.push(0); // v 55 | 56 | charIndices.push(i); // 何文字目かを表す番号 57 | 58 | randomValues.push(randomValue[0]); // GLSLで使用するランダムな値 (vec3になるので3つ) 59 | randomValues.push(randomValue[1]); // GLSLで使用するランダムな値 (vec3になるので3つ) 60 | randomValues.push(randomValue[2]); // GLSLで使用するランダムな値 (vec3になるので3つ) 61 | 62 | 63 | // 右上 64 | vertices.push(charHalfWidth); 65 | vertices.push(charHalfHeight); 66 | 67 | vertices.push(0); 68 | 69 | uvs.push(1); 70 | uvs.push(0); 71 | 72 | charIndices.push(i); 73 | 74 | randomValues.push(randomValue[0]); 75 | randomValues.push(randomValue[1]); 76 | randomValues.push(randomValue[2]); 77 | 78 | 79 | // 左下 80 | vertices.push(-charHalfWidth); 81 | vertices.push(-charHalfHeight); 82 | 83 | vertices.push(0); 84 | 85 | uvs.push(0); 86 | uvs.push(1); 87 | 88 | charIndices.push(i); 89 | 90 | randomValues.push(randomValue[0]); 91 | randomValues.push(randomValue[1]); 92 | randomValues.push(randomValue[2]); 93 | 94 | 95 | // 右下 96 | vertices.push(charHalfWidth); 97 | vertices.push(-charHalfHeight); 98 | 99 | vertices.push(0); 100 | 101 | uvs.push(1); 102 | uvs.push(1); 103 | 104 | charIndices.push(i); 105 | 106 | randomValues.push(randomValue[0]); 107 | randomValues.push(randomValue[1]); 108 | randomValues.push(randomValue[2]); 109 | 110 | 111 | // ポリゴンを生成するインデックスをpush (三角形ポリゴンが2枚なので6個) 112 | var indexOffset = i * 4; 113 | 114 | indices.push(indexOffset + 0); // 左上 115 | indices.push(indexOffset + 2); // 左下 116 | indices.push(indexOffset + 1); // 右上 117 | 118 | indices.push(indexOffset + 2); // 左下 119 | indices.push(indexOffset + 3); // 右下 120 | indices.push(indexOffset + 1); // 右上 121 | } 122 | 123 | // attributes 124 | this.addAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3)); // vec3 125 | this.addAttribute('randomValues', new THREE.BufferAttribute(new Float32Array(randomValues), 3)); // vec3 126 | this.addAttribute('charIndex', new THREE.BufferAttribute(new Float32Array(charIndices), 1)); // float 127 | this.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(uvs), 2)); // vec2 128 | 129 | // index 130 | this.setIndex(new THREE.BufferAttribute(new Uint16Array(indices), 1)); 131 | 132 | this.computeVertexNormals(); 133 | } 134 | 135 | })(); 136 | -------------------------------------------------------------------------------- /assets/js/step5/MainVisual.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var sample = window.sample || {}; 4 | window.sample = sample; 5 | 6 | /** 7 | * メインビジュアルクラス 8 | * @param {number} numChars - 文字数 (正方形の数) 9 | * @param {number} charWidth - 文字の幅 [px] 10 | * @param {number} numTextureGridCols - テクスチャの1行文の文字列 [px] 11 | * @param {number} textureGridSize - テクスチャの1文字分の幅 [px] 12 | */ 13 | sample.MainVisual = function(numChars, charWidth, numTextureGridCols, textureGridSize, fontFamily) { 14 | 15 | // 文字数 = 正方形の数 16 | this.numChars = numChars || 1000; 17 | 18 | // 文字の幅[px] (geometryの1文字の幅) 19 | this.charWidth = charWidth || 4; 20 | 21 | // テクスチャの1行文の文字列 22 | this.numTextureGridCols = numTextureGridCols || 16; 23 | 24 | // テクスチャの1文字分の幅 25 | this.textureGridSize = textureGridSize || 128; 26 | 27 | // 使用するフォント名 28 | this.fontFamily = fontFamily || 'Cabin Sketch' 29 | 30 | // アニメーション適用度 31 | // 頂点シェーダ内でアニメーションが3つ定義されており 32 | // それらを切り替えるための値 33 | this.animationValue1 = 1; 34 | this.animationValue2 = 0; 35 | this.animationValue3 = 0; 36 | 37 | // イニシャライズ 38 | this.init(); 39 | } 40 | 41 | /** 42 | * イニシャライズ 43 | */ 44 | sample.MainVisual.prototype.init = function() { 45 | var self = this; 46 | 47 | this.$window = $(window); 48 | 49 | // div#mainを取得 50 | this.$mainVisual = $('#main'); 51 | 52 | // webGL renderer 53 | this.renderer = new THREE.WebGLRenderer({ 54 | canvas: this.$mainVisual.find('canvas').get(0), // HTML上の#contents > #main > canvasのHTMLElementを指定 55 | alpha: true, 56 | antialias: true 57 | }); 58 | 59 | // 高解像度ディスプレイ対応 (2倍がmax) 60 | var pixelRatio = Math.min(window.devicePixelRatio || 1, 2); 61 | this.renderer.setPixelRatio(pixelRatio); 62 | 63 | // scene 64 | this.scene = new THREE.Scene(); 65 | 66 | // camera 67 | this.camera = new THREE.PerspectiveCamera(35, this.width / this.height, 10, 1000); 68 | this.camera.position.set(0, 0, 200); 69 | 70 | // controls 71 | // 第二引数にthis.renderer.domElementを指定しておかないと、dat.guiのGUIがうまく操作できない 72 | this.controls = new THREE.TrackballControls(this.camera, this.renderer.domElement); 73 | 74 | // ウィンドウリサイズイベント 75 | this.$window.on('resize', function(e) { 76 | // resizeメソッドを実行 77 | self.resize(); 78 | }); 79 | 80 | // THREE.Meshを拡張したFloatingCharsをイニシャライズ 81 | // 非同期処理が終了したらリサイズイベントを発火して、アニメーション開始 82 | this.initFloatingChars() 83 | .then(function() { 84 | // resizeイベントを発火してキャンバスサイズをリサイズ 85 | self.$window.trigger('resize'); 86 | 87 | // アニメーション開始 88 | self.start(); 89 | }); 90 | } 91 | 92 | 93 | /** 94 | * floatingCharsをイニシャライズ 95 | */ 96 | sample.MainVisual.prototype.initFloatingChars = function() { 97 | var self = this; 98 | 99 | return new Promise(function(resolve) { 100 | // webfont load event 101 | WebFont.load({ 102 | // Google Fontを使用 103 | google: { 104 | families: [ self.fontFamily ] // フォント名を指定 105 | }, 106 | active: function(fontFamily, fontDescription) { 107 | // ロード完了 108 | console.log('webfonts loaded'); 109 | 110 | // FloatingCharsインスタンス化 111 | self.floatingChars = new sample.FloatingChars( 112 | self.numChars, 113 | self.charWidth, 114 | self.numTextureGridCols, 115 | self.textureGridSize 116 | ); 117 | 118 | // テクスチャをイニシャライズ 119 | // 第一引数は使用する文字 (ユニーク) 120 | // 第二引数は使用するフォント名 121 | self.floatingChars.createTxtTexture('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|:;?<>,.', self.fontFamily); 122 | 123 | // シーンに追加 124 | self.scene.add(self.floatingChars); 125 | 126 | // dat.guiのGUIを生成 127 | self.createDatGUIBox(); 128 | 129 | // 完了 130 | resolve(); 131 | } 132 | }); 133 | }); 134 | } 135 | 136 | 137 | /** 138 | * アニメーション開始 139 | */ 140 | sample.MainVisual.prototype.start = function() { 141 | var self = this; 142 | 143 | var enterFrameHandler = function() { 144 | requestAnimationFrame(enterFrameHandler); 145 | self.update(); 146 | }; 147 | 148 | enterFrameHandler(); 149 | } 150 | 151 | 152 | /** 153 | * アニメーションループ内で実行される 154 | */ 155 | sample.MainVisual.prototype.update = function() { 156 | this.controls.update(); 157 | this.floatingChars.update(this.camera); 158 | this.renderer.render(this.scene, this.camera); 159 | } 160 | 161 | 162 | /** 163 | * リサイズ処理 164 | * @param {jQuery.Event} e - jQueryのイベントオブジェクト 165 | */ 166 | sample.MainVisual.prototype.resize = function() { 167 | this.width = this.$window.width(); 168 | this.height = this.$window.height(); 169 | 170 | // TrackballControlsのリサイズ処理を実行 171 | this.controls.handleResize(); 172 | 173 | // カメラの設定を更新 174 | this.camera.aspect = this.width / this.height; 175 | this.camera.updateProjectionMatrix(); 176 | 177 | // WebGLRendererの設定を更新 178 | this.renderer.setSize(this.width, this.height); 179 | } 180 | 181 | 182 | /** 183 | * dat.gui 184 | * dat.guiのコントローラーを定義 185 | */ 186 | sample.MainVisual.prototype.createDatGUIBox = function() { 187 | var self = this; 188 | 189 | // dat.gui 190 | var gui = new dat.GUI() 191 | 192 | // スライダーのGUIを追加 193 | var controler1 = gui.add(this, 'animationValue1', 0, 1).listen(); 194 | var controler2 = gui.add(this, 'animationValue2', 0, 1).listen(); 195 | var controler3 = gui.add(this, 'animationValue3', 0, 1).listen(); 196 | 197 | // 値をアニメーションさせるためのボタンを設置 198 | // それぞれをクリックすると、animation1, animation2, animation3メソッドが呼ばれる 199 | gui.add(this, 'animation1'); 200 | gui.add(this, 'animation2'); 201 | gui.add(this, 'animation3'); 202 | 203 | // 値の変更時にuniform変数の値を変更 204 | controler1.onChange(function(value) { 205 | self.floatingChars.setUniform('animationValue1', value); 206 | }); 207 | controler2.onChange(function(value) { 208 | self.floatingChars.setUniform('animationValue2', value); 209 | }); 210 | controler3.onChange(function(value) { 211 | self.floatingChars.setUniform('animationValue3', value); 212 | }); 213 | } 214 | 215 | 216 | /** 217 | * animationValueを変更 218 | * @param {number} index - 1 | 2 | 3 (animationValue) 219 | */ 220 | sample.MainVisual.prototype.animate = function(index) { 221 | if(this.animateTween) { 222 | this.animateTween.kill(); 223 | } 224 | 225 | var self = this; 226 | 227 | this.animateTween = TweenMax.to(this, 1, { 228 | ease: Expo.easeOut, 229 | animationValue1: (index == 1)? 1: 0, 230 | animationValue2: (index == 2)? 1: 0, 231 | animationValue3: (index == 3)? 1: 0, 232 | onUpdate: function() { 233 | self.floatingChars.setUniform('animationValue1', self.animationValue1); 234 | self.floatingChars.setUniform('animationValue2', self.animationValue2); 235 | self.floatingChars.setUniform('animationValue3', self.animationValue3); 236 | } 237 | }); 238 | } 239 | 240 | sample.MainVisual.prototype.animation1 = function() { 241 | this.animate(1); 242 | } 243 | sample.MainVisual.prototype.animation2 = function() { 244 | this.animate(2); 245 | } 246 | sample.MainVisual.prototype.animation3 = function() { 247 | this.animate(3); 248 | } 249 | 250 | 251 | })(); 252 | -------------------------------------------------------------------------------- /assets/js/step5/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var sample = window.sample || {}; 4 | window.sample = sample; 5 | 6 | $(function() { 7 | new sample.MainVisual(); 8 | }); 9 | 10 | })(); 11 | -------------------------------------------------------------------------------- /step1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2016.12.3 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /step2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2016.12.3 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /step3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2016.12.3 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 93 | 110 | 111 | -------------------------------------------------------------------------------- /step4/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2016.12.3 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 152 | 169 | 170 | -------------------------------------------------------------------------------- /step5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2016.12.3 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 226 | 245 | 246 | --------------------------------------------------------------------------------