├── 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 | ![Image](https://raw.githubusercontent.com/spite/THREE.DecalGeometry/master/demo/assets/snapshot.jpg) 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 5 | 6 | 7 | 22 | 23 | 24 | 25 |
26 |

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\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', 59 | ".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row,
  • */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n 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;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", 60 | dat.controllers.factory=function(e,a,b,d,f,c,p){return function(k,l,q,n){var r=k[l];if(p.isArray(q)||p.isObject(q))return new e(k,l,q);if(p.isNumber(r))return p.isNumber(q)&&p.isNumber(n)?new b(k,l,q,n):new a(k,l,{min:q,max:n});if(p.isString(r))return new d(k,l);if(p.isFunction(r))return new f(k,l,"");if(p.isBoolean(r))return new c(k,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(e,a,b){var d= 61 | function(b,c){function e(){k.setValue(k.__input.value)}d.superclass.call(this,b,c);var k=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",e);a.bind(this.__input,"change",e);a.bind(this.__input,"blur",function(){k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;b.extend(d.prototype, 62 | e.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, 63 | dat.controllers.ColorController=function(e,a,b,d,f){function c(a,b,d,c){a.style.background="";f.each(l,function(e){a.style.cssText+="background: "+e+"linear-gradient("+b+", "+d+" 0%, "+c+" 100%); "})}function p(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; 64 | a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var k=function(e,n){function r(b){t(b);a.bind(window,"mousemove",t);a.bind(window, 65 | "mouseup",l)}function l(){a.unbind(window,"mousemove",t);a.unbind(window,"mouseup",l)}function g(){var a=d(this.value);!1!==a?(s.__color.__state=a,s.setValue(s.__color.toOriginal())):this.value=s.__color.toString()}function h(){a.unbind(window,"mousemove",u);a.unbind(window,"mouseup",h)}function t(b){b.preventDefault();var d=a.getWidth(s.__saturation_field),c=a.getOffset(s.__saturation_field),e=(b.clientX-c.left+document.body.scrollLeft)/d;b=1-(b.clientY-c.top+document.body.scrollTop)/d;1 66 | b&&(b=0);1e&&(e=0);s.__color.v=b;s.__color.s=e;s.setValue(s.__color.toOriginal());return!1}function u(b){b.preventDefault();var d=a.getHeight(s.__hue_field),c=a.getOffset(s.__hue_field);b=1-(b.clientY-c.top+document.body.scrollTop)/d;1b&&(b=0);s.__color.h=360*b;s.setValue(s.__color.toOriginal());return!1}k.superclass.call(this,e,n);this.__color=new b(this.getValue());this.__temp=new b(0);var s=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1); 67 | this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=document.createElement("input"); 68 | this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(s.__selector,"drag")})});var v=document.createElement("div");f.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});f.extend(this.__field_knob.style, 69 | {position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(0.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});f.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});f.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});f.extend(v.style,{width:"100%",height:"100%", 70 | background:"none"});c(v,"top","rgba(0,0,0,0)","#000");f.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);f.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",r);a.bind(this.__field_knob,"mousedown",r);a.bind(this.__hue_field,"mousedown",function(b){u(b);a.bind(window, 71 | "mousemove",u);a.bind(window,"mouseup",h)});this.__saturation_field.appendChild(v);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};k.superclass=e;f.extend(k.prototype,e.prototype,{updateDisplay:function(){var a=d(this.getValue());if(!1!==a){var e=!1; 72 | f.each(b.COMPONENTS,function(b){if(!f.isUndefined(a[b])&&!f.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return e=!0,{}},this);e&&f.extend(this.__color.__state,a)}f.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var k=0.5>this.__color.v||0.5a&&(a+=1);return{h:360*a,s:e/c,v:c/255}},rgb_to_hex:function(a,b,d){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,b);return a=this.hex_with_component(a,0,d)},component_from_hex:function(a,b){return a>>8*b&255},hex_with_component:function(a,b,d){return d<<(e=8*b)|a&~(255< 2 | 3 | 4 | Decal Splatter 5 | 6 | 7 | 23 | 24 | 25 | 26 |
    27 |
    28 |

    Decal Splatter | Click or tap and drag to rotate, mouse wheel or pinch to zoom, click or tap to shoot paint.

    29 |

    Find the code here: THREE.DecalGeometry on GitHub.

    30 |
    31 | 32 | 33 | 34 | 35 | 36 | 37 | 385 | 386 | 387 | 388 | -------------------------------------------------------------------------------- /src/THREE.DecalGeometry.js: -------------------------------------------------------------------------------- 1 | //goog.provide( 'green.THREEAddOns' ); 2 | 3 | THREE.DecalVertex = function( v, n ) { 4 | 5 | this.vertex = v; 6 | this.normal = n; 7 | 8 | } 9 | 10 | THREE.DecalVertex.prototype.clone = function() { 11 | 12 | return new THREE.DecalVertex( this.vertex.clone(), this.normal.clone() ); 13 | 14 | } 15 | 16 | THREE.DecalGeometry = function( mesh, position, rotation, dimensions, check ) { 17 | 18 | THREE.Geometry.call( this ); 19 | 20 | if( check === undefined ) check = null; 21 | check = check || new THREE.Vector3( 1, 1, 1 ); 22 | 23 | this.uvs = []; 24 | 25 | this.cube = new THREE.Mesh( new THREE.BoxGeometry( dimensions.x, dimensions.y, dimensions.z ), new THREE.MeshBasicMaterial() ); 26 | this.cube.rotation.set( rotation.x, rotation.y, rotation.z ); 27 | this.cube.position.copy( position ); 28 | this.cube.scale.set( 1, 1, 1 ); 29 | this.cube.updateMatrix(); 30 | 31 | this.iCubeMatrix = ( new THREE.Matrix4() ).getInverse( this.cube.matrix ); 32 | 33 | this.faceIndices = [ 'a', 'b', 'c', 'd' ]; 34 | 35 | this.clipFace = function( inVertices, plane ) { 36 | 37 | var size = .5 * Math.abs( ( dimensions.clone() ).dot( plane ) ); 38 | 39 | function clip( v0, v1, p ) { 40 | 41 | var d0 = v0.vertex.dot( p ) - size, 42 | d1 = v1.vertex.dot( p ) - size; 43 | 44 | var s = d0 / ( d0 - d1 ); 45 | var v = new THREE.DecalVertex( 46 | new THREE.Vector3( 47 | v0.vertex.x + s * ( v1.vertex.x - v0.vertex.x ), 48 | v0.vertex.y + s * ( v1.vertex.y - v0.vertex.y ), 49 | v0.vertex.z + s * ( v1.vertex.z - v0.vertex.z ) 50 | ), 51 | new THREE.Vector3( 52 | v0.normal.x + s * ( v1.normal.x - v0.normal.x ), 53 | v0.normal.y + s * ( v1.normal.y - v0.normal.y ), 54 | v0.normal.z + s * ( v1.normal.z - v0.normal.z ) 55 | ) 56 | ); 57 | 58 | // need to clip more values (texture coordinates)? do it this way: 59 | //intersectpoint.value = a.value + s*(b.value-a.value); 60 | 61 | return v; 62 | 63 | } 64 | 65 | if( inVertices.length === 0 ) return []; 66 | var outVertices = []; 67 | 68 | for( var j = 0; j < inVertices.length; j += 3 ) { 69 | 70 | var v1Out, v2Out, v3Out, total = 0; 71 | 72 | var d1 = inVertices[ j + 0 ].vertex.dot( plane ) - size, 73 | d2 = inVertices[ j + 1 ].vertex.dot( plane ) - size, 74 | d3 = inVertices[ j + 2 ].vertex.dot( plane ) - size; 75 | 76 | v1Out = d1 > 0; 77 | v2Out = d2 > 0; 78 | v3Out = d3 > 0; 79 | 80 | total = ( v1Out?1:0 ) + ( v2Out?1:0 ) + ( v3Out?1:0 ); 81 | 82 | switch( total ) { 83 | case 0:{ 84 | outVertices.push( inVertices[ j ] ); 85 | outVertices.push( inVertices[ j + 1 ] ); 86 | outVertices.push( inVertices[ j + 2 ] ); 87 | break; 88 | } 89 | case 1:{ 90 | var nV1, nV2, nV3; 91 | if( v1Out ) { 92 | nV1 = inVertices[ j + 1 ]; 93 | nV2 = inVertices[ j + 2 ]; 94 | nV3 = clip( inVertices[ j ], nV1, plane ); 95 | nV4 = clip( inVertices[ j ], nV2, plane ); 96 | } 97 | if( v2Out ) { 98 | nV1 = inVertices[ j ]; 99 | nV2 = inVertices[ j + 2 ]; 100 | nV3 = clip( inVertices[ j + 1 ], nV1, plane ); 101 | nV4 = clip( inVertices[ j + 1 ], nV2, plane ); 102 | 103 | outVertices.push( nV3 ); 104 | outVertices.push( nV2.clone() ); 105 | outVertices.push( nV1.clone() ); 106 | 107 | outVertices.push( nV2.clone() ); 108 | outVertices.push( nV3.clone() ); 109 | outVertices.push( nV4 ); 110 | break; 111 | } 112 | if( v3Out ) { 113 | nV1 = inVertices[ j ]; 114 | nV2 = inVertices[ j + 1 ]; 115 | nV3 = clip( inVertices[ j + 2 ], nV1, plane ); 116 | nV4 = clip( inVertices[ j + 2 ], nV2, plane ); 117 | } 118 | 119 | outVertices.push( nV1.clone() ); 120 | outVertices.push( nV2.clone() ); 121 | outVertices.push( nV3 ); 122 | 123 | outVertices.push( nV4 ); 124 | outVertices.push( nV3.clone() ); 125 | outVertices.push( nV2.clone() ); 126 | 127 | break; 128 | } 129 | case 2: { 130 | var nV1, nV2, nV3; 131 | if( !v1Out ) { 132 | nV1 = inVertices[ j ].clone(); 133 | nV2 = clip( nV1, inVertices[ j + 1 ], plane ); 134 | nV3 = clip( nV1, inVertices[ j + 2 ], plane ); 135 | outVertices.push( nV1 ); 136 | outVertices.push( nV2 ); 137 | outVertices.push( nV3 ); 138 | } 139 | if( !v2Out ) { 140 | nV1 = inVertices[ j + 1 ].clone(); 141 | nV2 = clip( nV1, inVertices[ j + 2 ], plane ); 142 | nV3 = clip( nV1, inVertices[ j ], plane ); 143 | outVertices.push( nV1 ); 144 | outVertices.push( nV2 ); 145 | outVertices.push( nV3 ); 146 | } 147 | if( !v3Out ) { 148 | nV1 = inVertices[ j + 2 ].clone(); 149 | nV2 = clip( nV1, inVertices[ j ], plane ); 150 | nV3 = clip( nV1, inVertices[ j + 1 ], plane ); 151 | outVertices.push( nV1 ); 152 | outVertices.push( nV2 ); 153 | outVertices.push( nV3 ); 154 | } 155 | 156 | break; 157 | } 158 | case 3: { 159 | break; 160 | } 161 | } 162 | 163 | } 164 | 165 | return outVertices; 166 | 167 | } 168 | 169 | this.pushVertex = function( vertices, id, n ){ 170 | 171 | var v = mesh.geometry.vertices[ id ].clone(); 172 | v.applyMatrix4( mesh.matrix ); 173 | v.applyMatrix4( this.iCubeMatrix ); 174 | vertices.push( new THREE.DecalVertex( v, n.clone() ) ); 175 | 176 | } 177 | 178 | this.computeDecal = function() { 179 | 180 | var finalVertices = []; 181 | 182 | for( var i = 0; i < mesh.geometry.faces.length; i++ ) { 183 | 184 | var f = mesh.geometry.faces[ i ]; 185 | var n = ( f instanceof THREE.Face3 ) ? 3 : 4; 186 | var vertices = []; 187 | 188 | if( n === 3 ) { 189 | 190 | this.pushVertex( vertices, f[ this.faceIndices[ 0 ] ], f.vertexNormals[ 0 ] ); 191 | this.pushVertex( vertices, f[ this.faceIndices[ 1 ] ], f.vertexNormals[ 1 ] ); 192 | this.pushVertex( vertices, f[ this.faceIndices[ 2 ] ], f.vertexNormals[ 2 ] ); 193 | 194 | } else { 195 | 196 | this.pushVertex( vertices, f[ this.faceIndices[ 0 ] ], f.vertexNormals[ 0 ] ); 197 | this.pushVertex( vertices, f[ this.faceIndices[ 1 ] ], f.vertexNormals[ 1 ] ); 198 | this.pushVertex( vertices, f[ this.faceIndices[ 2 ] ], f.vertexNormals[ 2 ] ); 199 | 200 | this.pushVertex( vertices, f[ this.faceIndices[ 3 ] ], f.vertexNormals[ 3 ] ); 201 | this.pushVertex( vertices, f[ this.faceIndices[ 0 ] ], f.vertexNormals[ 0 ] ); 202 | this.pushVertex( vertices, f[ this.faceIndices[ 2 ] ], f.vertexNormals[ 2 ] ); 203 | 204 | } 205 | 206 | if( check.x ) { 207 | vertices = this.clipFace( vertices, new THREE.Vector3( 1, 0, 0 ) ); 208 | vertices = this.clipFace( vertices, new THREE.Vector3( -1, 0, 0 ) ); 209 | } 210 | if( check.y ) { 211 | vertices = this.clipFace( vertices, new THREE.Vector3( 0, 1, 0 ) ); 212 | vertices = this.clipFace( vertices, new THREE.Vector3( 0, -1, 0 ) ); 213 | } 214 | if( check.z ) { 215 | vertices = this.clipFace( vertices, new THREE.Vector3( 0, 0, 1 ) ); 216 | vertices = this.clipFace( vertices, new THREE.Vector3( 0, 0, -1 ) ); 217 | } 218 | 219 | for( var j = 0; j < vertices.length; j++ ) { 220 | 221 | var v = vertices[ j ]; 222 | 223 | this.uvs.push( new THREE.Vector2( 224 | .5 + ( v.vertex.x / dimensions.x ), 225 | .5 + ( v.vertex.y / dimensions.y ) 226 | ) ); 227 | 228 | vertices[ j ].vertex.applyMatrix4( this.cube.matrix ); 229 | 230 | } 231 | 232 | if( vertices.length === 0 ) continue; 233 | 234 | Array.prototype.push.apply(finalVertices, vertices); 235 | 236 | } 237 | 238 | for( var k = 0; k < finalVertices.length; k += 3 ) { 239 | 240 | this.vertices.push( 241 | finalVertices[ k ].vertex, 242 | finalVertices[ k + 1 ].vertex, 243 | finalVertices[ k + 2 ].vertex 244 | ); 245 | 246 | var f = new THREE.Face3( 247 | k, 248 | k + 1, 249 | k + 2 250 | ) 251 | f.vertexNormals.push( finalVertices[ k + 0 ].normal ); 252 | f.vertexNormals.push( finalVertices[ k + 1 ].normal ); 253 | f.vertexNormals.push( finalVertices[ k + 2 ].normal ); 254 | 255 | this.faces.push( f ); 256 | 257 | this.faceVertexUvs[ 0 ].push( [ 258 | this.uvs[ k ], 259 | this.uvs[ k + 1 ], 260 | this.uvs[ k + 2 ] 261 | ] ); 262 | 263 | } 264 | 265 | this.verticesNeedUpdate = true; 266 | this.elementsNeedUpdate = true; 267 | this.morphTargetsNeedUpdate = true; 268 | this.uvsNeedUpdate = true; 269 | this.normalsNeedUpdate = true; 270 | this.colorsNeedUpdate = true; 271 | this.tangentsNeedUpdate = true; 272 | this.computeFaceNormals(); 273 | 274 | } 275 | 276 | this.computeDecal(); 277 | 278 | } 279 | 280 | THREE.DecalGeometry.prototype = Object.create( THREE.Geometry.prototype ); 281 | --------------------------------------------------------------------------------