├── README.md ├── demo ├── assets │ ├── LeePerrySmith.js │ ├── Map-COL.jpg │ ├── Map-NOR.jpg │ ├── Map-SPEC.jpg │ ├── man.png │ ├── pattern.jpg │ ├── snapshot.jpg │ ├── splatter.png │ ├── white-splatter.png │ └── wrinkle-normal.jpg ├── intersection.html ├── js │ ├── OrbitControls.js │ ├── Three.js │ └── dat.gui.min.js └── splatter.html └── src └── THREE.DecalGeometry.js /README.md: -------------------------------------------------------------------------------- 1 | THREE.DecalGeometry.js 2 | ========================= 3 | 4 | This object creates a decal geometry, intersecting a cube against a THREE.Geometry. Based on this article [How to project decals](http://blog.wolfire.com/2009/06/how-to-project-decals/). It interesects a cube against an arbitrary geometry and clips vertex coordinates and normals. 5 | 6 |  7 | 8 | Demo is here: [Decal Splatter](http://clicktorelease.com/code/decal-splatter). 9 | 10 | How to use 11 | ---------- 12 | 13 | Include the library: 14 |
<script src="THREE.DecalGeometry.js" ></script>
15 |
16 | Instantiate a geometry passing:
17 | var decalGeometry = new THREE.DecalGeometry(
18 | meshToIntersect, // it has to be a THREE.Mesh
19 | position, // THREE.Vector3 in world coordinates
20 | direction, // THREE.Vector3 specifying the orientation of the decal
21 | dimensions, // THREE.Vector3 specifying the size of the decal box
22 | check // THREE.Vector3 specifying what sides to clip (1-clip, 0-noclip)
23 | );
24 |
25 | and create a mesh using that geometry, as usual:
26 |
27 | var mesh = new THREE.Mesh( decalGeometry, decalMaterial );
28 |
29 | The decal material can be any material, just make sure to have this attributes enabled:
30 |
31 | var decalMaterial = new THREE.MeshNormalMaterial( {
32 | transparent: true,
33 | depthTest: true,
34 | depthWrite: false,
35 | polygonOffset: true,
36 | polygonOffsetFactor: -4,
37 | });
38 |
39 | License
40 | -------
41 |
42 | MIT licensed
43 |
44 | Copyright (C) 2014 Jaume Sanchez Elias http://twitter.com/thespite
45 | Lee Perry Smith's head model and textures by [Infinite Realities](http://www.ir-ltd.net/)
46 |
47 | http://www.clicktorelease.com
--------------------------------------------------------------------------------
/demo/assets/Map-COL.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/THREE.DecalGeometry/546cebafebd34b4b10cc47c2d6412310e6372159/demo/assets/Map-COL.jpg
--------------------------------------------------------------------------------
/demo/assets/Map-NOR.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/THREE.DecalGeometry/546cebafebd34b4b10cc47c2d6412310e6372159/demo/assets/Map-NOR.jpg
--------------------------------------------------------------------------------
/demo/assets/Map-SPEC.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/THREE.DecalGeometry/546cebafebd34b4b10cc47c2d6412310e6372159/demo/assets/Map-SPEC.jpg
--------------------------------------------------------------------------------
/demo/assets/man.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/THREE.DecalGeometry/546cebafebd34b4b10cc47c2d6412310e6372159/demo/assets/man.png
--------------------------------------------------------------------------------
/demo/assets/pattern.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/THREE.DecalGeometry/546cebafebd34b4b10cc47c2d6412310e6372159/demo/assets/pattern.jpg
--------------------------------------------------------------------------------
/demo/assets/snapshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/THREE.DecalGeometry/546cebafebd34b4b10cc47c2d6412310e6372159/demo/assets/snapshot.jpg
--------------------------------------------------------------------------------
/demo/assets/splatter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/THREE.DecalGeometry/546cebafebd34b4b10cc47c2d6412310e6372159/demo/assets/splatter.png
--------------------------------------------------------------------------------
/demo/assets/white-splatter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/THREE.DecalGeometry/546cebafebd34b4b10cc47c2d6412310e6372159/demo/assets/white-splatter.png
--------------------------------------------------------------------------------
/demo/assets/wrinkle-normal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spite/THREE.DecalGeometry/546cebafebd34b4b10cc47c2d6412310e6372159/demo/assets/wrinkle-normal.jpg
--------------------------------------------------------------------------------
/demo/intersection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Decal test | Click/tap and drag to rotate, mouse wheel/pinch to zoom, space to toggle animation. 27 |
28 | 29 | 30 | 31 | 32 | 33 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /demo/js/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | /*global THREE, console */ 9 | 10 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains 11 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is 12 | // supported. 13 | // 14 | // Orbit - left mouse / touch: one finger move 15 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 16 | // Pan - right mouse, or arrow keys / touch: three finter swipe 17 | // 18 | // This is a drop-in replacement for (most) TrackballControls used in examples. 19 | // That is, include this js file and wherever you see: 20 | // controls = new THREE.TrackballControls( camera ); 21 | // controls.target.z = 150; 22 | // Simple substitute "OrbitControls" and the control should work as-is. 23 | 24 | THREE.OrbitControls = function ( object, domElement ) { 25 | 26 | this.object = object; 27 | this.domElement = ( domElement !== undefined ) ? domElement : document; 28 | 29 | // API 30 | 31 | // Set to false to disable this control 32 | this.enabled = true; 33 | 34 | // "target" sets the location of focus, where the control orbits around 35 | // and where it pans with respect to. 36 | this.target = new THREE.Vector3(); 37 | 38 | // center is old, deprecated; use "target" instead 39 | this.center = this.target; 40 | 41 | // This option actually enables dollying in and out; left as "zoom" for 42 | // backwards compatibility 43 | this.noZoom = false; 44 | this.zoomSpeed = 1.0; 45 | 46 | // Limits to how far you can dolly in and out 47 | this.minDistance = 0; 48 | this.maxDistance = Infinity; 49 | 50 | // Set to true to disable this control 51 | this.noRotate = false; 52 | this.rotateSpeed = 1.0; 53 | 54 | // Set to true to disable this control 55 | this.noPan = false; 56 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 57 | 58 | // Set to true to automatically rotate around the target 59 | this.autoRotate = false; 60 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 61 | 62 | // How far you can orbit vertically, upper and lower limits. 63 | // Range is 0 to Math.PI radians. 64 | this.minPolarAngle = 0; // radians 65 | this.maxPolarAngle = Math.PI; // radians 66 | 67 | // Set to true to disable use of the keys 68 | this.noKeys = false; 69 | 70 | // The four arrow keys 71 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 72 | 73 | //////////// 74 | // internals 75 | 76 | var scope = this; 77 | 78 | var EPS = 0.000001; 79 | 80 | var rotateStart = new THREE.Vector2(); 81 | var rotateEnd = new THREE.Vector2(); 82 | var rotateDelta = new THREE.Vector2(); 83 | 84 | var panStart = new THREE.Vector2(); 85 | var panEnd = new THREE.Vector2(); 86 | var panDelta = new THREE.Vector2(); 87 | var panOffset = new THREE.Vector3(); 88 | 89 | var offset = new THREE.Vector3(); 90 | 91 | var dollyStart = new THREE.Vector2(); 92 | var dollyEnd = new THREE.Vector2(); 93 | var dollyDelta = new THREE.Vector2(); 94 | 95 | var phiDelta = 0; 96 | var thetaDelta = 0; 97 | var scale = 1; 98 | var pan = new THREE.Vector3(); 99 | 100 | var lastPosition = new THREE.Vector3(); 101 | 102 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 103 | 104 | var state = STATE.NONE; 105 | 106 | // for reset 107 | 108 | this.target0 = this.target.clone(); 109 | this.position0 = this.object.position.clone(); 110 | 111 | // so camera.up is the orbit axis 112 | 113 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 114 | var quatInverse = quat.clone().inverse(); 115 | 116 | // events 117 | 118 | var changeEvent = { type: 'change' }; 119 | var startEvent = { type: 'start'}; 120 | var endEvent = { type: 'end'}; 121 | 122 | this.rotateLeft = function ( angle ) { 123 | 124 | if ( angle === undefined ) { 125 | 126 | angle = getAutoRotationAngle(); 127 | 128 | } 129 | 130 | thetaDelta -= angle; 131 | 132 | }; 133 | 134 | this.rotateUp = function ( angle ) { 135 | 136 | if ( angle === undefined ) { 137 | 138 | angle = getAutoRotationAngle(); 139 | 140 | } 141 | 142 | phiDelta -= angle; 143 | 144 | }; 145 | 146 | // pass in distance in world space to move left 147 | this.panLeft = function ( distance ) { 148 | 149 | var te = this.object.matrix.elements; 150 | 151 | // get X column of matrix 152 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 153 | panOffset.multiplyScalar( - distance ); 154 | 155 | pan.add( panOffset ); 156 | 157 | }; 158 | 159 | // pass in distance in world space to move up 160 | this.panUp = function ( distance ) { 161 | 162 | var te = this.object.matrix.elements; 163 | 164 | // get Y column of matrix 165 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 166 | panOffset.multiplyScalar( distance ); 167 | 168 | pan.add( panOffset ); 169 | 170 | }; 171 | 172 | // pass in x,y of change desired in pixel space, 173 | // right and down are positive 174 | this.pan = function ( deltaX, deltaY ) { 175 | 176 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 177 | 178 | if ( scope.object.fov !== undefined ) { 179 | 180 | // perspective 181 | var position = scope.object.position; 182 | var offset = position.clone().sub( scope.target ); 183 | var targetDistance = offset.length(); 184 | 185 | // half of the fov is center to top of screen 186 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 187 | 188 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 189 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); 190 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); 191 | 192 | } else if ( scope.object.top !== undefined ) { 193 | 194 | // orthographic 195 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); 196 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); 197 | 198 | } else { 199 | 200 | // camera neither orthographic or perspective 201 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 202 | 203 | } 204 | 205 | }; 206 | 207 | this.dollyIn = function ( dollyScale ) { 208 | 209 | if ( dollyScale === undefined ) { 210 | 211 | dollyScale = getZoomScale(); 212 | 213 | } 214 | 215 | scale /= dollyScale; 216 | 217 | }; 218 | 219 | this.dollyOut = function ( dollyScale ) { 220 | 221 | if ( dollyScale === undefined ) { 222 | 223 | dollyScale = getZoomScale(); 224 | 225 | } 226 | 227 | scale *= dollyScale; 228 | 229 | }; 230 | 231 | this.update = function () { 232 | 233 | var position = this.object.position; 234 | 235 | offset.copy( position ).sub( this.target ); 236 | 237 | // rotate offset to "y-axis-is-up" space 238 | offset.applyQuaternion( quat ); 239 | 240 | // angle from z-axis around y-axis 241 | 242 | var theta = Math.atan2( offset.x, offset.z ); 243 | 244 | // angle from y-axis 245 | 246 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 247 | 248 | if ( this.autoRotate ) { 249 | 250 | this.rotateLeft( getAutoRotationAngle() ); 251 | 252 | } 253 | 254 | theta += thetaDelta; 255 | phi += phiDelta; 256 | 257 | // restrict phi to be between desired limits 258 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 259 | 260 | // restrict phi to be betwee EPS and PI-EPS 261 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 262 | 263 | var radius = offset.length() * scale; 264 | 265 | // restrict radius to be between desired limits 266 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 267 | 268 | // move target to panned location 269 | this.target.add( pan ); 270 | 271 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 272 | offset.y = radius * Math.cos( phi ); 273 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 274 | 275 | // rotate offset back to "camera-up-vector-is-up" space 276 | offset.applyQuaternion( quatInverse ); 277 | 278 | position.copy( this.target ).add( offset ); 279 | 280 | this.object.lookAt( this.target ); 281 | 282 | thetaDelta = 0; 283 | phiDelta = 0; 284 | scale = 1; 285 | pan.set( 0, 0, 0 ); 286 | 287 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS ) { 288 | 289 | this.dispatchEvent( changeEvent ); 290 | 291 | lastPosition.copy( this.object.position ); 292 | 293 | } 294 | 295 | }; 296 | 297 | 298 | this.reset = function () { 299 | 300 | state = STATE.NONE; 301 | 302 | this.target.copy( this.target0 ); 303 | this.object.position.copy( this.position0 ); 304 | 305 | this.update(); 306 | 307 | }; 308 | 309 | function getAutoRotationAngle() { 310 | 311 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 312 | 313 | } 314 | 315 | function getZoomScale() { 316 | 317 | return Math.pow( 0.95, scope.zoomSpeed ); 318 | 319 | } 320 | 321 | function onMouseDown( event ) { 322 | 323 | if ( scope.enabled === false ) return; 324 | event.preventDefault(); 325 | 326 | if ( event.button === 0 ) { 327 | if ( scope.noRotate === true ) return; 328 | 329 | state = STATE.ROTATE; 330 | 331 | rotateStart.set( event.clientX, event.clientY ); 332 | 333 | } else if ( event.button === 1 ) { 334 | if ( scope.noZoom === true ) return; 335 | 336 | state = STATE.DOLLY; 337 | 338 | dollyStart.set( event.clientX, event.clientY ); 339 | 340 | } else if ( event.button === 2 ) { 341 | if ( scope.noPan === true ) return; 342 | 343 | state = STATE.PAN; 344 | 345 | panStart.set( event.clientX, event.clientY ); 346 | 347 | } 348 | 349 | scope.domElement.addEventListener( 'mousemove', onMouseMove, false ); 350 | scope.domElement.addEventListener( 'mouseup', onMouseUp, false ); 351 | scope.dispatchEvent( startEvent ); 352 | 353 | } 354 | 355 | function onMouseMove( event ) { 356 | 357 | if ( scope.enabled === false ) return; 358 | 359 | event.preventDefault(); 360 | 361 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 362 | 363 | if ( state === STATE.ROTATE ) { 364 | 365 | if ( scope.noRotate === true ) return; 366 | 367 | rotateEnd.set( event.clientX, event.clientY ); 368 | rotateDelta.subVectors( rotateEnd, rotateStart ); 369 | 370 | // rotating across whole screen goes 360 degrees around 371 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 372 | 373 | // rotating up and down along whole screen attempts to go 360, but limited to 180 374 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 375 | 376 | rotateStart.copy( rotateEnd ); 377 | 378 | } else if ( state === STATE.DOLLY ) { 379 | 380 | if ( scope.noZoom === true ) return; 381 | 382 | dollyEnd.set( event.clientX, event.clientY ); 383 | dollyDelta.subVectors( dollyEnd, dollyStart ); 384 | 385 | if ( dollyDelta.y > 0 ) { 386 | 387 | scope.dollyIn(); 388 | 389 | } else { 390 | 391 | scope.dollyOut(); 392 | 393 | } 394 | 395 | dollyStart.copy( dollyEnd ); 396 | 397 | } else if ( state === STATE.PAN ) { 398 | 399 | if ( scope.noPan === true ) return; 400 | 401 | panEnd.set( event.clientX, event.clientY ); 402 | panDelta.subVectors( panEnd, panStart ); 403 | 404 | scope.pan( panDelta.x, panDelta.y ); 405 | 406 | panStart.copy( panEnd ); 407 | 408 | } 409 | 410 | scope.update(); 411 | 412 | } 413 | 414 | function onMouseUp( /* event */ ) { 415 | 416 | if ( scope.enabled === false ) return; 417 | 418 | scope.domElement.removeEventListener( 'mousemove', onMouseMove, false ); 419 | scope.domElement.removeEventListener( 'mouseup', onMouseUp, false ); 420 | scope.dispatchEvent( endEvent ); 421 | state = STATE.NONE; 422 | 423 | } 424 | 425 | function onMouseWheel( event ) { 426 | 427 | if ( scope.enabled === false || scope.noZoom === true ) return; 428 | 429 | event.preventDefault(); 430 | event.stopPropagation(); 431 | 432 | var delta = 0; 433 | 434 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 435 | 436 | delta = event.wheelDelta; 437 | 438 | } else if ( event.detail !== undefined ) { // Firefox 439 | 440 | delta = - event.detail; 441 | 442 | } 443 | 444 | if ( delta > 0 ) { 445 | 446 | scope.dollyOut(); 447 | 448 | } else { 449 | 450 | scope.dollyIn(); 451 | 452 | } 453 | 454 | scope.update(); 455 | scope.dispatchEvent( startEvent ); 456 | scope.dispatchEvent( changeEvent ); 457 | scope.dispatchEvent( endEvent ); 458 | 459 | } 460 | 461 | function onKeyDown( event ) { 462 | 463 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; 464 | 465 | switch ( event.keyCode ) { 466 | 467 | case scope.keys.UP: 468 | scope.pan( 0, scope.keyPanSpeed ); 469 | scope.update(); 470 | break; 471 | 472 | case scope.keys.BOTTOM: 473 | scope.pan( 0, - scope.keyPanSpeed ); 474 | scope.update(); 475 | break; 476 | 477 | case scope.keys.LEFT: 478 | scope.pan( scope.keyPanSpeed, 0 ); 479 | scope.update(); 480 | break; 481 | 482 | case scope.keys.RIGHT: 483 | scope.pan( - scope.keyPanSpeed, 0 ); 484 | scope.update(); 485 | break; 486 | 487 | } 488 | 489 | } 490 | 491 | function touchstart( event ) { 492 | 493 | if ( scope.enabled === false ) return; 494 | 495 | switch ( event.touches.length ) { 496 | 497 | case 1: // one-fingered touch: rotate 498 | 499 | if ( scope.noRotate === true ) return; 500 | 501 | state = STATE.TOUCH_ROTATE; 502 | 503 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 504 | break; 505 | 506 | case 2: // two-fingered touch: dolly 507 | 508 | if ( scope.noZoom === true ) return; 509 | 510 | state = STATE.TOUCH_DOLLY; 511 | 512 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 513 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 514 | var distance = Math.sqrt( dx * dx + dy * dy ); 515 | dollyStart.set( 0, distance ); 516 | break; 517 | 518 | case 3: // three-fingered touch: pan 519 | 520 | if ( scope.noPan === true ) return; 521 | 522 | state = STATE.TOUCH_PAN; 523 | 524 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 525 | break; 526 | 527 | default: 528 | 529 | state = STATE.NONE; 530 | 531 | } 532 | 533 | scope.dispatchEvent( startEvent ); 534 | 535 | } 536 | 537 | function touchmove( event ) { 538 | 539 | if ( scope.enabled === false ) return; 540 | 541 | event.preventDefault(); 542 | event.stopPropagation(); 543 | 544 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 545 | 546 | switch ( event.touches.length ) { 547 | 548 | case 1: // one-fingered touch: rotate 549 | 550 | if ( scope.noRotate === true ) return; 551 | if ( state !== STATE.TOUCH_ROTATE ) return; 552 | 553 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 554 | rotateDelta.subVectors( rotateEnd, rotateStart ); 555 | 556 | // rotating across whole screen goes 360 degrees around 557 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 558 | // rotating up and down along whole screen attempts to go 360, but limited to 180 559 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 560 | 561 | rotateStart.copy( rotateEnd ); 562 | 563 | scope.update(); 564 | break; 565 | 566 | case 2: // two-fingered touch: dolly 567 | 568 | if ( scope.noZoom === true ) return; 569 | if ( state !== STATE.TOUCH_DOLLY ) return; 570 | 571 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 572 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 573 | var distance = Math.sqrt( dx * dx + dy * dy ); 574 | 575 | dollyEnd.set( 0, distance ); 576 | dollyDelta.subVectors( dollyEnd, dollyStart ); 577 | 578 | if ( dollyDelta.y > 0 ) { 579 | 580 | scope.dollyOut(); 581 | 582 | } else { 583 | 584 | scope.dollyIn(); 585 | 586 | } 587 | 588 | dollyStart.copy( dollyEnd ); 589 | 590 | scope.update(); 591 | break; 592 | 593 | case 3: // three-fingered touch: pan 594 | 595 | if ( scope.noPan === true ) return; 596 | if ( state !== STATE.TOUCH_PAN ) return; 597 | 598 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 599 | panDelta.subVectors( panEnd, panStart ); 600 | 601 | scope.pan( panDelta.x, panDelta.y ); 602 | 603 | panStart.copy( panEnd ); 604 | 605 | scope.update(); 606 | break; 607 | 608 | default: 609 | 610 | state = STATE.NONE; 611 | 612 | } 613 | 614 | } 615 | 616 | function touchend( /* event */ ) { 617 | 618 | if ( scope.enabled === false ) return; 619 | 620 | scope.dispatchEvent( endEvent ); 621 | state = STATE.NONE; 622 | 623 | } 624 | 625 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 626 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 627 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 628 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 629 | 630 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 631 | this.domElement.addEventListener( 'touchend', touchend, false ); 632 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 633 | 634 | window.addEventListener( 'keydown', onKeyDown, false ); 635 | 636 | // force an update at start 637 | this.update(); 638 | 639 | }; 640 | 641 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); -------------------------------------------------------------------------------- /demo/js/dat.gui.min.js: -------------------------------------------------------------------------------- 1 | var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(e,a){a=a||document;var b=a.createElement("link");b.type="text/css";b.rel="stylesheet";b.href=e;a.getElementsByTagName("head")[0].appendChild(b)},inject:function(e,a){a=a||document;var b=document.createElement("style");b.type="text/css";b.innerHTML=e;a.getElementsByTagName("head")[0].appendChild(b)}}}(); 2 | dat.utils.common=function(){var e=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(b){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(a[f])||(b[f]=a[f])},this);return b},defaults:function(b){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(b[f])&&(b[f]=a[f])},this);return b},compose:function(){var b=a.call(arguments);return function(){for(var d=a.call(arguments),f=b.length-1;0<=f;f--)d=[b[f].apply(this,d)];return d[0]}}, 3 | each:function(a,d,f){if(e&&a.forEach===e)a.forEach(d,f);else if(a.length===a.length+0)for(var c=0,p=a.length;cthis.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return b.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__step=a;return this}});return b}(dat.controllers.Controller,dat.utils.common);
17 | dat.controllers.NumberControllerBox=function(e,a,b){var d=function(f,c,e){function k(){var a=parseFloat(n.__input.value);b.isNaN(a)||n.setValue(a)}function l(a){var c=r-a.clientY;n.setValue(n.getValue()+c*n.__impliedStep);r=a.clientY}function q(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",q)}this.__truncationSuspended=!1;d.superclass.call(this,f,c,e);var n=this,r;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",k);a.bind(this.__input,
18 | "blur",function(){k();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(c){a.bind(window,"mousemove",l);a.bind(window,"mouseup",q);r=c.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;b.extend(d.prototype,e.prototype,{updateDisplay:function(){var a=this.__input,c;if(this.__truncationSuspended)c=
19 | this.getValue();else{c=this.getValue();var b=Math.pow(10,this.__precision);c=Math.round(c*b)/b}a.value=c;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common);
20 | dat.controllers.NumberControllerSlider=function(e,a,b,d,f){function c(a,c,d,b,f){return b+(a-c)/(d-c)*(f-b)}var p=function(d,b,f,e,r){function y(d){d.preventDefault();var b=a.getOffset(h.__background),f=a.getWidth(h.__background);h.setValue(c(d.clientX,b.left,b.left+f,h.__min,h.__max));return!1}function g(){a.unbind(window,"mousemove",y);a.unbind(window,"mouseup",g);h.__onFinishChange&&h.__onFinishChange.call(h,h.getValue())}p.superclass.call(this,d,b,{min:f,max:e,step:r});var h=this;this.__background=
21 | document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(c){a.bind(window,"mousemove",y);a.bind(window,"mouseup",g);y(c)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=e;p.useDefaultStyles=function(){b.inject(f)};d.extend(p.prototype,e.prototype,{updateDisplay:function(){var a=
22 | (this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.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}");
23 | dat.controllers.FunctionController=function(e,a,b){var d=function(b,c,e){d.superclass.call(this,b,c);var k=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===e?"Fire":e;a.bind(this.__button,"click",function(a){a.preventDefault();k.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};d.superclass=e;b.extend(d.prototype,e.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.__onFinishChange&&this.__onFinishChange.call(this,
24 | this.getValue());this.getValue().call(this.object)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common);
25 | dat.controllers.BooleanController=function(e,a,b){var d=function(b,c){d.superclass.call(this,b,c);var e=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){e.setValue(!e.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};d.superclass=e;b.extend(d.prototype,e.prototype,{setValue:function(a){a=d.superclass.prototype.setValue.call(this,a);this.__onFinishChange&&
26 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common);
27 | dat.color.toString=function(e){return function(a){if(1==a.a||e.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common);
28 | dat.color.interpret=function(e,a){var b,d,f=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:e},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:e},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
29 | return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:e},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:e}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return 3!=
30 | a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return 4!=a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(c){return a.isNumber(c.r)&&a.isNumber(c.g)&&a.isNumber(c.b)&&a.isNumber(c.a)?{space:"RGB",r:c.r,g:c.g,b:c.b,a:c.a}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(c){return a.isNumber(c.r)&&
31 | a.isNumber(c.g)&&a.isNumber(c.b)?{space:"RGB",r:c.r,g:c.g,b:c.b}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(c){return a.isNumber(c.h)&&a.isNumber(c.s)&&a.isNumber(c.v)&&a.isNumber(c.a)?{space:"HSV",h:c.h,s:c.s,v:c.v,a:c.a}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(d){return a.isNumber(d.h)&&a.isNumber(d.s)&&a.isNumber(d.v)?{space:"HSV",h:d.h,s:d.s,v:d.v}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){d=!1;
32 | var c=1 Decal Splatter | Click or tap and drag to rotate, mouse wheel or pinch to zoom, click or tap to shoot paint. Find the code here: THREE.DecalGeometry on GitHub.GUI
\'s constructor:\n\n \n\n localStorage
on exit.\n\n 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