├── example ├── splat.png ├── uv_test.jpg ├── index.html ├── decals.js └── OrbitControls.js ├── README.md └── js └── Decal.js /example/splat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benpurdy/threejs-decals/HEAD/example/splat.png -------------------------------------------------------------------------------- /example/uv_test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benpurdy/threejs-decals/HEAD/example/uv_test.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | threejs-decals 2 | ============== 3 | 4 | Dyncamic decals for THREE.js. 5 | 6 | Based on the [technique described here](http://blog.wolfire.com/2009/06/how-to-project-decals/) 7 | 8 | Note, This implementation doesn't clip the edges of the polygons. 9 | 10 | Also, and more importantly, if a triangle in the target geometry is so large that none of the vertices fall within the projection volume, the triangle will not be included in the decal geometry. For example, if you try to project a tiny decal onto a the side of a giant cube, nothing will happen. 11 | 12 | See it in action: 13 | [http://www.youtube.com/watch?v=ckLghsutfmA](http://www.youtube.com/watch?v=ckLghsutfmA) 14 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /js/Decal.js: -------------------------------------------------------------------------------- 1 | THREE.DecalFactory = function( params ) 2 | { 3 | this._orthoMatrix = new THREE.Matrix4(); 4 | this._rotateMatrix = new THREE.Matrix4(); 5 | this._translateMatrix = new THREE.Matrix4(); 6 | 7 | this.decalMaterial = params.material; 8 | 9 | this.maxDecals = params.maxDecals ? params.maxDecals : 25; 10 | 11 | this.decals = []; 12 | 13 | this.decalsExpire = false; 14 | this.decalLifeSpan = params.lifeSpan ? params.lifeSpan : 30; 15 | this.decalFade = true; 16 | 17 | 18 | this.update = function() { 19 | 20 | if(!this.decalsExpire){ 21 | return; 22 | } 23 | 24 | var now = new Date().getTime(); 25 | 26 | for(var i = 0; i < this.decals.length; i++) { 27 | 28 | if(this.decals[i].fading){ 29 | var fadeAmount = 1 - ((now - this.decals[i].fadeStart) / 1000); 30 | this.decals[i].obj.material.opacity = fadeAmount; 31 | 32 | if(fadeAmount <= 0){ 33 | this.removeDecal(i); 34 | } 35 | }else if(now - this.decals[i].spawnTime > this.decalLifeSpan){ 36 | 37 | if(this.decalFade){ 38 | this.decals[i].fading = true; 39 | this.decals[i].fadeStart = now; 40 | } else { 41 | this.removeDecal(i); 42 | } 43 | } 44 | } 45 | }; 46 | 47 | this.projectOnMesh = function(mesh, origin, direction, angle, size) { 48 | 49 | this._orthoMatrix.makeOrthographic(-size.x/2, size.x/2, -size.y/2, size.y/2, size.z/2, -size.z/2); 50 | 51 | this._rotateMatrix.lookAt( direction.clone(), new THREE.Vector3(), new THREE.Vector3(0,1,0) ); 52 | this._orthoMatrix.multiply(this._rotateMatrix.getInverse(this._rotateMatrix)); 53 | 54 | this._rotateMatrix.makeRotationAxis( direction, angle ); 55 | this._orthoMatrix.multiply( this._rotateMatrix ); 56 | 57 | this._translateMatrix.makeTranslation(-origin.x, -origin.y, -origin.z); 58 | this._orthoMatrix.multiply(this._translateMatrix); 59 | 60 | 61 | var geometry = this.createGeometry(this._orthoMatrix, mesh); 62 | 63 | var decal = new THREE.Mesh(geometry, this.decalMaterial.clone()); 64 | 65 | this.decals.push({ 66 | obj: decal, 67 | fading: false, 68 | fadeStart: 0, 69 | spawnTime: new Date().getTime(), 70 | 71 | }); 72 | this.checkDecalCount(); 73 | 74 | mesh.add(decal); 75 | }; 76 | 77 | this.checkDecalCount = function() { 78 | if(this.decals.length > this.maxDecals){ 79 | this.removeDecal(0); 80 | } 81 | } 82 | 83 | this.removeDecal = function(index){ 84 | if((index >= 0) && (index < this.decals.length)){ 85 | this.decals[index].obj.parent.remove(this.decals[index].obj); 86 | this.decals.splice(index, 1); 87 | } 88 | } 89 | 90 | this.createGeometry = function(matrix, mesh) { 91 | 92 | var geom = mesh.geometry; 93 | 94 | var decalGeometry = new THREE.Geometry(); 95 | 96 | var projectorInverse = matrix.clone().getInverse(matrix); 97 | var meshInverse = mesh.matrixWorld.clone().getInverse(mesh.matrixWorld); 98 | var faces = []; 99 | 100 | for(var i = 0; i < geom.faces.length; i++){ 101 | 102 | var verts = [geom.faces[i].a, geom.faces[i].b, geom.faces[i].c]; 103 | 104 | var pts = []; 105 | var valid = false; 106 | 107 | for(var v = 0; v < 3; v++) { 108 | 109 | var vec = geom.vertices[verts[v]].clone(); 110 | 111 | vec.applyMatrix4(mesh.matrixWorld); 112 | vec.applyMatrix4(matrix); 113 | 114 | if((vec.z > 1) || (vec.z < -1) || (vec.x > 1) || (vec.x < -1) || (vec.y > 1) || (vec.y < -1)) { 115 | } else { 116 | valid = true; 117 | } 118 | 119 | pts.push(vec); 120 | } 121 | 122 | if(valid) { 123 | 124 | var uv = []; 125 | for(var n = 0; n < 3; n++){ 126 | uv.push(new THREE.Vector2( (pts[n].x + 1) / 2, (pts[n].y + 1) / 2)); 127 | 128 | pts[n].applyMatrix4(projectorInverse); 129 | pts[n].applyMatrix4(meshInverse); 130 | 131 | decalGeometry.vertices.push( pts[n] ); 132 | } 133 | 134 | // update UV's 135 | decalGeometry.faceVertexUvs[0].push(uv); 136 | 137 | var newFace = geom.faces[i].clone(); 138 | 139 | newFace.a = decalGeometry.vertices.length - 3; 140 | newFace.b = decalGeometry.vertices.length - 2; 141 | newFace.c = decalGeometry.vertices.length - 1; 142 | 143 | decalGeometry.faces.push(newFace); 144 | } 145 | 146 | } 147 | 148 | return decalGeometry; 149 | } 150 | } -------------------------------------------------------------------------------- /example/decals.js: -------------------------------------------------------------------------------- 1 | var renderer, camera; 2 | var scene, element; 3 | var ambient, point; 4 | var aspectRatio, windowHalf; 5 | var mouse, time; 6 | 7 | var controls; 8 | var clock; 9 | 10 | var ground, groundGeometry, groundMaterial; 11 | var sphere, torus; 12 | 13 | var decalTexture; 14 | var decalTargets = []; 15 | var decalFactory; 16 | 17 | function initScene() { 18 | clock = new THREE.Clock(); 19 | mouse = new THREE.Vector2(0, 0); 20 | 21 | windowHalf = new THREE.Vector2(window.innerWidth / 2, window.innerHeight / 2); 22 | aspectRatio = window.innerWidth / window.innerHeight; 23 | 24 | scene = new THREE.Scene(); 25 | 26 | camera = new THREE.PerspectiveCamera(45, aspectRatio, 1, 10000); 27 | camera.position.z = 512; 28 | camera.position.y = 250; 29 | camera.up 30 | camera.lookAt(scene.position); 31 | 32 | // Initialize the renderer 33 | renderer = new THREE.WebGLRenderer(); 34 | renderer.setSize(window.innerWidth, window.innerHeight); 35 | renderer.shadowMapEnabled = true; 36 | renderer.shadowMapType = THREE.PCFShadowMap; 37 | 38 | element = document.getElementById('viewport'); 39 | element.appendChild(renderer.domElement); 40 | 41 | controls = new THREE.OrbitControls(camera); 42 | 43 | decalTexture = THREE.ImageUtils.loadTexture("splat.png"); 44 | 45 | decalFactory = new THREE.DecalFactory( { 46 | material: new THREE.MeshPhongMaterial({ 47 | sides: THREE.DoubleSide, 48 | color: 0xffffff, 49 | map: decalTexture, 50 | polygonOffset: true, 51 | polygonOffsetFactor: -4, 52 | transparent: true, 53 | depthWrite: false, 54 | shininess: 150, 55 | specular: 0xffffff 56 | }), 57 | maxDecals:5 } 58 | ); 59 | 60 | time = Date.now(); 61 | } 62 | 63 | 64 | function initLights(){ 65 | 66 | var hemiLight = new THREE.HemisphereLight( 0x4020ff, 0x802020, 0.05); 67 | scene.add(hemiLight); 68 | 69 | var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.85 ); 70 | directionalLight.position.set( 0.7, 1, 0.4 ); 71 | scene.add( directionalLight ); 72 | 73 | ambient = new THREE.AmbientLight(0x001111); 74 | scene.add(ambient); 75 | 76 | point = new THREE.PointLight( 0xffffff, 1, 0, Math.PI, 1 ); 77 | point.position.set( 150, 150, -28 ); 78 | 79 | var point2 = new THREE.PointLight( 0x202080, 1, 0, Math.PI, 1 ); 80 | point2.position.set( -252, 150, -128 ); 81 | scene.add(point2); 82 | 83 | } 84 | 85 | 86 | function initGeometry(){ 87 | groundMaterial = new THREE.MeshBasicMaterial({ 88 | wireframe: true 89 | }); 90 | 91 | torus = new THREE.Mesh( 92 | new THREE.TorusGeometry(10, 5, 25, 20, Math.PI * 2), 93 | new THREE.MeshLambertMaterial({ 94 | sides: THREE.FrontSide, 95 | color: 0xffffff, 96 | }) 97 | ); 98 | 99 | torus.position.set(30, 15, 30); 100 | torus.name = "torus"; 101 | scene.add(torus); 102 | 103 | 104 | sphere = new THREE.Mesh( 105 | new THREE.SphereGeometry(15, 25, 20), 106 | new THREE.MeshLambertMaterial({ 107 | sides: THREE.FrontSide, 108 | color: 0xffffff 109 | }) 110 | ); 111 | 112 | sphere.position.set(-30, 40, -30); 113 | sphere.name = "sphere"; 114 | scene.add(sphere); 115 | 116 | 117 | var ground = new THREE.Mesh( 118 | new THREE.PlaneGeometry(200, 200, 64, 64), 119 | new THREE.MeshLambertMaterial({ 120 | map: THREE.ImageUtils.loadTexture("uv_test.jpg") 121 | }) 122 | ); 123 | 124 | ground.rotation.x = -Math.PI/2; 125 | scene.add(ground); 126 | 127 | 128 | decalTargets.push(ground); 129 | decalTargets.push(sphere); 130 | decalTargets.push(torus); 131 | } 132 | 133 | 134 | function init(){ 135 | document.addEventListener('mousedown', onMouseDown, false); 136 | document.addEventListener('mousemove', onMouseMove, false); 137 | 138 | window.addEventListener('resize', onResize, false); 139 | 140 | initScene(); 141 | initLights(); 142 | initGeometry(); 143 | } 144 | 145 | 146 | function onResize() { 147 | windowHalf = new THREE.Vector2(window.innerWidth / 2, window.innerHeight / 2); 148 | aspectRatio = window.innerWidth / window.innerHeight; 149 | camera.aspect = aspectRatio; 150 | camera.updateProjectionMatrix(); 151 | renderer.setSize(window.innerWidth, window.innerHeight); 152 | } 153 | 154 | 155 | function onMouseMove(event) { 156 | mouse.set( (event.clientX / window.innerWidth - 0.5) * 2, (event.clientY / window.innerHeight - 0.5) * 2); 157 | } 158 | 159 | 160 | function onMouseDown(event) { 161 | 162 | var mouse = new THREE.Vector3( 163 | event.clientX / (window.innerWidth * 0.5) - 1.0, 164 | -1.0 * (event.clientY / (window.innerHeight * 0.5) - 1.0), 0.0); 165 | 166 | var proj = new THREE.Projector(); 167 | var raycaster = proj.pickingRay(mouse, camera); 168 | 169 | var meshList = []; 170 | 171 | var intersects = raycaster.intersectObjects(decalTargets); 172 | 173 | if (intersects.length > 0) { 174 | var closest = 0; 175 | var closeDistance = 1000000000000000000; 176 | for(var i = 0; i < intersects.length; i++){ 177 | if(intersects[i].distance < closeDistance){ 178 | closest = i; 179 | closeDistance = intersects[i].distance; 180 | } 181 | } 182 | 183 | var size = Math.random() * 40 + 2; 184 | decalFactory.projectOnMesh( intersects[closest].object, intersects[closest].point, raycaster.ray.direction, Math.random() * Math.PI * 2, new THREE.Vector3(size, size, size+5) ); 185 | } else { 186 | console.log("No intersections."); 187 | } 188 | } 189 | 190 | 191 | function animate() { 192 | requestAnimationFrame(animate); 193 | render(); 194 | } 195 | 196 | 197 | function render() { 198 | var delta = clock.getDelta(); 199 | time += delta; 200 | 201 | torus.rotation.y += 1 * delta; 202 | 203 | sphere.rotation.x += 1 * delta; 204 | sphere.position.y = Math.sin(time) * 8 + 18; 205 | 206 | controls.update(); 207 | 208 | decalFactory.update(); 209 | renderer.render(scene, camera); 210 | } 211 | 212 | 213 | window.onload = function() { 214 | init(); 215 | animate(); 216 | } -------------------------------------------------------------------------------- /example/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 | */ 7 | 8 | THREE.OrbitControls = function ( object, domElement ) { 9 | 10 | this.object = object; 11 | this.domElement = ( domElement !== undefined ) ? domElement : document; 12 | 13 | // API 14 | 15 | this.enabled = true; 16 | 17 | this.center = new THREE.Vector3(); 18 | 19 | this.userZoom = true; 20 | this.userZoomSpeed = 1.0; 21 | 22 | this.userRotate = true; 23 | this.userRotateSpeed = 1.0; 24 | 25 | this.userPan = true; 26 | this.userPanSpeed = 2.0; 27 | 28 | this.autoRotate = false; 29 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 30 | 31 | this.minPolarAngle = 0; // radians 32 | this.maxPolarAngle = Math.PI; // radians 33 | 34 | this.minDistance = 0; 35 | this.maxDistance = Infinity; 36 | 37 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40, IN: 73, OUT: 79 }; 38 | this.previousKey = 0; 39 | this.navigationScale = this.userPanSpeed; 40 | this.navigationAcceleration = 1.03; 41 | 42 | // internals 43 | 44 | var scope = this; 45 | 46 | var EPS = 0.000001; 47 | var PIXELS_PER_ROUND = 1800; 48 | 49 | var rotateStart = new THREE.Vector2(); 50 | var rotateEnd = new THREE.Vector2(); 51 | var rotateDelta = new THREE.Vector2(); 52 | 53 | var zoomStart = new THREE.Vector2(); 54 | var zoomEnd = new THREE.Vector2(); 55 | var zoomDelta = new THREE.Vector2(); 56 | 57 | var phiDelta = 0; 58 | var thetaDelta = 0; 59 | var scale = 1; 60 | 61 | var lastPosition = new THREE.Vector3(); 62 | 63 | var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2 }; 64 | var state = STATE.NONE; 65 | 66 | // events 67 | 68 | var changeEvent = { type: 'change' }; 69 | 70 | 71 | this.rotateLeft = function ( angle ) { 72 | 73 | if ( angle === undefined ) { 74 | 75 | angle = getAutoRotationAngle(); 76 | 77 | } 78 | 79 | thetaDelta -= angle; 80 | 81 | }; 82 | 83 | this.rotateRight = function ( angle ) { 84 | 85 | if ( angle === undefined ) { 86 | 87 | angle = getAutoRotationAngle(); 88 | 89 | } 90 | 91 | thetaDelta += angle; 92 | 93 | }; 94 | 95 | this.rotateUp = function ( angle ) { 96 | 97 | if ( angle === undefined ) { 98 | 99 | angle = getAutoRotationAngle(); 100 | 101 | } 102 | 103 | phiDelta -= angle; 104 | 105 | }; 106 | 107 | this.rotateDown = function ( angle ) { 108 | 109 | if ( angle === undefined ) { 110 | 111 | angle = getAutoRotationAngle(); 112 | 113 | } 114 | 115 | phiDelta += angle; 116 | 117 | }; 118 | 119 | this.zoomIn = function ( zoomScale ) { 120 | 121 | if ( zoomScale === undefined ) { 122 | 123 | zoomScale = getZoomScale(); 124 | 125 | } 126 | 127 | scale /= zoomScale; 128 | 129 | }; 130 | 131 | this.zoomOut = function ( zoomScale ) { 132 | 133 | if ( zoomScale === undefined ) { 134 | 135 | zoomScale = getZoomScale(); 136 | 137 | } 138 | 139 | scale *= zoomScale; 140 | 141 | }; 142 | 143 | this.pan = function ( distance ) { 144 | 145 | distance.transformDirection( this.object.matrix ); 146 | distance.multiplyScalar( scope.navigationScale ); 147 | 148 | this.object.position.add( distance ); 149 | this.center.add( distance ); 150 | }; 151 | 152 | this.update = function () { 153 | 154 | var position = this.object.position; 155 | var offset = position.clone().sub( this.center ); 156 | 157 | // angle from z-axis around y-axis 158 | 159 | var theta = Math.atan2( offset.x, offset.z ); 160 | 161 | // angle from y-axis 162 | 163 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 164 | 165 | if ( this.autoRotate ) { 166 | 167 | this.rotateLeft( getAutoRotationAngle() ); 168 | 169 | } 170 | 171 | theta += thetaDelta; 172 | phi += phiDelta; 173 | 174 | // restrict phi to be between desired limits 175 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 176 | 177 | // restrict phi to be betwee EPS and PI-EPS 178 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 179 | 180 | var radius = offset.length() * scale; 181 | 182 | // restrict radius to be between desired limits 183 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 184 | 185 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 186 | offset.y = radius * Math.cos( phi ); 187 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 188 | 189 | position.copy( this.center ).add( offset ); 190 | 191 | this.object.lookAt( this.center ); 192 | 193 | thetaDelta = 0; 194 | phiDelta = 0; 195 | scale = 1; 196 | 197 | if ( lastPosition.distanceTo( this.object.position ) > 0 ) { 198 | 199 | this.dispatchEvent( changeEvent ); 200 | 201 | lastPosition.copy( this.object.position ); 202 | 203 | } 204 | 205 | }; 206 | 207 | 208 | function getAutoRotationAngle() { 209 | 210 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 211 | 212 | } 213 | 214 | function getZoomScale() { 215 | 216 | return Math.pow( 0.95, scope.userZoomSpeed ); 217 | 218 | } 219 | 220 | function onMouseDown( event ) { 221 | 222 | if ( scope.enabled === false ) return; 223 | if ( scope.userRotate === false ) return; 224 | 225 | event.preventDefault(); 226 | 227 | if ( event.button === 0 ) { 228 | 229 | state = STATE.ROTATE; 230 | 231 | rotateStart.set( event.clientX, event.clientY ); 232 | 233 | } else if ( event.button === 1 ) { 234 | 235 | state = STATE.ZOOM; 236 | 237 | zoomStart.set( event.clientX, event.clientY ); 238 | 239 | } else if ( event.button === 2 ) { 240 | 241 | state = STATE.PAN; 242 | 243 | } 244 | 245 | document.addEventListener( 'mousemove', onMouseMove, false ); 246 | document.addEventListener( 'mouseup', onMouseUp, false ); 247 | 248 | } 249 | 250 | function onMouseMove( event ) { 251 | 252 | if ( scope.enabled === false ) return; 253 | 254 | event.preventDefault(); 255 | 256 | if ( state === STATE.ROTATE ) { 257 | 258 | rotateEnd.set( event.clientX, event.clientY ); 259 | rotateDelta.subVectors( rotateEnd, rotateStart ); 260 | 261 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed ); 262 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed ); 263 | 264 | rotateStart.copy( rotateEnd ); 265 | 266 | } else if ( state === STATE.ZOOM ) { 267 | 268 | zoomEnd.set( event.clientX, event.clientY ); 269 | zoomDelta.subVectors( zoomEnd, zoomStart ); 270 | 271 | if ( zoomDelta.y > 0 ) { 272 | 273 | scope.zoomIn(); 274 | 275 | } else { 276 | 277 | scope.zoomOut(); 278 | 279 | } 280 | 281 | zoomStart.copy( zoomEnd ); 282 | 283 | } else if ( state === STATE.PAN ) { 284 | 285 | var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; 286 | var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; 287 | 288 | scope.pan( new THREE.Vector3( - movementX, movementY, 0 ) ); 289 | 290 | } 291 | 292 | } 293 | 294 | function onMouseUp( event ) { 295 | 296 | if ( scope.enabled === false ) return; 297 | if ( scope.userRotate === false ) return; 298 | 299 | document.removeEventListener( 'mousemove', onMouseMove, false ); 300 | document.removeEventListener( 'mouseup', onMouseUp, false ); 301 | 302 | state = STATE.NONE; 303 | 304 | } 305 | 306 | function onMouseWheel( event ) { 307 | 308 | if ( scope.enabled === false ) return; 309 | if ( scope.userZoom === false ) return; 310 | 311 | var delta = 0; 312 | 313 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 314 | 315 | delta = event.wheelDelta; 316 | 317 | } else if ( event.detail ) { // Firefox 318 | 319 | delta = - event.detail; 320 | 321 | } 322 | 323 | if ( delta > 0 ) { 324 | 325 | scope.zoomOut(); 326 | 327 | } else { 328 | 329 | scope.zoomIn(); 330 | 331 | } 332 | 333 | } 334 | 335 | function onKeyDown( event ) { 336 | 337 | if ( scope.enabled === false ) return; 338 | if ( scope.userPan === false ) return; 339 | 340 | if ( event.keyCode === scope.previousKey ) { 341 | scope.navigationScale *= scope.navigationAcceleration; 342 | } else { 343 | scope.navigationScale = scope.userPanSpeed; 344 | } 345 | scope.previousKey = event.keyCode; 346 | 347 | switch ( event.keyCode ) { 348 | 349 | case scope.keys.UP: 350 | scope.pan( new THREE.Vector3( 0, 1, 0 ) ); 351 | break; 352 | case scope.keys.BOTTOM: 353 | scope.pan( new THREE.Vector3( 0, -1, 0 ) ); 354 | break; 355 | case scope.keys.LEFT: 356 | scope.pan( new THREE.Vector3( -1, 0, 0 ) ); 357 | break; 358 | case scope.keys.RIGHT: 359 | scope.pan( new THREE.Vector3( 1, 0, 0 ) ); 360 | break; 361 | case scope.keys.IN: 362 | scope.pan( new THREE.Vector3( 0, 0, -1 ) ); 363 | break; 364 | case scope.keys.OUT: 365 | scope.pan( new THREE.Vector3( 0, 0, 1 ) ); 366 | break; 367 | } 368 | 369 | } 370 | 371 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 372 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 373 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 374 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 375 | this.domElement.addEventListener( 'keydown', onKeyDown, false ); 376 | 377 | }; 378 | 379 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); --------------------------------------------------------------------------------