├── LICENSE ├── README.md ├── css └── caps.css ├── index.html ├── js ├── caps.js ├── material.js ├── picking.js ├── planeGeometry.js ├── schedule.js ├── selection.js ├── selectionBoxFace.js ├── selectionBoxLine.js ├── shader.js ├── simulation.js ├── threeExtensions.js └── uniforms.js ├── lib ├── ColladaLoader.js ├── OrbitControls.js ├── three-license.txt └── three.js ├── models └── house.dae ├── screenshot_01.png └── screenshot_02.png /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jakob Mischek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | clipping-with-caps 2 | ================== 3 | 4 | ### Clipping a mesh and covering the openings with caps in WebGL ### 5 | 6 | [View the demo](http://daign.github.io/clipping-with-caps/) 7 | 8 | ![screenshot](./screenshot_01.png) 9 | 10 | Licensed under [MIT License](./LICENSE) 11 | 12 | Dependencies: 13 | * [three.js](https://github.com/mrdoob/three.js) 14 | 15 | --- 16 | 17 | Clipping meshes inside a shader program is fast and avoids triangulation problems that can arise 18 | when actually constructing a clipped mesh with constructive solid geometry. 19 | But since a mesh is just a hollow hull of triangles the clipped object will have an opening along 20 | the clipping edge. 21 | It is however possible to visually close this gaps without actually constructing the cap faces. 22 | 23 | When there is just one clipping plane on an otherwise closed mesh and backface rendering is enabled, 24 | then there are backsides visible through the whole opening. 25 | Rendered into a stencil this area can be used to define where to render the caps. 26 | First a scene showing only the backfaces is used to increment the stencil and then another scene 27 | showing the front faces decrements the stencil. 28 | The resulting stencil is applied to a scene rendering a plane at the location of the clipping plane. 29 | 30 | But this method fails when there is more than one clipping plane. 31 | Because clipping planes facing away from the camera result in hidden backfaces, these areas are 32 | missing in the stencil. 33 | The result would look like this: 34 | 35 | ![screenshot](./screenshot_02.png) 36 | 37 | The solution is to use a different shader for rendering the stencil areas, that only clipps at 38 | clipping planes facing the camera. 39 | Since the camera position is known it can be calculated in the shader whether a clipping plane is 40 | facing towards the camera or away from it. 41 | 42 | 43 | -------------------------------------------------------------------------------- /css/caps.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Monospace; 3 | background-color: #000000; 4 | margin: 0px; 5 | overflow: hidden; 6 | } 7 | #info { 8 | color: #000; 9 | position: absolute; 10 | top: 10px; 11 | left: 10px; 12 | z-index: 100; 13 | display: block; 14 | } 15 | #controls { 16 | color: #000; 17 | position: absolute; 18 | bottom: 10px; 19 | left: 10px; 20 | z-index: 100; 21 | display: block; 22 | } 23 | a { 24 | color: #000; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Clipping with caps 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | Clipping with caps.
Source code. 32 |
33 |
34 | 35 |
36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /js/caps.js: -------------------------------------------------------------------------------- 1 | var CAPS = {}; 2 | 3 | -------------------------------------------------------------------------------- /js/material.js: -------------------------------------------------------------------------------- 1 | CAPS.MATERIAL = { 2 | 3 | sheet: new THREE.ShaderMaterial( { 4 | uniforms: CAPS.UNIFORMS.clipping, 5 | vertexShader: CAPS.SHADER.vertexClipping, 6 | fragmentShader: CAPS.SHADER.fragmentClipping 7 | } ), 8 | 9 | cap: new THREE.ShaderMaterial( { 10 | uniforms: CAPS.UNIFORMS.caps, 11 | vertexShader: CAPS.SHADER.vertex, 12 | fragmentShader: CAPS.SHADER.fragment 13 | } ), 14 | 15 | backStencil: new THREE.ShaderMaterial( { 16 | uniforms: CAPS.UNIFORMS.clipping, 17 | vertexShader: CAPS.SHADER.vertexClipping, 18 | fragmentShader: CAPS.SHADER.fragmentClippingFront, 19 | colorWrite: false, 20 | depthWrite: false, 21 | side: THREE.BackSide 22 | } ), 23 | 24 | frontStencil: new THREE.ShaderMaterial( { 25 | uniforms: CAPS.UNIFORMS.clipping, 26 | vertexShader: CAPS.SHADER.vertexClipping, 27 | fragmentShader: CAPS.SHADER.fragmentClippingFront, 28 | colorWrite: false, 29 | depthWrite: false, 30 | } ), 31 | 32 | BoxBackFace: new THREE.MeshBasicMaterial( { color: 0xEEDDCC, transparent: true } ), 33 | BoxWireframe: new THREE.LineBasicMaterial( { color: 0x000000, linewidth: 2 } ), 34 | BoxWireActive: new THREE.LineBasicMaterial( { color: 0xf83610, linewidth: 4 } ), 35 | 36 | Invisible: new THREE.ShaderMaterial( { 37 | vertexShader: CAPS.SHADER.invisibleVertexShader, 38 | fragmentShader: CAPS.SHADER.invisibleFragmentShader 39 | } ) 40 | 41 | }; 42 | 43 | -------------------------------------------------------------------------------- /js/picking.js: -------------------------------------------------------------------------------- 1 | CAPS.picking = function ( simulation ) { 2 | 3 | var intersected = null; 4 | var mouse = new THREE.Vector2(); 5 | var ray = new THREE.Raycaster(); 6 | 7 | var normals = { 8 | x1: new THREE.Vector3( -1, 0, 0 ), 9 | x2: new THREE.Vector3( 1, 0, 0 ), 10 | y1: new THREE.Vector3( 0, -1, 0 ), 11 | y2: new THREE.Vector3( 0, 1, 0 ), 12 | z1: new THREE.Vector3( 0, 0, -1 ), 13 | z2: new THREE.Vector3( 0, 0, 1 ) 14 | }; 15 | 16 | var plane = new THREE.Mesh( new THREE.PlaneGeometry( 100, 100, 4, 4 ), CAPS.MATERIAL.Invisible ); 17 | simulation.scene.add( plane ); 18 | 19 | var targeting = function ( event ) { 20 | 21 | mouse.setToNormalizedDeviceCoordinates( event, window ); 22 | 23 | ray.setFromCamera( mouse, simulation.camera ); 24 | 25 | var intersects = ray.intersectObjects( simulation.selection.selectables ); 26 | 27 | if ( intersects.length > 0 ) { 28 | 29 | var candidate = intersects[ 0 ].object; 30 | 31 | if ( intersected !== candidate ) { 32 | 33 | if ( intersected !== null ) { 34 | intersected.guardian.rayOut(); 35 | } 36 | 37 | candidate.guardian.rayOver(); 38 | 39 | intersected = candidate; 40 | 41 | simulation.renderer.domElement.style.cursor = 'pointer'; 42 | simulation.throttledRender(); 43 | 44 | } 45 | 46 | } else if ( intersected !== null ) { 47 | 48 | intersected.guardian.rayOut(); 49 | intersected = null; 50 | 51 | simulation.renderer.domElement.style.cursor = 'auto'; 52 | simulation.throttledRender(); 53 | 54 | } 55 | 56 | }; 57 | 58 | var beginDrag = function ( event ) { 59 | 60 | mouse.setToNormalizedDeviceCoordinates( event, window ); 61 | 62 | ray.setFromCamera( mouse, simulation.camera ); 63 | 64 | var intersects = ray.intersectObjects( simulation.selection.selectables ); 65 | 66 | if ( intersects.length > 0 ) { 67 | 68 | event.preventDefault(); 69 | event.stopPropagation(); 70 | 71 | simulation.controls.enabled = false; 72 | 73 | var intersectionPoint = intersects[ 0 ].point; 74 | 75 | var axis = intersects[ 0 ].object.axis; 76 | 77 | if ( axis === 'x1' || axis === 'x2' ) { 78 | intersectionPoint.setX( 0 ); 79 | } else if ( axis === 'y1' || axis === 'y2' ) { 80 | intersectionPoint.setY( 0 ); 81 | } else if ( axis === 'z1' || axis === 'z2' ) { 82 | intersectionPoint.setZ( 0 ); 83 | } 84 | plane.position.copy( intersectionPoint ); 85 | 86 | var newNormal = simulation.camera.position.clone().sub( 87 | simulation.camera.position.clone().projectOnVector( normals[ axis ] ) 88 | ); 89 | plane.lookAt( newNormal.add( intersectionPoint ) ); 90 | 91 | simulation.renderer.domElement.style.cursor = 'move'; 92 | simulation.throttledRender(); 93 | 94 | var continueDrag = function ( event ) { 95 | 96 | event.preventDefault(); 97 | event.stopPropagation(); 98 | 99 | mouse.setToNormalizedDeviceCoordinates( event, window ); 100 | 101 | ray.setFromCamera( mouse, simulation.camera ); 102 | 103 | var intersects = ray.intersectObject( plane ); 104 | 105 | if ( intersects.length > 0 ) { 106 | 107 | if ( axis === 'x1' || axis === 'x2' ) { 108 | value = intersects[ 0 ].point.x; 109 | } else if ( axis === 'y1' || axis === 'y2' ) { 110 | value = intersects[ 0 ].point.y; 111 | } else if ( axis === 'z1' || axis === 'z2' ) { 112 | value = intersects[ 0 ].point.z; 113 | } 114 | 115 | simulation.selection.setValue( axis, value ); 116 | simulation.throttledRender(); 117 | 118 | } 119 | 120 | }; 121 | 122 | var endDrag = function ( event ) { 123 | 124 | simulation.controls.enabled = true; 125 | 126 | simulation.renderer.domElement.style.cursor = 'pointer'; 127 | 128 | document.removeEventListener( 'mousemove', continueDrag, true ); 129 | document.removeEventListener( 'touchmove', continueDrag, true ); 130 | 131 | document.removeEventListener( 'mouseup', endDrag, false ); 132 | document.removeEventListener( 'touchend', endDrag, false ); 133 | document.removeEventListener( 'touchcancel', endDrag, false ); 134 | document.removeEventListener( 'touchleave', endDrag, false ); 135 | 136 | }; 137 | 138 | document.addEventListener( 'mousemove', continueDrag, true ); 139 | document.addEventListener( 'touchmove', continueDrag, true ); 140 | 141 | document.addEventListener( 'mouseup', endDrag, false ); 142 | document.addEventListener( 'touchend', endDrag, false ); 143 | document.addEventListener( 'touchcancel', endDrag, false ); 144 | document.addEventListener( 'touchleave', endDrag, false ); 145 | 146 | } 147 | 148 | }; 149 | 150 | simulation.renderer.domElement.addEventListener( 'mousemove', targeting, true ); 151 | simulation.renderer.domElement.addEventListener( 'mousedown', beginDrag, false ); 152 | simulation.renderer.domElement.addEventListener( 'touchstart', beginDrag, false ); 153 | 154 | }; 155 | 156 | -------------------------------------------------------------------------------- /js/planeGeometry.js: -------------------------------------------------------------------------------- 1 | CAPS.PlaneGeometry = function ( v0, v1, v2, v3 ) { 2 | 3 | THREE.Geometry.call( this ); 4 | 5 | this.vertices.push( v0, v1, v2, v3 ); 6 | this.faces.push( new THREE.Face3( 0, 1, 2 ) ); 7 | this.faces.push( new THREE.Face3( 0, 2, 3 ) ); 8 | 9 | this.computeFaceNormals(); 10 | this.computeVertexNormals(); 11 | 12 | }; 13 | 14 | CAPS.PlaneGeometry.prototype = new THREE.Geometry(); 15 | CAPS.PlaneGeometry.prototype.constructor = CAPS.PlaneGeometry; 16 | 17 | -------------------------------------------------------------------------------- /js/schedule.js: -------------------------------------------------------------------------------- 1 | CAPS.SCHEDULE = { 2 | 3 | postpone: function ( callback, context, wait ) { 4 | 5 | return function () { 6 | setTimeout( function () { 7 | callback.apply( context, arguments ); 8 | }, wait ); 9 | }; 10 | 11 | }, 12 | 13 | deferringThrottle: function ( callback, context, wait ) { // wait 60 = 16fps // wait 40 = 25fps // wait 20 = 50fps 14 | 15 | var execute = function ( arguments ) { 16 | callback.apply( context, arguments ); 17 | setTimeout( function () { 18 | if ( deferredCalls ) { 19 | deferredCalls = false; 20 | execute( args ); 21 | } else { 22 | blocked = false; 23 | } 24 | }, wait ); 25 | }; 26 | 27 | var blocked = false; 28 | var deferredCalls = false; 29 | var args = undefined; 30 | 31 | return function () { 32 | if ( blocked ) { 33 | args = arguments; 34 | deferredCalls = true; 35 | return; 36 | } else { 37 | blocked = true; 38 | deferredCalls = false; 39 | execute( arguments ); 40 | } 41 | }; 42 | 43 | } 44 | 45 | }; 46 | 47 | -------------------------------------------------------------------------------- /js/selection.js: -------------------------------------------------------------------------------- 1 | CAPS.Selection = function ( low, high ) { 2 | 3 | this.limitLow = low; 4 | this.limitHigh = high; 5 | 6 | this.box = new THREE.BoxGeometry( 1, 1, 1 ); 7 | this.boxMesh = new THREE.Mesh( this.box, CAPS.MATERIAL.cap ); 8 | 9 | this.vertices = [ 10 | new THREE.Vector3(), new THREE.Vector3(), 11 | new THREE.Vector3(), new THREE.Vector3(), 12 | new THREE.Vector3(), new THREE.Vector3(), 13 | new THREE.Vector3(), new THREE.Vector3() 14 | ]; 15 | this.updateVertices(); 16 | 17 | var v = this.vertices; 18 | 19 | this.touchMeshes = new THREE.Object3D(); 20 | this.displayMeshes = new THREE.Object3D(); 21 | this.meshGeometries = []; 22 | this.lineGeometries = []; 23 | this.selectables = []; 24 | 25 | this.faces = []; 26 | var f = this.faces; 27 | this.faces.push( new CAPS.SelectionBoxFace( 'y1', v[ 0 ], v[ 1 ], v[ 5 ], v[ 4 ], this ) ); 28 | this.faces.push( new CAPS.SelectionBoxFace( 'z1', v[ 0 ], v[ 2 ], v[ 3 ], v[ 1 ], this ) ); 29 | this.faces.push( new CAPS.SelectionBoxFace( 'x1', v[ 0 ], v[ 4 ], v[ 6 ], v[ 2 ], this ) ); 30 | this.faces.push( new CAPS.SelectionBoxFace( 'x2', v[ 7 ], v[ 5 ], v[ 1 ], v[ 3 ], this ) ); 31 | this.faces.push( new CAPS.SelectionBoxFace( 'y2', v[ 7 ], v[ 3 ], v[ 2 ], v[ 6 ], this ) ); 32 | this.faces.push( new CAPS.SelectionBoxFace( 'z2', v[ 7 ], v[ 6 ], v[ 4 ], v[ 5 ], this ) ); 33 | 34 | var l0 = new CAPS.SelectionBoxLine( v[ 0 ], v[ 1 ], f[ 0 ], f[ 1 ], this ); 35 | var l1 = new CAPS.SelectionBoxLine( v[ 0 ], v[ 2 ], f[ 1 ], f[ 2 ], this ); 36 | var l2 = new CAPS.SelectionBoxLine( v[ 0 ], v[ 4 ], f[ 0 ], f[ 2 ], this ); 37 | var l3 = new CAPS.SelectionBoxLine( v[ 1 ], v[ 3 ], f[ 1 ], f[ 3 ], this ); 38 | var l4 = new CAPS.SelectionBoxLine( v[ 1 ], v[ 5 ], f[ 0 ], f[ 3 ], this ); 39 | var l5 = new CAPS.SelectionBoxLine( v[ 2 ], v[ 3 ], f[ 1 ], f[ 4 ], this ); 40 | var l6 = new CAPS.SelectionBoxLine( v[ 2 ], v[ 6 ], f[ 2 ], f[ 4 ], this ); 41 | var l7 = new CAPS.SelectionBoxLine( v[ 3 ], v[ 7 ], f[ 3 ], f[ 4 ], this ); 42 | var l8 = new CAPS.SelectionBoxLine( v[ 4 ], v[ 5 ], f[ 0 ], f[ 5 ], this ); 43 | var l9 = new CAPS.SelectionBoxLine( v[ 4 ], v[ 6 ], f[ 2 ], f[ 5 ], this ); 44 | var l10 = new CAPS.SelectionBoxLine( v[ 5 ], v[ 7 ], f[ 3 ], f[ 5 ], this ); 45 | var l11 = new CAPS.SelectionBoxLine( v[ 6 ], v[ 7 ], f[ 4 ], f[ 5 ], this ); 46 | 47 | this.setBox(); 48 | this.setUniforms(); 49 | 50 | }; 51 | 52 | CAPS.Selection.prototype = { 53 | 54 | constructor: CAPS.Selection, 55 | 56 | updateVertices: function () { 57 | 58 | this.vertices[ 0 ].set( this.limitLow.x, this.limitLow.y, this.limitLow.z ); 59 | this.vertices[ 1 ].set( this.limitHigh.x, this.limitLow.y, this.limitLow.z ); 60 | this.vertices[ 2 ].set( this.limitLow.x, this.limitHigh.y, this.limitLow.z ); 61 | this.vertices[ 3 ].set( this.limitHigh.x, this.limitHigh.y, this.limitLow.z ); 62 | this.vertices[ 4 ].set( this.limitLow.x, this.limitLow.y, this.limitHigh.z ); 63 | this.vertices[ 5 ].set( this.limitHigh.x, this.limitLow.y, this.limitHigh.z ); 64 | this.vertices[ 6 ].set( this.limitLow.x, this.limitHigh.y, this.limitHigh.z ); 65 | this.vertices[ 7 ].set( this.limitHigh.x, this.limitHigh.y, this.limitHigh.z ); 66 | 67 | }, 68 | 69 | updateGeometries: function () { 70 | 71 | for ( var i = 0; i < this.meshGeometries.length; i++ ) { 72 | this.meshGeometries[ i ].verticesNeedUpdate = true; 73 | this.meshGeometries[ i ].computeBoundingSphere(); 74 | this.meshGeometries[ i ].computeBoundingBox(); 75 | } 76 | for ( var i = 0; i < this.lineGeometries.length; i++ ) { 77 | this.lineGeometries[ i ].verticesNeedUpdate = true; 78 | } 79 | 80 | }, 81 | 82 | setBox: function () { 83 | 84 | var width = new THREE.Vector3(); 85 | width.subVectors( this.limitHigh, this.limitLow ); 86 | 87 | this.boxMesh.scale.copy( width ); 88 | width.multiplyScalar( 0.5 ).add( this.limitLow ); 89 | this.boxMesh.position.copy( width ); 90 | 91 | }, 92 | 93 | setUniforms: function () { 94 | 95 | var uniforms = CAPS.UNIFORMS.clipping; 96 | uniforms.clippingLow.value.copy( this.limitLow ); 97 | uniforms.clippingHigh.value.copy( this.limitHigh ); 98 | 99 | }, 100 | 101 | setValue: function ( axis, value ) { 102 | 103 | var buffer = 0.4; 104 | var limit = 14; 105 | 106 | if ( axis === 'x1' ) { 107 | this.limitLow.x = Math.max( -limit, Math.min( this.limitHigh.x-buffer, value ) ); 108 | } else if ( axis === 'x2' ) { 109 | this.limitHigh.x = Math.max( this.limitLow.x+buffer, Math.min( limit, value ) ); 110 | } else if ( axis === 'y1' ) { 111 | this.limitLow.y = Math.max( -limit, Math.min( this.limitHigh.y-buffer, value ) ); 112 | } else if ( axis === 'y2' ) { 113 | this.limitHigh.y = Math.max( this.limitLow.y+buffer, Math.min( limit, value ) ); 114 | } else if ( axis === 'z1' ) { 115 | this.limitLow.z = Math.max( -limit, Math.min( this.limitHigh.z-buffer, value ) ); 116 | } else if ( axis === 'z2' ) { 117 | this.limitHigh.z = Math.max( this.limitLow.z+buffer, Math.min( limit, value ) ); 118 | } 119 | 120 | this.setBox(); 121 | this.setUniforms(); 122 | 123 | this.updateVertices(); 124 | this.updateGeometries(); 125 | 126 | } 127 | 128 | }; 129 | 130 | -------------------------------------------------------------------------------- /js/selectionBoxFace.js: -------------------------------------------------------------------------------- 1 | CAPS.SelectionBoxFace = function ( axis, v0, v1, v2, v3, selection ) { 2 | 3 | var frontFaceGeometry = new CAPS.PlaneGeometry( v0, v1, v2, v3 ); 4 | frontFaceGeometry.dynamic = true; 5 | selection.meshGeometries.push( frontFaceGeometry ); 6 | 7 | var frontFaceMesh = new THREE.Mesh( frontFaceGeometry, CAPS.MATERIAL.Invisible ); 8 | frontFaceMesh.axis = axis; 9 | frontFaceMesh.guardian = this; 10 | selection.touchMeshes.add( frontFaceMesh ); 11 | selection.selectables.push( frontFaceMesh ); 12 | 13 | var backFaceGeometry = new CAPS.PlaneGeometry( v3, v2, v1, v0 ); 14 | backFaceGeometry.dynamic = true; 15 | selection.meshGeometries.push( backFaceGeometry ); 16 | 17 | var backFaceMesh = new THREE.Mesh( backFaceGeometry, CAPS.MATERIAL.BoxBackFace ); 18 | selection.displayMeshes.add( backFaceMesh ); 19 | 20 | this.lines = new Array(); 21 | 22 | }; 23 | 24 | CAPS.SelectionBoxFace.prototype = { 25 | 26 | constructor: CAPS.SelectionBoxFace, 27 | 28 | rayOver: function () { 29 | this.highlightLines( true ); 30 | }, 31 | 32 | rayOut: function () { 33 | this.highlightLines( false ); 34 | }, 35 | 36 | highlightLines: function ( b ) { 37 | for ( var i = 0; i < this.lines.length; i++ ) { 38 | this.lines[ i ].setHighlight( b ); 39 | } 40 | } 41 | 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /js/selectionBoxLine.js: -------------------------------------------------------------------------------- 1 | CAPS.SelectionBoxLine = function ( v0, v1, f0, f1, selection ) { 2 | 3 | var lineGeometry = new THREE.Geometry(); 4 | lineGeometry.vertices.push( v0, v1 ); 5 | lineGeometry.computeLineDistances(); 6 | lineGeometry.dynamic = true; 7 | selection.lineGeometries.push( lineGeometry ); 8 | 9 | this.line = new THREE.LineSegments( lineGeometry, CAPS.MATERIAL.BoxWireframe ); 10 | selection.displayMeshes.add( this.line ); 11 | 12 | f0.lines.push( this ); 13 | f1.lines.push( this ); 14 | 15 | }; 16 | 17 | CAPS.SelectionBoxLine.prototype = { 18 | 19 | constructor: CAPS.SelectionBoxLine, 20 | 21 | setHighlight: function ( b ) { 22 | this.line.material = b ? CAPS.MATERIAL.BoxWireActive : CAPS.MATERIAL.BoxWireframe; 23 | } 24 | 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /js/shader.js: -------------------------------------------------------------------------------- 1 | CAPS.SHADER = { 2 | 3 | vertex: '\ 4 | uniform vec3 color;\ 5 | varying vec3 pixelNormal;\ 6 | \ 7 | void main() {\ 8 | \ 9 | pixelNormal = normal;\ 10 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\ 11 | \ 12 | }', 13 | 14 | vertexClipping: '\ 15 | uniform vec3 color;\ 16 | uniform vec3 clippingLow;\ 17 | uniform vec3 clippingHigh;\ 18 | \ 19 | varying vec3 pixelNormal;\ 20 | varying vec4 worldPosition;\ 21 | varying vec3 camPosition;\ 22 | \ 23 | void main() {\ 24 | \ 25 | pixelNormal = normal;\ 26 | worldPosition = modelMatrix * vec4( position, 1.0 );\ 27 | camPosition = cameraPosition;\ 28 | \ 29 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\ 30 | \ 31 | }', 32 | 33 | fragment: '\ 34 | uniform vec3 color;\ 35 | varying vec3 pixelNormal;\ 36 | \ 37 | void main( void ) {\ 38 | \ 39 | float shade = (\ 40 | 3.0 * pow ( abs ( pixelNormal.y ), 2.0 )\ 41 | + 2.0 * pow ( abs ( pixelNormal.z ), 2.0 )\ 42 | + 1.0 * pow ( abs ( pixelNormal.x ), 2.0 )\ 43 | ) / 3.0;\ 44 | \ 45 | gl_FragColor = vec4( color * shade, 1.0 );\ 46 | \ 47 | }', 48 | 49 | fragmentClipping: '\ 50 | uniform vec3 color;\ 51 | uniform vec3 clippingLow;\ 52 | uniform vec3 clippingHigh;\ 53 | \ 54 | varying vec3 pixelNormal;\ 55 | varying vec4 worldPosition;\ 56 | \ 57 | void main( void ) {\ 58 | \ 59 | float shade = (\ 60 | 3.0 * pow ( abs ( pixelNormal.y ), 2.0 )\ 61 | + 2.0 * pow ( abs ( pixelNormal.z ), 2.0 )\ 62 | + 1.0 * pow ( abs ( pixelNormal.x ), 2.0 )\ 63 | ) / 3.0;\ 64 | \ 65 | if (\ 66 | worldPosition.x < clippingLow.x\ 67 | || worldPosition.x > clippingHigh.x\ 68 | || worldPosition.y < clippingLow.y\ 69 | || worldPosition.y > clippingHigh.y\ 70 | || worldPosition.z < clippingLow.z\ 71 | || worldPosition.z > clippingHigh.z\ 72 | ) {\ 73 | \ 74 | discard;\ 75 | \ 76 | } else {\ 77 | \ 78 | gl_FragColor = vec4( color * shade, 1.0 );\ 79 | \ 80 | }\ 81 | \ 82 | }', 83 | 84 | fragmentClippingFront: '\ 85 | uniform vec3 color;\ 86 | uniform vec3 clippingLow;\ 87 | uniform vec3 clippingHigh;\ 88 | \ 89 | varying vec3 pixelNormal;\ 90 | varying vec4 worldPosition;\ 91 | varying vec3 camPosition;\ 92 | \ 93 | void main( void ) {\ 94 | \ 95 | float shade = (\ 96 | 3.0 * pow ( abs ( pixelNormal.y ), 2.0 )\ 97 | + 2.0 * pow ( abs ( pixelNormal.z ), 2.0 )\ 98 | + 1.0 * pow ( abs ( pixelNormal.x ), 2.0 )\ 99 | ) / 3.0;\ 100 | \ 101 | if (\ 102 | worldPosition.x < clippingLow.x && camPosition.x < clippingLow.x\ 103 | || worldPosition.x > clippingHigh.x && camPosition.x > clippingHigh.x\ 104 | || worldPosition.y < clippingLow.y && camPosition.y < clippingLow.y\ 105 | || worldPosition.y > clippingHigh.y && camPosition.y > clippingHigh.y\ 106 | || worldPosition.z < clippingLow.z && camPosition.z < clippingLow.z\ 107 | || worldPosition.z > clippingHigh.z && camPosition.z > clippingHigh.z\ 108 | ) {\ 109 | \ 110 | discard;\ 111 | \ 112 | } else {\ 113 | \ 114 | gl_FragColor = vec4( color * shade, 1.0 );\ 115 | \ 116 | }\ 117 | \ 118 | }', 119 | 120 | invisibleVertexShader: '\ 121 | void main() {\ 122 | vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\ 123 | gl_Position = projectionMatrix * mvPosition;\ 124 | }', 125 | 126 | invisibleFragmentShader: '\ 127 | void main( void ) {\ 128 | gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\ 129 | discard;\ 130 | }' 131 | 132 | }; 133 | 134 | -------------------------------------------------------------------------------- /js/simulation.js: -------------------------------------------------------------------------------- 1 | CAPS.Simulation = function () { 2 | 3 | this.scene = undefined; 4 | this.capsScene = undefined; 5 | this.backStencil = undefined; 6 | this.frontStencil = undefined; 7 | 8 | this.camera = undefined; 9 | this.renderer = undefined; 10 | this.controls = undefined; 11 | 12 | this.showCaps = true; 13 | 14 | this.init(); 15 | 16 | }; 17 | 18 | CAPS.Simulation.prototype = { 19 | 20 | constructor: CAPS.Simulation, 21 | 22 | init: function () { 23 | 24 | var self = this; 25 | 26 | var loader = new THREE.ColladaLoader(); 27 | loader.options.convertUpAxis = true; 28 | loader.load( './models/house.dae', function ( collada ) { 29 | self.initScene( collada.scene ); 30 | } ); 31 | 32 | var container = document.createElement( 'div' ); 33 | document.body.appendChild( container ); 34 | 35 | this.camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 ); 36 | this.camera.position.set( 20, 20, 30 ); 37 | this.camera.lookAt( new THREE.Vector3( 0, 0, 0 ) ); 38 | 39 | this.scene = new THREE.Scene(); 40 | this.capsScene = new THREE.Scene(); 41 | this.backStencil = new THREE.Scene(); 42 | this.frontStencil = new THREE.Scene(); 43 | 44 | this.selection = new CAPS.Selection( 45 | new THREE.Vector3( -7, -14, -14 ), 46 | new THREE.Vector3( 14, 9, 3 ) 47 | ); 48 | this.capsScene.add( this.selection.boxMesh ); 49 | this.scene.add( this.selection.touchMeshes ); 50 | this.scene.add( this.selection.displayMeshes ); 51 | 52 | this.renderer = new THREE.WebGLRenderer( { antialias: true } ); 53 | this.renderer.setPixelRatio( window.devicePixelRatio ); 54 | this.renderer.setSize( window.innerWidth, window.innerHeight ); 55 | this.renderer.setClearColor( 0xffffff ); 56 | this.renderer.autoClear = false; 57 | container.appendChild( this.renderer.domElement ); 58 | 59 | var throttledRender = CAPS.SCHEDULE.deferringThrottle( this._render, this, 40 ); 60 | this.throttledRender = throttledRender; 61 | 62 | CAPS.picking( this ); // must come before OrbitControls, so it can cancel them 63 | 64 | this.controls = new THREE.OrbitControls( this.camera, this.renderer.domElement ); 65 | this.controls.addEventListener( 'change', throttledRender ); 66 | 67 | var onWindowResize = function () { 68 | self.camera.aspect = window.innerWidth / window.innerHeight; 69 | self.camera.updateProjectionMatrix(); 70 | self.renderer.setSize( window.innerWidth, window.innerHeight ); 71 | throttledRender(); 72 | }; 73 | window.addEventListener( 'resize', onWindowResize, false ); 74 | 75 | var showCapsInput = document.getElementById( 'showCaps' ); 76 | this.showCaps = showCapsInput.checked; 77 | var onShowCaps = function () { 78 | self.showCaps = showCapsInput.checked; 79 | throttledRender(); 80 | }; 81 | showCapsInput.addEventListener( 'change', onShowCaps, false ); 82 | 83 | throttledRender(); 84 | 85 | }, 86 | 87 | initScene: function ( collada ) { 88 | 89 | var setMaterial = function ( node, material ) { 90 | node.material = material; 91 | if ( node.children ) { 92 | for ( var i = 0; i < node.children.length; i++ ) { 93 | setMaterial( node.children[i], material ); 94 | } 95 | } 96 | }; 97 | 98 | var back = collada.clone(); 99 | setMaterial( back, CAPS.MATERIAL.backStencil ); 100 | back.scale.set( 0.03, 0.03, 0.03 ); 101 | back.updateMatrix(); 102 | this.backStencil.add( back ); 103 | 104 | var front = collada.clone(); 105 | setMaterial( front, CAPS.MATERIAL.frontStencil ); 106 | front.scale.set( 0.03, 0.03, 0.03 ); 107 | front.updateMatrix(); 108 | this.frontStencil.add( front ); 109 | 110 | setMaterial( collada, CAPS.MATERIAL.sheet ); 111 | collada.scale.set( 0.03, 0.03, 0.03 ); 112 | collada.updateMatrix(); 113 | this.scene.add( collada ); 114 | 115 | this.throttledRender(); 116 | 117 | }, 118 | 119 | _render: function () { 120 | 121 | this.renderer.clear(); 122 | 123 | var gl = this.renderer.context; 124 | 125 | if ( this.showCaps ) { 126 | 127 | this.renderer.state.setStencilTest( true ); 128 | 129 | this.renderer.state.setStencilFunc( gl.ALWAYS, 1, 0xff ); 130 | this.renderer.state.setStencilOp( gl.KEEP, gl.KEEP, gl.INCR ); 131 | this.renderer.render( this.backStencil, this.camera ); 132 | 133 | this.renderer.state.setStencilFunc( gl.ALWAYS, 1, 0xff ); 134 | this.renderer.state.setStencilOp( gl.KEEP, gl.KEEP, gl.DECR ); 135 | this.renderer.render( this.frontStencil, this.camera ); 136 | 137 | this.renderer.state.setStencilFunc( gl.EQUAL, 1, 0xff ); 138 | this.renderer.state.setStencilOp( gl.KEEP, gl.KEEP, gl.KEEP ); 139 | this.renderer.render( this.capsScene, this.camera ); 140 | 141 | this.renderer.state.setStencilTest( false ); 142 | 143 | } 144 | 145 | this.renderer.render( this.scene, this.camera ); 146 | 147 | } 148 | 149 | }; 150 | 151 | -------------------------------------------------------------------------------- /js/threeExtensions.js: -------------------------------------------------------------------------------- 1 | // sets this vector to the coordinates of a mouse event, uses touch event if applicable 2 | THREE.Vector2.prototype.setFromEvent = function ( event ) { 3 | 4 | this.x = ( event.clientX !== undefined ) ? event.clientX : ( event.touches && event.touches[ 0 ].clientX ); 5 | this.y = ( event.clientY !== undefined ) ? event.clientY : ( event.touches && event.touches[ 0 ].clientY ); 6 | return this; 7 | 8 | }; 9 | 10 | // calculate mouse position in normalized device coordinates 11 | THREE.Vector2.prototype.setToNormalizedDeviceCoordinates = function ( event, window ) { 12 | 13 | this.setFromEvent( event ); 14 | this.x = ( this.x / window.innerWidth ) * 2 - 1; 15 | this.y = - ( this.y / window.innerHeight ) * 2 + 1; 16 | return this; 17 | 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /js/uniforms.js: -------------------------------------------------------------------------------- 1 | CAPS.UNIFORMS = { 2 | 3 | clipping: { 4 | color: { type: "c", value: new THREE.Color( 0x3d9ecb ) }, 5 | clippingLow: { type: "v3", value: new THREE.Vector3( 0, 0, 0 ) }, 6 | clippingHigh: { type: "v3", value: new THREE.Vector3( 0, 0, 0 ) } 7 | }, 8 | 9 | caps: { 10 | color: { type: "c", value: new THREE.Color( 0xf83610 ) } 11 | } 12 | 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /lib/ColladaLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tim Knip / http://www.floorplanner.com/ / tim at floorplanner.com 3 | * @author Tony Parisi / http://www.tonyparisi.com/ 4 | */ 5 | 6 | THREE.ColladaLoader = function () { 7 | 8 | var COLLADA = null; 9 | var scene = null; 10 | var visualScene; 11 | var kinematicsModel; 12 | 13 | var readyCallbackFunc = null; 14 | 15 | var sources = {}; 16 | var images = {}; 17 | var animations = {}; 18 | var controllers = {}; 19 | var geometries = {}; 20 | var materials = {}; 21 | var effects = {}; 22 | var cameras = {}; 23 | var lights = {}; 24 | 25 | var animData; 26 | var kinematics; 27 | var visualScenes; 28 | var kinematicsModels; 29 | var baseUrl; 30 | var morphs; 31 | var skins; 32 | 33 | var flip_uv = true; 34 | var preferredShading = THREE.SmoothShading; 35 | 36 | var options = { 37 | // Force Geometry to always be centered at the local origin of the 38 | // containing Mesh. 39 | centerGeometry: false, 40 | 41 | // Axis conversion is done for geometries, animations, and controllers. 42 | // If we ever pull cameras or lights out of the COLLADA file, they'll 43 | // need extra work. 44 | convertUpAxis: false, 45 | 46 | subdivideFaces: true, 47 | 48 | upAxis: 'Y', 49 | 50 | // For reflective or refractive materials we'll use this cubemap 51 | defaultEnvMap: null 52 | 53 | }; 54 | 55 | var colladaUnit = 1.0; 56 | var colladaUp = 'Y'; 57 | var upConversion = null; 58 | 59 | function load ( url, readyCallback, progressCallback, failCallback ) { 60 | 61 | var length = 0; 62 | 63 | if ( document.implementation && document.implementation.createDocument ) { 64 | 65 | var request = new XMLHttpRequest(); 66 | 67 | request.onreadystatechange = function() { 68 | 69 | if ( request.readyState === 4 ) { 70 | 71 | if ( request.status === 0 || request.status === 200 ) { 72 | 73 | if ( request.response ) { 74 | 75 | readyCallbackFunc = readyCallback; 76 | parse( request.response, undefined, url ); 77 | 78 | } else { 79 | 80 | if ( failCallback ) { 81 | 82 | failCallback( { type: 'error', url: url } ); 83 | 84 | } else { 85 | 86 | console.error( "ColladaLoader: Empty or non-existing file (" + url + ")" ); 87 | 88 | } 89 | 90 | } 91 | 92 | }else{ 93 | 94 | if( failCallback ){ 95 | 96 | failCallback( { type: 'error', url: url } ); 97 | 98 | }else{ 99 | 100 | console.error( 'ColladaLoader: Couldn\'t load "' + url + '" (' + request.status + ')' ); 101 | 102 | } 103 | 104 | } 105 | 106 | } else if ( request.readyState === 3 ) { 107 | 108 | if ( progressCallback ) { 109 | 110 | if ( length === 0 ) { 111 | 112 | length = request.getResponseHeader( "Content-Length" ); 113 | 114 | } 115 | 116 | progressCallback( { total: length, loaded: request.responseText.length } ); 117 | 118 | } 119 | 120 | } 121 | 122 | }; 123 | 124 | request.open( "GET", url, true ); 125 | request.send( null ); 126 | 127 | } else { 128 | 129 | alert( "Don't know how to parse XML!" ); 130 | 131 | } 132 | 133 | } 134 | 135 | function parse( text, callBack, url ) { 136 | 137 | COLLADA = new DOMParser().parseFromString( text, 'text/xml' ); 138 | callBack = callBack || readyCallbackFunc; 139 | 140 | if ( url !== undefined ) { 141 | 142 | var parts = url.split( '/' ); 143 | parts.pop(); 144 | baseUrl = ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/'; 145 | 146 | } 147 | 148 | parseAsset(); 149 | setUpConversion(); 150 | images = parseLib( "library_images image", _Image, "image" ); 151 | materials = parseLib( "library_materials material", Material, "material" ); 152 | effects = parseLib( "library_effects effect", Effect, "effect" ); 153 | geometries = parseLib( "library_geometries geometry", Geometry, "geometry" ); 154 | cameras = parseLib( "library_cameras camera", Camera, "camera" ); 155 | lights = parseLib( "library_lights light", Light, "light" ); 156 | controllers = parseLib( "library_controllers controller", Controller, "controller" ); 157 | animations = parseLib( "library_animations animation", Animation, "animation" ); 158 | visualScenes = parseLib( "library_visual_scenes visual_scene", VisualScene, "visual_scene" ); 159 | kinematicsModels = parseLib( "library_kinematics_models kinematics_model", KinematicsModel, "kinematics_model" ); 160 | 161 | morphs = []; 162 | skins = []; 163 | 164 | visualScene = parseScene(); 165 | scene = new THREE.Group(); 166 | 167 | for ( var i = 0; i < visualScene.nodes.length; i ++ ) { 168 | 169 | scene.add( createSceneGraph( visualScene.nodes[ i ] ) ); 170 | 171 | } 172 | 173 | // unit conversion 174 | scene.scale.multiplyScalar( colladaUnit ); 175 | 176 | createAnimations(); 177 | 178 | kinematicsModel = parseKinematicsModel(); 179 | createKinematics(); 180 | 181 | var result = { 182 | 183 | scene: scene, 184 | morphs: morphs, 185 | skins: skins, 186 | animations: animData, 187 | kinematics: kinematics, 188 | dae: { 189 | images: images, 190 | materials: materials, 191 | cameras: cameras, 192 | lights: lights, 193 | effects: effects, 194 | geometries: geometries, 195 | controllers: controllers, 196 | animations: animations, 197 | visualScenes: visualScenes, 198 | visualScene: visualScene, 199 | scene: visualScene, 200 | kinematicsModels: kinematicsModels, 201 | kinematicsModel: kinematicsModel 202 | } 203 | 204 | }; 205 | 206 | if ( callBack ) { 207 | 208 | callBack( result ); 209 | 210 | } 211 | 212 | return result; 213 | 214 | } 215 | 216 | function setPreferredShading ( shading ) { 217 | 218 | preferredShading = shading; 219 | 220 | } 221 | 222 | function parseAsset () { 223 | 224 | var elements = COLLADA.querySelectorAll('asset'); 225 | 226 | var element = elements[0]; 227 | 228 | if ( element && element.childNodes ) { 229 | 230 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 231 | 232 | var child = element.childNodes[ i ]; 233 | 234 | switch ( child.nodeName ) { 235 | 236 | case 'unit': 237 | 238 | var meter = child.getAttribute( 'meter' ); 239 | 240 | if ( meter ) { 241 | 242 | colladaUnit = parseFloat( meter ); 243 | 244 | } 245 | 246 | break; 247 | 248 | case 'up_axis': 249 | 250 | colladaUp = child.textContent.charAt(0); 251 | break; 252 | 253 | } 254 | 255 | } 256 | 257 | } 258 | 259 | } 260 | 261 | function parseLib ( q, classSpec, prefix ) { 262 | 263 | var elements = COLLADA.querySelectorAll(q); 264 | 265 | var lib = {}; 266 | 267 | var i = 0; 268 | 269 | var elementsLength = elements.length; 270 | 271 | for ( var j = 0; j < elementsLength; j ++ ) { 272 | 273 | var element = elements[j]; 274 | var daeElement = ( new classSpec() ).parse( element ); 275 | 276 | if ( !daeElement.id || daeElement.id.length === 0 ) daeElement.id = prefix + ( i ++ ); 277 | lib[ daeElement.id ] = daeElement; 278 | 279 | } 280 | 281 | return lib; 282 | 283 | } 284 | 285 | function parseScene() { 286 | 287 | var sceneElement = COLLADA.querySelectorAll('scene instance_visual_scene')[0]; 288 | 289 | if ( sceneElement ) { 290 | 291 | var url = sceneElement.getAttribute( 'url' ).replace( /^#/, '' ); 292 | return visualScenes[ url.length > 0 ? url : 'visual_scene0' ]; 293 | 294 | } else { 295 | 296 | return null; 297 | 298 | } 299 | 300 | } 301 | 302 | function parseKinematicsModel() { 303 | 304 | var kinematicsModelElement = COLLADA.querySelectorAll('instance_kinematics_model')[0]; 305 | 306 | if ( kinematicsModelElement ) { 307 | 308 | var url = kinematicsModelElement.getAttribute( 'url' ).replace(/^#/, ''); 309 | return kinematicsModels[ url.length > 0 ? url : 'kinematics_model0' ]; 310 | 311 | } else { 312 | 313 | return null; 314 | 315 | } 316 | 317 | } 318 | 319 | function createAnimations() { 320 | 321 | animData = []; 322 | 323 | // fill in the keys 324 | recurseHierarchy( scene ); 325 | 326 | } 327 | 328 | function recurseHierarchy( node ) { 329 | 330 | var n = visualScene.getChildById( node.colladaId, true ), 331 | newData = null; 332 | 333 | if ( n && n.keys ) { 334 | 335 | newData = { 336 | fps: 60, 337 | hierarchy: [ { 338 | node: n, 339 | keys: n.keys, 340 | sids: n.sids 341 | } ], 342 | node: node, 343 | name: 'animation_' + node.name, 344 | length: 0 345 | }; 346 | 347 | animData.push(newData); 348 | 349 | for ( var i = 0, il = n.keys.length; i < il; i ++ ) { 350 | 351 | newData.length = Math.max( newData.length, n.keys[i].time ); 352 | 353 | } 354 | 355 | } else { 356 | 357 | newData = { 358 | hierarchy: [ { 359 | keys: [], 360 | sids: [] 361 | } ] 362 | } 363 | 364 | } 365 | 366 | for ( var i = 0, il = node.children.length; i < il; i ++ ) { 367 | 368 | var d = recurseHierarchy( node.children[i] ); 369 | 370 | for ( var j = 0, jl = d.hierarchy.length; j < jl; j ++ ) { 371 | 372 | newData.hierarchy.push( { 373 | keys: [], 374 | sids: [] 375 | } ); 376 | 377 | } 378 | 379 | } 380 | 381 | return newData; 382 | 383 | } 384 | 385 | function calcAnimationBounds () { 386 | 387 | var start = 1000000; 388 | var end = -start; 389 | var frames = 0; 390 | var ID; 391 | for ( var id in animations ) { 392 | 393 | var animation = animations[ id ]; 394 | ID = ID || animation.id; 395 | for ( var i = 0; i < animation.sampler.length; i ++ ) { 396 | 397 | var sampler = animation.sampler[ i ]; 398 | 399 | sampler.create(); 400 | 401 | start = Math.min( start, sampler.startTime ); 402 | end = Math.max( end, sampler.endTime ); 403 | frames = Math.max( frames, sampler.input.length ); 404 | 405 | } 406 | 407 | } 408 | 409 | return { start:start, end:end, frames:frames,ID:ID }; 410 | 411 | } 412 | 413 | function createMorph ( geometry, ctrl ) { 414 | 415 | var morphCtrl = ctrl instanceof InstanceController ? controllers[ ctrl.url ] : ctrl; 416 | 417 | if ( !morphCtrl || !morphCtrl.morph ) { 418 | 419 | console.log("could not find morph controller!"); 420 | return; 421 | 422 | } 423 | 424 | var morph = morphCtrl.morph; 425 | 426 | for ( var i = 0; i < morph.targets.length; i ++ ) { 427 | 428 | var target_id = morph.targets[ i ]; 429 | var daeGeometry = geometries[ target_id ]; 430 | 431 | if ( !daeGeometry.mesh || 432 | !daeGeometry.mesh.primitives || 433 | !daeGeometry.mesh.primitives.length ) { 434 | continue; 435 | } 436 | 437 | var target = daeGeometry.mesh.primitives[ 0 ].geometry; 438 | 439 | if ( target.vertices.length === geometry.vertices.length ) { 440 | 441 | geometry.morphTargets.push( { name: "target_1", vertices: target.vertices } ); 442 | 443 | } 444 | 445 | } 446 | 447 | geometry.morphTargets.push( { name: "target_Z", vertices: geometry.vertices } ); 448 | 449 | } 450 | 451 | function createSkin ( geometry, ctrl, applyBindShape ) { 452 | 453 | var skinCtrl = controllers[ ctrl.url ]; 454 | 455 | if ( !skinCtrl || !skinCtrl.skin ) { 456 | 457 | console.log( "could not find skin controller!" ); 458 | return; 459 | 460 | } 461 | 462 | if ( !ctrl.skeleton || !ctrl.skeleton.length ) { 463 | 464 | console.log( "could not find the skeleton for the skin!" ); 465 | return; 466 | 467 | } 468 | 469 | var skin = skinCtrl.skin; 470 | var skeleton = visualScene.getChildById( ctrl.skeleton[ 0 ] ); 471 | var hierarchy = []; 472 | 473 | applyBindShape = applyBindShape !== undefined ? applyBindShape : true; 474 | 475 | var bones = []; 476 | geometry.skinWeights = []; 477 | geometry.skinIndices = []; 478 | 479 | //createBones( geometry.bones, skin, hierarchy, skeleton, null, -1 ); 480 | //createWeights( skin, geometry.bones, geometry.skinIndices, geometry.skinWeights ); 481 | 482 | /* 483 | geometry.animation = { 484 | name: 'take_001', 485 | fps: 30, 486 | length: 2, 487 | JIT: true, 488 | hierarchy: hierarchy 489 | }; 490 | */ 491 | 492 | if ( applyBindShape ) { 493 | 494 | for ( var i = 0; i < geometry.vertices.length; i ++ ) { 495 | 496 | geometry.vertices[ i ].applyMatrix4( skin.bindShapeMatrix ); 497 | 498 | } 499 | 500 | } 501 | 502 | } 503 | 504 | function setupSkeleton ( node, bones, frame, parent ) { 505 | 506 | node.world = node.world || new THREE.Matrix4(); 507 | node.localworld = node.localworld || new THREE.Matrix4(); 508 | node.world.copy( node.matrix ); 509 | node.localworld.copy( node.matrix ); 510 | 511 | if ( node.channels && node.channels.length ) { 512 | 513 | var channel = node.channels[ 0 ]; 514 | var m = channel.sampler.output[ frame ]; 515 | 516 | if ( m instanceof THREE.Matrix4 ) { 517 | 518 | node.world.copy( m ); 519 | node.localworld.copy(m); 520 | if (frame === 0) 521 | node.matrix.copy(m); 522 | } 523 | 524 | } 525 | 526 | if ( parent ) { 527 | 528 | node.world.multiplyMatrices( parent, node.world ); 529 | 530 | } 531 | 532 | bones.push( node ); 533 | 534 | for ( var i = 0; i < node.nodes.length; i ++ ) { 535 | 536 | setupSkeleton( node.nodes[ i ], bones, frame, node.world ); 537 | 538 | } 539 | 540 | } 541 | 542 | function setupSkinningMatrices ( bones, skin ) { 543 | 544 | // FIXME: this is dumb... 545 | 546 | for ( var i = 0; i < bones.length; i ++ ) { 547 | 548 | var bone = bones[ i ]; 549 | var found = -1; 550 | 551 | if ( bone.type != 'JOINT' ) continue; 552 | 553 | for ( var j = 0; j < skin.joints.length; j ++ ) { 554 | 555 | if ( bone.sid === skin.joints[ j ] ) { 556 | 557 | found = j; 558 | break; 559 | 560 | } 561 | 562 | } 563 | 564 | if ( found >= 0 ) { 565 | 566 | var inv = skin.invBindMatrices[ found ]; 567 | 568 | bone.invBindMatrix = inv; 569 | bone.skinningMatrix = new THREE.Matrix4(); 570 | bone.skinningMatrix.multiplyMatrices(bone.world, inv); // (IBMi * JMi) 571 | bone.animatrix = new THREE.Matrix4(); 572 | 573 | bone.animatrix.copy(bone.localworld); 574 | bone.weights = []; 575 | 576 | for ( var j = 0; j < skin.weights.length; j ++ ) { 577 | 578 | for (var k = 0; k < skin.weights[ j ].length; k ++ ) { 579 | 580 | var w = skin.weights[ j ][ k ]; 581 | 582 | if ( w.joint === found ) { 583 | 584 | bone.weights.push( w ); 585 | 586 | } 587 | 588 | } 589 | 590 | } 591 | 592 | } else { 593 | 594 | console.warn( "ColladaLoader: Could not find joint '" + bone.sid + "'." ); 595 | 596 | bone.skinningMatrix = new THREE.Matrix4(); 597 | bone.weights = []; 598 | 599 | } 600 | } 601 | 602 | } 603 | 604 | //Walk the Collada tree and flatten the bones into a list, extract the position, quat and scale from the matrix 605 | function flattenSkeleton(skeleton) { 606 | 607 | var list = []; 608 | var walk = function(parentid, node, list) { 609 | 610 | var bone = {}; 611 | bone.name = node.sid; 612 | bone.parent = parentid; 613 | bone.matrix = node.matrix; 614 | var data = [ new THREE.Vector3(),new THREE.Quaternion(),new THREE.Vector3() ]; 615 | bone.matrix.decompose(data[0], data[1], data[2]); 616 | 617 | bone.pos = [ data[0].x,data[0].y,data[0].z ]; 618 | 619 | bone.scl = [ data[2].x,data[2].y,data[2].z ]; 620 | bone.rotq = [ data[1].x,data[1].y,data[1].z,data[1].w ]; 621 | list.push(bone); 622 | 623 | for (var i in node.nodes) { 624 | 625 | walk(node.sid, node.nodes[i], list); 626 | 627 | } 628 | 629 | }; 630 | 631 | walk(-1, skeleton, list); 632 | return list; 633 | 634 | } 635 | 636 | //Move the vertices into the pose that is proper for the start of the animation 637 | function skinToBindPose(geometry,skeleton,skinController) { 638 | 639 | var bones = []; 640 | setupSkeleton( skeleton, bones, -1 ); 641 | setupSkinningMatrices( bones, skinController.skin ); 642 | var v = new THREE.Vector3(); 643 | var skinned = []; 644 | 645 | for (var i = 0; i < geometry.vertices.length; i ++) { 646 | 647 | skinned.push(new THREE.Vector3()); 648 | 649 | } 650 | 651 | for ( i = 0; i < bones.length; i ++ ) { 652 | 653 | if ( bones[ i ].type != 'JOINT' ) continue; 654 | 655 | for ( var j = 0; j < bones[ i ].weights.length; j ++ ) { 656 | 657 | var w = bones[ i ].weights[ j ]; 658 | var vidx = w.index; 659 | var weight = w.weight; 660 | 661 | var o = geometry.vertices[vidx]; 662 | var s = skinned[vidx]; 663 | 664 | v.x = o.x; 665 | v.y = o.y; 666 | v.z = o.z; 667 | 668 | v.applyMatrix4( bones[i].skinningMatrix ); 669 | 670 | s.x += (v.x * weight); 671 | s.y += (v.y * weight); 672 | s.z += (v.z * weight); 673 | } 674 | 675 | } 676 | 677 | for (var i = 0; i < geometry.vertices.length; i ++) { 678 | 679 | geometry.vertices[i] = skinned[i]; 680 | 681 | } 682 | 683 | } 684 | 685 | function applySkin ( geometry, instanceCtrl, frame ) { 686 | 687 | var skinController = controllers[ instanceCtrl.url ]; 688 | 689 | frame = frame !== undefined ? frame : 40; 690 | 691 | if ( !skinController || !skinController.skin ) { 692 | 693 | console.log( 'ColladaLoader: Could not find skin controller.' ); 694 | return; 695 | 696 | } 697 | 698 | if ( !instanceCtrl.skeleton || !instanceCtrl.skeleton.length ) { 699 | 700 | console.log( 'ColladaLoader: Could not find the skeleton for the skin. ' ); 701 | return; 702 | 703 | } 704 | 705 | var animationBounds = calcAnimationBounds(); 706 | var skeleton = visualScene.getChildById( instanceCtrl.skeleton[0], true ) || visualScene.getChildBySid( instanceCtrl.skeleton[0], true ); 707 | 708 | //flatten the skeleton into a list of bones 709 | var bonelist = flattenSkeleton(skeleton); 710 | var joints = skinController.skin.joints; 711 | 712 | //sort that list so that the order reflects the order in the joint list 713 | var sortedbones = []; 714 | for (var i = 0; i < joints.length; i ++) { 715 | 716 | for (var j = 0; j < bonelist.length; j ++) { 717 | 718 | if (bonelist[j].name === joints[i]) { 719 | 720 | sortedbones[i] = bonelist[j]; 721 | 722 | } 723 | 724 | } 725 | 726 | } 727 | 728 | //hook up the parents by index instead of name 729 | for (var i = 0; i < sortedbones.length; i ++) { 730 | 731 | for (var j = 0; j < sortedbones.length; j ++) { 732 | 733 | if (sortedbones[i].parent === sortedbones[j].name) { 734 | 735 | sortedbones[i].parent = j; 736 | 737 | } 738 | 739 | } 740 | 741 | } 742 | 743 | 744 | var i, j, w, vidx, weight; 745 | var v = new THREE.Vector3(), o, s; 746 | 747 | // move vertices to bind shape 748 | for ( i = 0; i < geometry.vertices.length; i ++ ) { 749 | geometry.vertices[i].applyMatrix4( skinController.skin.bindShapeMatrix ); 750 | } 751 | 752 | var skinIndices = []; 753 | var skinWeights = []; 754 | var weights = skinController.skin.weights; 755 | 756 | // hook up the skin weights 757 | // TODO - this might be a good place to choose greatest 4 weights 758 | for ( var i =0; i < weights.length; i ++ ) { 759 | 760 | var indicies = new THREE.Vector4(weights[i][0] ? weights[i][0].joint : 0,weights[i][1] ? weights[i][1].joint : 0,weights[i][2] ? weights[i][2].joint : 0,weights[i][3] ? weights[i][3].joint : 0); 761 | var weight = new THREE.Vector4(weights[i][0] ? weights[i][0].weight : 0,weights[i][1] ? weights[i][1].weight : 0,weights[i][2] ? weights[i][2].weight : 0,weights[i][3] ? weights[i][3].weight : 0); 762 | 763 | skinIndices.push(indicies); 764 | skinWeights.push(weight); 765 | 766 | } 767 | 768 | geometry.skinIndices = skinIndices; 769 | geometry.skinWeights = skinWeights; 770 | geometry.bones = sortedbones; 771 | // process animation, or simply pose the rig if no animation 772 | 773 | //create an animation for the animated bones 774 | //NOTE: this has no effect when using morphtargets 775 | var animationdata = { "name":animationBounds.ID,"fps":30,"length":animationBounds.frames / 30,"hierarchy":[] }; 776 | 777 | for (var j = 0; j < sortedbones.length; j ++) { 778 | 779 | animationdata.hierarchy.push({ parent:sortedbones[j].parent, name:sortedbones[j].name, keys:[] }); 780 | 781 | } 782 | 783 | console.log( 'ColladaLoader:', animationBounds.ID + ' has ' + sortedbones.length + ' bones.' ); 784 | 785 | 786 | 787 | skinToBindPose(geometry, skeleton, skinController); 788 | 789 | 790 | for ( frame = 0; frame < animationBounds.frames; frame ++ ) { 791 | 792 | var bones = []; 793 | var skinned = []; 794 | // process the frame and setup the rig with a fresh 795 | // transform, possibly from the bone's animation channel(s) 796 | 797 | setupSkeleton( skeleton, bones, frame ); 798 | setupSkinningMatrices( bones, skinController.skin ); 799 | 800 | for (var i = 0; i < bones.length; i ++) { 801 | 802 | for (var j = 0; j < animationdata.hierarchy.length; j ++) { 803 | 804 | if (animationdata.hierarchy[j].name === bones[i].sid) { 805 | 806 | var key = {}; 807 | key.time = (frame / 30); 808 | key.matrix = bones[i].animatrix; 809 | 810 | if (frame === 0) 811 | bones[i].matrix = key.matrix; 812 | 813 | var data = [ new THREE.Vector3(),new THREE.Quaternion(),new THREE.Vector3() ]; 814 | key.matrix.decompose(data[0], data[1], data[2]); 815 | 816 | key.pos = [ data[0].x,data[0].y,data[0].z ]; 817 | 818 | key.scl = [ data[2].x,data[2].y,data[2].z ]; 819 | key.rot = data[1]; 820 | 821 | animationdata.hierarchy[j].keys.push(key); 822 | 823 | } 824 | 825 | } 826 | 827 | } 828 | 829 | geometry.animation = animationdata; 830 | 831 | } 832 | 833 | } 834 | 835 | function createKinematics() { 836 | 837 | if ( kinematicsModel && kinematicsModel.joints.length === 0 ) { 838 | kinematics = undefined; 839 | return; 840 | } 841 | 842 | var jointMap = {}; 843 | 844 | var _addToMap = function( jointIndex, parentVisualElement ) { 845 | 846 | var parentVisualElementId = parentVisualElement.getAttribute( 'id' ); 847 | var colladaNode = visualScene.getChildById( parentVisualElementId, true ); 848 | var joint = kinematicsModel.joints[ jointIndex ]; 849 | 850 | scene.traverse(function( node ) { 851 | 852 | if ( node.colladaId == parentVisualElementId ) { 853 | 854 | jointMap[ jointIndex ] = { 855 | node: node, 856 | transforms: colladaNode.transforms, 857 | joint: joint, 858 | position: joint.zeroPosition 859 | }; 860 | 861 | } 862 | 863 | }); 864 | 865 | }; 866 | 867 | kinematics = { 868 | 869 | joints: kinematicsModel && kinematicsModel.joints, 870 | 871 | getJointValue: function( jointIndex ) { 872 | 873 | var jointData = jointMap[ jointIndex ]; 874 | 875 | if ( jointData ) { 876 | 877 | return jointData.position; 878 | 879 | } else { 880 | 881 | console.log( 'getJointValue: joint ' + jointIndex + ' doesn\'t exist' ); 882 | 883 | } 884 | 885 | }, 886 | 887 | setJointValue: function( jointIndex, value ) { 888 | 889 | var jointData = jointMap[ jointIndex ]; 890 | 891 | if ( jointData ) { 892 | 893 | var joint = jointData.joint; 894 | 895 | if ( value > joint.limits.max || value < joint.limits.min ) { 896 | 897 | console.log( 'setJointValue: joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ')' ); 898 | 899 | } else if ( joint.static ) { 900 | 901 | console.log( 'setJointValue: joint ' + jointIndex + ' is static' ); 902 | 903 | } else { 904 | 905 | var threejsNode = jointData.node; 906 | var axis = joint.axis; 907 | var transforms = jointData.transforms; 908 | 909 | var matrix = new THREE.Matrix4(); 910 | 911 | for (i = 0; i < transforms.length; i ++ ) { 912 | 913 | var transform = transforms[ i ]; 914 | 915 | // kinda ghetto joint detection 916 | if ( transform.sid && transform.sid.indexOf( 'joint' + jointIndex ) !== -1 ) { 917 | 918 | // apply actual joint value here 919 | switch ( joint.type ) { 920 | 921 | case 'revolute': 922 | 923 | matrix.multiply( m1.makeRotationAxis( axis, THREE.Math.degToRad(value) ) ); 924 | break; 925 | 926 | case 'prismatic': 927 | 928 | matrix.multiply( m1.makeTranslation(axis.x * value, axis.y * value, axis.z * value ) ); 929 | break; 930 | 931 | default: 932 | 933 | console.warn( 'setJointValue: unknown joint type: ' + joint.type ); 934 | break; 935 | 936 | } 937 | 938 | } else { 939 | 940 | var m1 = new THREE.Matrix4(); 941 | 942 | switch ( transform.type ) { 943 | 944 | case 'matrix': 945 | 946 | matrix.multiply( transform.obj ); 947 | 948 | break; 949 | 950 | case 'translate': 951 | 952 | matrix.multiply( m1.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) ); 953 | 954 | break; 955 | 956 | case 'rotate': 957 | 958 | matrix.multiply( m1.makeRotationAxis( transform.obj, transform.angle ) ); 959 | 960 | break; 961 | 962 | } 963 | } 964 | } 965 | 966 | // apply the matrix to the threejs node 967 | var elementsFloat32Arr = matrix.elements; 968 | var elements = Array.prototype.slice.call( elementsFloat32Arr ); 969 | 970 | var elementsRowMajor = [ 971 | elements[ 0 ], 972 | elements[ 4 ], 973 | elements[ 8 ], 974 | elements[ 12 ], 975 | elements[ 1 ], 976 | elements[ 5 ], 977 | elements[ 9 ], 978 | elements[ 13 ], 979 | elements[ 2 ], 980 | elements[ 6 ], 981 | elements[ 10 ], 982 | elements[ 14 ], 983 | elements[ 3 ], 984 | elements[ 7 ], 985 | elements[ 11 ], 986 | elements[ 15 ] 987 | ]; 988 | 989 | threejsNode.matrix.set.apply( threejsNode.matrix, elementsRowMajor ); 990 | threejsNode.matrix.decompose( threejsNode.position, threejsNode.quaternion, threejsNode.scale ); 991 | } 992 | 993 | } else { 994 | 995 | console.log( 'setJointValue: joint ' + jointIndex + ' doesn\'t exist' ); 996 | 997 | } 998 | 999 | } 1000 | 1001 | }; 1002 | 1003 | var element = COLLADA.querySelector('scene instance_kinematics_scene'); 1004 | 1005 | if ( element ) { 1006 | 1007 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1008 | 1009 | var child = element.childNodes[ i ]; 1010 | 1011 | if ( child.nodeType != 1 ) continue; 1012 | 1013 | switch ( child.nodeName ) { 1014 | 1015 | case 'bind_joint_axis': 1016 | 1017 | var visualTarget = child.getAttribute( 'target' ).split( '/' ).pop(); 1018 | var axis = child.querySelector('axis param').textContent; 1019 | var jointIndex = parseInt( axis.split( 'joint' ).pop().split( '.' )[0] ); 1020 | var visualTargetElement = COLLADA.querySelector( '[sid="' + visualTarget + '"]' ); 1021 | 1022 | if ( visualTargetElement ) { 1023 | var parentVisualElement = visualTargetElement.parentElement; 1024 | _addToMap(jointIndex, parentVisualElement); 1025 | } 1026 | 1027 | break; 1028 | 1029 | default: 1030 | 1031 | break; 1032 | 1033 | } 1034 | 1035 | } 1036 | } 1037 | 1038 | } 1039 | 1040 | function createSceneGraph ( node, parent ) { 1041 | 1042 | var obj = new THREE.Object3D(); 1043 | var skinned = false; 1044 | var skinController; 1045 | var morphController; 1046 | var i, j; 1047 | 1048 | // FIXME: controllers 1049 | 1050 | for ( i = 0; i < node.controllers.length; i ++ ) { 1051 | 1052 | var controller = controllers[ node.controllers[ i ].url ]; 1053 | 1054 | switch ( controller.type ) { 1055 | 1056 | case 'skin': 1057 | 1058 | if ( geometries[ controller.skin.source ] ) { 1059 | 1060 | var inst_geom = new InstanceGeometry(); 1061 | 1062 | inst_geom.url = controller.skin.source; 1063 | inst_geom.instance_material = node.controllers[ i ].instance_material; 1064 | 1065 | node.geometries.push( inst_geom ); 1066 | skinned = true; 1067 | skinController = node.controllers[ i ]; 1068 | 1069 | } else if ( controllers[ controller.skin.source ] ) { 1070 | 1071 | // urgh: controller can be chained 1072 | // handle the most basic case... 1073 | 1074 | var second = controllers[ controller.skin.source ]; 1075 | morphController = second; 1076 | // skinController = node.controllers[i]; 1077 | 1078 | if ( second.morph && geometries[ second.morph.source ] ) { 1079 | 1080 | var inst_geom = new InstanceGeometry(); 1081 | 1082 | inst_geom.url = second.morph.source; 1083 | inst_geom.instance_material = node.controllers[ i ].instance_material; 1084 | 1085 | node.geometries.push( inst_geom ); 1086 | 1087 | } 1088 | 1089 | } 1090 | 1091 | break; 1092 | 1093 | case 'morph': 1094 | 1095 | if ( geometries[ controller.morph.source ] ) { 1096 | 1097 | var inst_geom = new InstanceGeometry(); 1098 | 1099 | inst_geom.url = controller.morph.source; 1100 | inst_geom.instance_material = node.controllers[ i ].instance_material; 1101 | 1102 | node.geometries.push( inst_geom ); 1103 | morphController = node.controllers[ i ]; 1104 | 1105 | } 1106 | 1107 | console.log( 'ColladaLoader: Morph-controller partially supported.' ); 1108 | 1109 | default: 1110 | break; 1111 | 1112 | } 1113 | 1114 | } 1115 | 1116 | // geometries 1117 | 1118 | var double_sided_materials = {}; 1119 | 1120 | for ( i = 0; i < node.geometries.length; i ++ ) { 1121 | 1122 | var instance_geometry = node.geometries[i]; 1123 | var instance_materials = instance_geometry.instance_material; 1124 | var geometry = geometries[ instance_geometry.url ]; 1125 | var used_materials = {}; 1126 | var used_materials_array = []; 1127 | var num_materials = 0; 1128 | var first_material; 1129 | 1130 | if ( geometry ) { 1131 | 1132 | if ( !geometry.mesh || !geometry.mesh.primitives ) 1133 | continue; 1134 | 1135 | if ( obj.name.length === 0 ) { 1136 | 1137 | obj.name = geometry.id; 1138 | 1139 | } 1140 | 1141 | // collect used fx for this geometry-instance 1142 | 1143 | if ( instance_materials ) { 1144 | 1145 | for ( j = 0; j < instance_materials.length; j ++ ) { 1146 | 1147 | var instance_material = instance_materials[ j ]; 1148 | var mat = materials[ instance_material.target ]; 1149 | var effect_id = mat.instance_effect.url; 1150 | var shader = effects[ effect_id ].shader; 1151 | var material3js = shader.material; 1152 | 1153 | if ( geometry.doubleSided ) { 1154 | 1155 | if ( !( instance_material.symbol in double_sided_materials ) ) { 1156 | 1157 | var _copied_material = material3js.clone(); 1158 | _copied_material.side = THREE.DoubleSide; 1159 | double_sided_materials[ instance_material.symbol ] = _copied_material; 1160 | 1161 | } 1162 | 1163 | material3js = double_sided_materials[ instance_material.symbol ]; 1164 | 1165 | } 1166 | 1167 | material3js.opacity = !material3js.opacity ? 1 : material3js.opacity; 1168 | used_materials[ instance_material.symbol ] = num_materials; 1169 | used_materials_array.push( material3js ); 1170 | first_material = material3js; 1171 | first_material.name = mat.name === null || mat.name === '' ? mat.id : mat.name; 1172 | num_materials ++; 1173 | 1174 | } 1175 | 1176 | } 1177 | 1178 | var mesh; 1179 | var material = first_material || new THREE.MeshLambertMaterial( { color: 0xdddddd, side: geometry.doubleSided ? THREE.DoubleSide : THREE.FrontSide } ); 1180 | var geom = geometry.mesh.geometry3js; 1181 | 1182 | if ( num_materials > 1 ) { 1183 | 1184 | material = new THREE.MultiMaterial( used_materials_array ); 1185 | 1186 | for ( j = 0; j < geom.faces.length; j ++ ) { 1187 | 1188 | var face = geom.faces[ j ]; 1189 | face.materialIndex = used_materials[ face.daeMaterial ] 1190 | 1191 | } 1192 | 1193 | } 1194 | 1195 | if ( skinController !== undefined ) { 1196 | 1197 | 1198 | applySkin( geom, skinController ); 1199 | 1200 | if ( geom.morphTargets.length > 0 ) { 1201 | 1202 | material.morphTargets = true; 1203 | material.skinning = false; 1204 | 1205 | } else { 1206 | 1207 | material.morphTargets = false; 1208 | material.skinning = true; 1209 | 1210 | } 1211 | 1212 | 1213 | mesh = new THREE.SkinnedMesh( geom, material, false ); 1214 | 1215 | 1216 | //mesh.skeleton = skinController.skeleton; 1217 | //mesh.skinController = controllers[ skinController.url ]; 1218 | //mesh.skinInstanceController = skinController; 1219 | mesh.name = 'skin_' + skins.length; 1220 | 1221 | 1222 | 1223 | //mesh.animationHandle.setKey(0); 1224 | skins.push( mesh ); 1225 | 1226 | } else if ( morphController !== undefined ) { 1227 | 1228 | createMorph( geom, morphController ); 1229 | 1230 | material.morphTargets = true; 1231 | 1232 | mesh = new THREE.Mesh( geom, material ); 1233 | mesh.name = 'morph_' + morphs.length; 1234 | 1235 | morphs.push( mesh ); 1236 | 1237 | } else { 1238 | 1239 | if ( geom.isLineStrip === true ) { 1240 | 1241 | mesh = new THREE.Line( geom ); 1242 | 1243 | } else { 1244 | 1245 | mesh = new THREE.Mesh( geom, material ); 1246 | 1247 | } 1248 | 1249 | } 1250 | 1251 | obj.add(mesh); 1252 | 1253 | } 1254 | 1255 | } 1256 | 1257 | for ( i = 0; i < node.cameras.length; i ++ ) { 1258 | 1259 | var instance_camera = node.cameras[i]; 1260 | var cparams = cameras[instance_camera.url]; 1261 | 1262 | var cam = new THREE.PerspectiveCamera(cparams.yfov, parseFloat(cparams.aspect_ratio), 1263 | parseFloat(cparams.znear), parseFloat(cparams.zfar)); 1264 | 1265 | obj.add(cam); 1266 | } 1267 | 1268 | for ( i = 0; i < node.lights.length; i ++ ) { 1269 | 1270 | var light = null; 1271 | var instance_light = node.lights[i]; 1272 | var lparams = lights[instance_light.url]; 1273 | 1274 | if ( lparams && lparams.technique ) { 1275 | 1276 | var color = lparams.color.getHex(); 1277 | var intensity = lparams.intensity; 1278 | var distance = lparams.distance; 1279 | var angle = lparams.falloff_angle; 1280 | 1281 | switch ( lparams.technique ) { 1282 | 1283 | case 'directional': 1284 | 1285 | light = new THREE.DirectionalLight( color, intensity, distance ); 1286 | light.position.set(0, 0, 1); 1287 | break; 1288 | 1289 | case 'point': 1290 | 1291 | light = new THREE.PointLight( color, intensity, distance ); 1292 | break; 1293 | 1294 | case 'spot': 1295 | 1296 | light = new THREE.SpotLight( color, intensity, distance, angle ); 1297 | light.position.set(0, 0, 1); 1298 | break; 1299 | 1300 | case 'ambient': 1301 | 1302 | light = new THREE.AmbientLight( color ); 1303 | break; 1304 | 1305 | } 1306 | 1307 | } 1308 | 1309 | if (light) { 1310 | obj.add(light); 1311 | } 1312 | } 1313 | 1314 | obj.name = node.name || node.id || ""; 1315 | obj.colladaId = node.id || ""; 1316 | obj.layer = node.layer || ""; 1317 | obj.matrix = node.matrix; 1318 | obj.matrix.decompose( obj.position, obj.quaternion, obj.scale ); 1319 | 1320 | if ( options.centerGeometry && obj.geometry ) { 1321 | 1322 | var delta = obj.geometry.center(); 1323 | delta.multiply( obj.scale ); 1324 | delta.applyQuaternion( obj.quaternion ); 1325 | 1326 | obj.position.sub( delta ); 1327 | 1328 | } 1329 | 1330 | for ( i = 0; i < node.nodes.length; i ++ ) { 1331 | 1332 | obj.add( createSceneGraph( node.nodes[i], node ) ); 1333 | 1334 | } 1335 | 1336 | return obj; 1337 | 1338 | } 1339 | 1340 | function getJointId( skin, id ) { 1341 | 1342 | for ( var i = 0; i < skin.joints.length; i ++ ) { 1343 | 1344 | if ( skin.joints[ i ] === id ) { 1345 | 1346 | return i; 1347 | 1348 | } 1349 | 1350 | } 1351 | 1352 | } 1353 | 1354 | function getLibraryNode( id ) { 1355 | 1356 | var nodes = COLLADA.querySelectorAll('library_nodes node'); 1357 | 1358 | for ( var i = 0; i < nodes.length; i++ ) { 1359 | 1360 | var attObj = nodes[i].attributes.getNamedItem('id'); 1361 | 1362 | if ( attObj && attObj.value === id ) { 1363 | 1364 | return nodes[i]; 1365 | 1366 | } 1367 | 1368 | } 1369 | 1370 | return undefined; 1371 | 1372 | } 1373 | 1374 | function getChannelsForNode ( node ) { 1375 | 1376 | var channels = []; 1377 | var startTime = 1000000; 1378 | var endTime = -1000000; 1379 | 1380 | for ( var id in animations ) { 1381 | 1382 | var animation = animations[id]; 1383 | 1384 | for ( var i = 0; i < animation.channel.length; i ++ ) { 1385 | 1386 | var channel = animation.channel[i]; 1387 | var sampler = animation.sampler[i]; 1388 | var id = channel.target.split('/')[0]; 1389 | 1390 | if ( id == node.id ) { 1391 | 1392 | sampler.create(); 1393 | channel.sampler = sampler; 1394 | startTime = Math.min(startTime, sampler.startTime); 1395 | endTime = Math.max(endTime, sampler.endTime); 1396 | channels.push(channel); 1397 | 1398 | } 1399 | 1400 | } 1401 | 1402 | } 1403 | 1404 | if ( channels.length ) { 1405 | 1406 | node.startTime = startTime; 1407 | node.endTime = endTime; 1408 | 1409 | } 1410 | 1411 | return channels; 1412 | 1413 | } 1414 | 1415 | function calcFrameDuration( node ) { 1416 | 1417 | var minT = 10000000; 1418 | 1419 | for ( var i = 0; i < node.channels.length; i ++ ) { 1420 | 1421 | var sampler = node.channels[i].sampler; 1422 | 1423 | for ( var j = 0; j < sampler.input.length - 1; j ++ ) { 1424 | 1425 | var t0 = sampler.input[ j ]; 1426 | var t1 = sampler.input[ j + 1 ]; 1427 | minT = Math.min( minT, t1 - t0 ); 1428 | 1429 | } 1430 | } 1431 | 1432 | return minT; 1433 | 1434 | } 1435 | 1436 | function calcMatrixAt( node, t ) { 1437 | 1438 | var animated = {}; 1439 | 1440 | var i, j; 1441 | 1442 | for ( i = 0; i < node.channels.length; i ++ ) { 1443 | 1444 | var channel = node.channels[ i ]; 1445 | animated[ channel.sid ] = channel; 1446 | 1447 | } 1448 | 1449 | var matrix = new THREE.Matrix4(); 1450 | 1451 | for ( i = 0; i < node.transforms.length; i ++ ) { 1452 | 1453 | var transform = node.transforms[ i ]; 1454 | var channel = animated[ transform.sid ]; 1455 | 1456 | if ( channel !== undefined ) { 1457 | 1458 | var sampler = channel.sampler; 1459 | var value; 1460 | 1461 | for ( j = 0; j < sampler.input.length - 1; j ++ ) { 1462 | 1463 | if ( sampler.input[ j + 1 ] > t ) { 1464 | 1465 | value = sampler.output[ j ]; 1466 | //console.log(value.flatten) 1467 | break; 1468 | 1469 | } 1470 | 1471 | } 1472 | 1473 | if ( value !== undefined ) { 1474 | 1475 | if ( value instanceof THREE.Matrix4 ) { 1476 | 1477 | matrix.multiplyMatrices( matrix, value ); 1478 | 1479 | } else { 1480 | 1481 | // FIXME: handle other types 1482 | 1483 | matrix.multiplyMatrices( matrix, transform.matrix ); 1484 | 1485 | } 1486 | 1487 | } else { 1488 | 1489 | matrix.multiplyMatrices( matrix, transform.matrix ); 1490 | 1491 | } 1492 | 1493 | } else { 1494 | 1495 | matrix.multiplyMatrices( matrix, transform.matrix ); 1496 | 1497 | } 1498 | 1499 | } 1500 | 1501 | return matrix; 1502 | 1503 | } 1504 | 1505 | function bakeAnimations ( node ) { 1506 | 1507 | if ( node.channels && node.channels.length ) { 1508 | 1509 | var keys = [], 1510 | sids = []; 1511 | 1512 | for ( var i = 0, il = node.channels.length; i < il; i ++ ) { 1513 | 1514 | var channel = node.channels[i], 1515 | fullSid = channel.fullSid, 1516 | sampler = channel.sampler, 1517 | input = sampler.input, 1518 | transform = node.getTransformBySid( channel.sid ), 1519 | member; 1520 | 1521 | if ( channel.arrIndices ) { 1522 | 1523 | member = []; 1524 | 1525 | for ( var j = 0, jl = channel.arrIndices.length; j < jl; j ++ ) { 1526 | 1527 | member[ j ] = getConvertedIndex( channel.arrIndices[ j ] ); 1528 | 1529 | } 1530 | 1531 | } else { 1532 | 1533 | member = getConvertedMember( channel.member ); 1534 | 1535 | } 1536 | 1537 | if ( transform ) { 1538 | 1539 | if ( sids.indexOf( fullSid ) === -1 ) { 1540 | 1541 | sids.push( fullSid ); 1542 | 1543 | } 1544 | 1545 | for ( var j = 0, jl = input.length; j < jl; j ++ ) { 1546 | 1547 | var time = input[j], 1548 | data = sampler.getData( transform.type, j, member ), 1549 | key = findKey( keys, time ); 1550 | 1551 | if ( !key ) { 1552 | 1553 | key = new Key( time ); 1554 | var timeNdx = findTimeNdx( keys, time ); 1555 | keys.splice( timeNdx === -1 ? keys.length : timeNdx, 0, key ); 1556 | 1557 | } 1558 | 1559 | key.addTarget( fullSid, transform, member, data ); 1560 | 1561 | } 1562 | 1563 | } else { 1564 | 1565 | console.log( 'Could not find transform "' + channel.sid + '" in node ' + node.id ); 1566 | 1567 | } 1568 | 1569 | } 1570 | 1571 | // post process 1572 | for ( var i = 0; i < sids.length; i ++ ) { 1573 | 1574 | var sid = sids[ i ]; 1575 | 1576 | for ( var j = 0; j < keys.length; j ++ ) { 1577 | 1578 | var key = keys[ j ]; 1579 | 1580 | if ( !key.hasTarget( sid ) ) { 1581 | 1582 | interpolateKeys( keys, key, j, sid ); 1583 | 1584 | } 1585 | 1586 | } 1587 | 1588 | } 1589 | 1590 | node.keys = keys; 1591 | node.sids = sids; 1592 | 1593 | } 1594 | 1595 | } 1596 | 1597 | function findKey ( keys, time) { 1598 | 1599 | var retVal = null; 1600 | 1601 | for ( var i = 0, il = keys.length; i < il && retVal === null; i ++ ) { 1602 | 1603 | var key = keys[i]; 1604 | 1605 | if ( key.time === time ) { 1606 | 1607 | retVal = key; 1608 | 1609 | } else if ( key.time > time ) { 1610 | 1611 | break; 1612 | 1613 | } 1614 | 1615 | } 1616 | 1617 | return retVal; 1618 | 1619 | } 1620 | 1621 | function findTimeNdx ( keys, time) { 1622 | 1623 | var ndx = -1; 1624 | 1625 | for ( var i = 0, il = keys.length; i < il && ndx === -1; i ++ ) { 1626 | 1627 | var key = keys[i]; 1628 | 1629 | if ( key.time >= time ) { 1630 | 1631 | ndx = i; 1632 | 1633 | } 1634 | 1635 | } 1636 | 1637 | return ndx; 1638 | 1639 | } 1640 | 1641 | function interpolateKeys ( keys, key, ndx, fullSid ) { 1642 | 1643 | var prevKey = getPrevKeyWith( keys, fullSid, ndx ? ndx - 1 : 0 ), 1644 | nextKey = getNextKeyWith( keys, fullSid, ndx + 1 ); 1645 | 1646 | if ( prevKey && nextKey ) { 1647 | 1648 | var scale = (key.time - prevKey.time) / (nextKey.time - prevKey.time), 1649 | prevTarget = prevKey.getTarget( fullSid ), 1650 | nextData = nextKey.getTarget( fullSid ).data, 1651 | prevData = prevTarget.data, 1652 | data; 1653 | 1654 | if ( prevTarget.type === 'matrix' ) { 1655 | 1656 | data = prevData; 1657 | 1658 | } else if ( prevData.length ) { 1659 | 1660 | data = []; 1661 | 1662 | for ( var i = 0; i < prevData.length; ++ i ) { 1663 | 1664 | data[ i ] = prevData[ i ] + ( nextData[ i ] - prevData[ i ] ) * scale; 1665 | 1666 | } 1667 | 1668 | } else { 1669 | 1670 | data = prevData + ( nextData - prevData ) * scale; 1671 | 1672 | } 1673 | 1674 | key.addTarget( fullSid, prevTarget.transform, prevTarget.member, data ); 1675 | 1676 | } 1677 | 1678 | } 1679 | 1680 | // Get next key with given sid 1681 | 1682 | function getNextKeyWith( keys, fullSid, ndx ) { 1683 | 1684 | for ( ; ndx < keys.length; ndx ++ ) { 1685 | 1686 | var key = keys[ ndx ]; 1687 | 1688 | if ( key.hasTarget( fullSid ) ) { 1689 | 1690 | return key; 1691 | 1692 | } 1693 | 1694 | } 1695 | 1696 | return null; 1697 | 1698 | } 1699 | 1700 | // Get previous key with given sid 1701 | 1702 | function getPrevKeyWith( keys, fullSid, ndx ) { 1703 | 1704 | ndx = ndx >= 0 ? ndx : ndx + keys.length; 1705 | 1706 | for ( ; ndx >= 0; ndx -- ) { 1707 | 1708 | var key = keys[ ndx ]; 1709 | 1710 | if ( key.hasTarget( fullSid ) ) { 1711 | 1712 | return key; 1713 | 1714 | } 1715 | 1716 | } 1717 | 1718 | return null; 1719 | 1720 | } 1721 | 1722 | function _Image() { 1723 | 1724 | this.id = ""; 1725 | this.init_from = ""; 1726 | 1727 | } 1728 | 1729 | _Image.prototype.parse = function(element) { 1730 | 1731 | this.id = element.getAttribute('id'); 1732 | 1733 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1734 | 1735 | var child = element.childNodes[ i ]; 1736 | 1737 | if ( child.nodeName === 'init_from' ) { 1738 | 1739 | this.init_from = child.textContent; 1740 | 1741 | } 1742 | 1743 | } 1744 | 1745 | return this; 1746 | 1747 | }; 1748 | 1749 | function Controller() { 1750 | 1751 | this.id = ""; 1752 | this.name = ""; 1753 | this.type = ""; 1754 | this.skin = null; 1755 | this.morph = null; 1756 | 1757 | } 1758 | 1759 | Controller.prototype.parse = function( element ) { 1760 | 1761 | this.id = element.getAttribute('id'); 1762 | this.name = element.getAttribute('name'); 1763 | this.type = "none"; 1764 | 1765 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1766 | 1767 | var child = element.childNodes[ i ]; 1768 | 1769 | switch ( child.nodeName ) { 1770 | 1771 | case 'skin': 1772 | 1773 | this.skin = (new Skin()).parse(child); 1774 | this.type = child.nodeName; 1775 | break; 1776 | 1777 | case 'morph': 1778 | 1779 | this.morph = (new Morph()).parse(child); 1780 | this.type = child.nodeName; 1781 | break; 1782 | 1783 | default: 1784 | break; 1785 | 1786 | } 1787 | } 1788 | 1789 | return this; 1790 | 1791 | }; 1792 | 1793 | function Morph() { 1794 | 1795 | this.method = null; 1796 | this.source = null; 1797 | this.targets = null; 1798 | this.weights = null; 1799 | 1800 | } 1801 | 1802 | Morph.prototype.parse = function( element ) { 1803 | 1804 | var sources = {}; 1805 | var inputs = []; 1806 | var i; 1807 | 1808 | this.method = element.getAttribute( 'method' ); 1809 | this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); 1810 | 1811 | for ( i = 0; i < element.childNodes.length; i ++ ) { 1812 | 1813 | var child = element.childNodes[ i ]; 1814 | if ( child.nodeType != 1 ) continue; 1815 | 1816 | switch ( child.nodeName ) { 1817 | 1818 | case 'source': 1819 | 1820 | var source = ( new Source() ).parse( child ); 1821 | sources[ source.id ] = source; 1822 | break; 1823 | 1824 | case 'targets': 1825 | 1826 | inputs = this.parseInputs( child ); 1827 | break; 1828 | 1829 | default: 1830 | 1831 | console.log( child.nodeName ); 1832 | break; 1833 | 1834 | } 1835 | 1836 | } 1837 | 1838 | for ( i = 0; i < inputs.length; i ++ ) { 1839 | 1840 | var input = inputs[ i ]; 1841 | var source = sources[ input.source ]; 1842 | 1843 | switch ( input.semantic ) { 1844 | 1845 | case 'MORPH_TARGET': 1846 | 1847 | this.targets = source.read(); 1848 | break; 1849 | 1850 | case 'MORPH_WEIGHT': 1851 | 1852 | this.weights = source.read(); 1853 | break; 1854 | 1855 | default: 1856 | break; 1857 | 1858 | } 1859 | } 1860 | 1861 | return this; 1862 | 1863 | }; 1864 | 1865 | Morph.prototype.parseInputs = function(element) { 1866 | 1867 | var inputs = []; 1868 | 1869 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1870 | 1871 | var child = element.childNodes[i]; 1872 | if ( child.nodeType != 1) continue; 1873 | 1874 | switch ( child.nodeName ) { 1875 | 1876 | case 'input': 1877 | 1878 | inputs.push( (new Input()).parse(child) ); 1879 | break; 1880 | 1881 | default: 1882 | break; 1883 | } 1884 | } 1885 | 1886 | return inputs; 1887 | 1888 | }; 1889 | 1890 | function Skin() { 1891 | 1892 | this.source = ""; 1893 | this.bindShapeMatrix = null; 1894 | this.invBindMatrices = []; 1895 | this.joints = []; 1896 | this.weights = []; 1897 | 1898 | } 1899 | 1900 | Skin.prototype.parse = function( element ) { 1901 | 1902 | var sources = {}; 1903 | var joints, weights; 1904 | 1905 | this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); 1906 | this.invBindMatrices = []; 1907 | this.joints = []; 1908 | this.weights = []; 1909 | 1910 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1911 | 1912 | var child = element.childNodes[i]; 1913 | if ( child.nodeType != 1 ) continue; 1914 | 1915 | switch ( child.nodeName ) { 1916 | 1917 | case 'bind_shape_matrix': 1918 | 1919 | var f = _floats(child.textContent); 1920 | this.bindShapeMatrix = getConvertedMat4( f ); 1921 | break; 1922 | 1923 | case 'source': 1924 | 1925 | var src = new Source().parse(child); 1926 | sources[ src.id ] = src; 1927 | break; 1928 | 1929 | case 'joints': 1930 | 1931 | joints = child; 1932 | break; 1933 | 1934 | case 'vertex_weights': 1935 | 1936 | weights = child; 1937 | break; 1938 | 1939 | default: 1940 | 1941 | console.log( child.nodeName ); 1942 | break; 1943 | 1944 | } 1945 | } 1946 | 1947 | this.parseJoints( joints, sources ); 1948 | this.parseWeights( weights, sources ); 1949 | 1950 | return this; 1951 | 1952 | }; 1953 | 1954 | Skin.prototype.parseJoints = function ( element, sources ) { 1955 | 1956 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1957 | 1958 | var child = element.childNodes[ i ]; 1959 | if ( child.nodeType != 1 ) continue; 1960 | 1961 | switch ( child.nodeName ) { 1962 | 1963 | case 'input': 1964 | 1965 | var input = ( new Input() ).parse( child ); 1966 | var source = sources[ input.source ]; 1967 | 1968 | if ( input.semantic === 'JOINT' ) { 1969 | 1970 | this.joints = source.read(); 1971 | 1972 | } else if ( input.semantic === 'INV_BIND_MATRIX' ) { 1973 | 1974 | this.invBindMatrices = source.read(); 1975 | 1976 | } 1977 | 1978 | break; 1979 | 1980 | default: 1981 | break; 1982 | } 1983 | 1984 | } 1985 | 1986 | }; 1987 | 1988 | Skin.prototype.parseWeights = function ( element, sources ) { 1989 | 1990 | var v, vcount, inputs = []; 1991 | 1992 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1993 | 1994 | var child = element.childNodes[ i ]; 1995 | if ( child.nodeType != 1 ) continue; 1996 | 1997 | switch ( child.nodeName ) { 1998 | 1999 | case 'input': 2000 | 2001 | inputs.push( ( new Input() ).parse( child ) ); 2002 | break; 2003 | 2004 | case 'v': 2005 | 2006 | v = _ints( child.textContent ); 2007 | break; 2008 | 2009 | case 'vcount': 2010 | 2011 | vcount = _ints( child.textContent ); 2012 | break; 2013 | 2014 | default: 2015 | break; 2016 | 2017 | } 2018 | 2019 | } 2020 | 2021 | var index = 0; 2022 | 2023 | for ( var i = 0; i < vcount.length; i ++ ) { 2024 | 2025 | var numBones = vcount[i]; 2026 | var vertex_weights = []; 2027 | 2028 | for ( var j = 0; j < numBones; j ++ ) { 2029 | 2030 | var influence = {}; 2031 | 2032 | for ( var k = 0; k < inputs.length; k ++ ) { 2033 | 2034 | var input = inputs[ k ]; 2035 | var value = v[ index + input.offset ]; 2036 | 2037 | switch ( input.semantic ) { 2038 | 2039 | case 'JOINT': 2040 | 2041 | influence.joint = value;//this.joints[value]; 2042 | break; 2043 | 2044 | case 'WEIGHT': 2045 | 2046 | influence.weight = sources[ input.source ].data[ value ]; 2047 | break; 2048 | 2049 | default: 2050 | break; 2051 | 2052 | } 2053 | 2054 | } 2055 | 2056 | vertex_weights.push( influence ); 2057 | index += inputs.length; 2058 | } 2059 | 2060 | for ( var j = 0; j < vertex_weights.length; j ++ ) { 2061 | 2062 | vertex_weights[ j ].index = i; 2063 | 2064 | } 2065 | 2066 | this.weights.push( vertex_weights ); 2067 | 2068 | } 2069 | 2070 | }; 2071 | 2072 | function VisualScene () { 2073 | 2074 | this.id = ""; 2075 | this.name = ""; 2076 | this.nodes = []; 2077 | this.scene = new THREE.Group(); 2078 | 2079 | } 2080 | 2081 | VisualScene.prototype.getChildById = function( id, recursive ) { 2082 | 2083 | for ( var i = 0; i < this.nodes.length; i ++ ) { 2084 | 2085 | var node = this.nodes[ i ].getChildById( id, recursive ); 2086 | 2087 | if ( node ) { 2088 | 2089 | return node; 2090 | 2091 | } 2092 | 2093 | } 2094 | 2095 | return null; 2096 | 2097 | }; 2098 | 2099 | VisualScene.prototype.getChildBySid = function( sid, recursive ) { 2100 | 2101 | for ( var i = 0; i < this.nodes.length; i ++ ) { 2102 | 2103 | var node = this.nodes[ i ].getChildBySid( sid, recursive ); 2104 | 2105 | if ( node ) { 2106 | 2107 | return node; 2108 | 2109 | } 2110 | 2111 | } 2112 | 2113 | return null; 2114 | 2115 | }; 2116 | 2117 | VisualScene.prototype.parse = function( element ) { 2118 | 2119 | this.id = element.getAttribute( 'id' ); 2120 | this.name = element.getAttribute( 'name' ); 2121 | this.nodes = []; 2122 | 2123 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2124 | 2125 | var child = element.childNodes[ i ]; 2126 | if ( child.nodeType != 1 ) continue; 2127 | 2128 | switch ( child.nodeName ) { 2129 | 2130 | case 'node': 2131 | 2132 | this.nodes.push( ( new Node() ).parse( child ) ); 2133 | break; 2134 | 2135 | default: 2136 | break; 2137 | 2138 | } 2139 | 2140 | } 2141 | 2142 | return this; 2143 | 2144 | }; 2145 | 2146 | function Node() { 2147 | 2148 | this.id = ""; 2149 | this.name = ""; 2150 | this.sid = ""; 2151 | this.nodes = []; 2152 | this.controllers = []; 2153 | this.transforms = []; 2154 | this.geometries = []; 2155 | this.channels = []; 2156 | this.matrix = new THREE.Matrix4(); 2157 | 2158 | } 2159 | 2160 | Node.prototype.getChannelForTransform = function( transformSid ) { 2161 | 2162 | for ( var i = 0; i < this.channels.length; i ++ ) { 2163 | 2164 | var channel = this.channels[i]; 2165 | var parts = channel.target.split('/'); 2166 | var id = parts.shift(); 2167 | var sid = parts.shift(); 2168 | var dotSyntax = (sid.indexOf(".") >= 0); 2169 | var arrSyntax = (sid.indexOf("(") >= 0); 2170 | var arrIndices; 2171 | var member; 2172 | 2173 | if ( dotSyntax ) { 2174 | 2175 | parts = sid.split("."); 2176 | sid = parts.shift(); 2177 | member = parts.shift(); 2178 | 2179 | } else if ( arrSyntax ) { 2180 | 2181 | arrIndices = sid.split("("); 2182 | sid = arrIndices.shift(); 2183 | 2184 | for ( var j = 0; j < arrIndices.length; j ++ ) { 2185 | 2186 | arrIndices[ j ] = parseInt( arrIndices[ j ].replace( /\)/, '' ) ); 2187 | 2188 | } 2189 | 2190 | } 2191 | 2192 | if ( sid === transformSid ) { 2193 | 2194 | channel.info = { sid: sid, dotSyntax: dotSyntax, arrSyntax: arrSyntax, arrIndices: arrIndices }; 2195 | return channel; 2196 | 2197 | } 2198 | 2199 | } 2200 | 2201 | return null; 2202 | 2203 | }; 2204 | 2205 | Node.prototype.getChildById = function ( id, recursive ) { 2206 | 2207 | if ( this.id === id ) { 2208 | 2209 | return this; 2210 | 2211 | } 2212 | 2213 | if ( recursive ) { 2214 | 2215 | for ( var i = 0; i < this.nodes.length; i ++ ) { 2216 | 2217 | var n = this.nodes[ i ].getChildById( id, recursive ); 2218 | 2219 | if ( n ) { 2220 | 2221 | return n; 2222 | 2223 | } 2224 | 2225 | } 2226 | 2227 | } 2228 | 2229 | return null; 2230 | 2231 | }; 2232 | 2233 | Node.prototype.getChildBySid = function ( sid, recursive ) { 2234 | 2235 | if ( this.sid === sid ) { 2236 | 2237 | return this; 2238 | 2239 | } 2240 | 2241 | if ( recursive ) { 2242 | 2243 | for ( var i = 0; i < this.nodes.length; i ++ ) { 2244 | 2245 | var n = this.nodes[ i ].getChildBySid( sid, recursive ); 2246 | 2247 | if ( n ) { 2248 | 2249 | return n; 2250 | 2251 | } 2252 | 2253 | } 2254 | } 2255 | 2256 | return null; 2257 | 2258 | }; 2259 | 2260 | Node.prototype.getTransformBySid = function ( sid ) { 2261 | 2262 | for ( var i = 0; i < this.transforms.length; i ++ ) { 2263 | 2264 | if ( this.transforms[ i ].sid === sid ) return this.transforms[ i ]; 2265 | 2266 | } 2267 | 2268 | return null; 2269 | 2270 | }; 2271 | 2272 | Node.prototype.parse = function( element ) { 2273 | 2274 | var url; 2275 | 2276 | this.id = element.getAttribute('id'); 2277 | this.sid = element.getAttribute('sid'); 2278 | this.name = element.getAttribute('name'); 2279 | this.type = element.getAttribute('type'); 2280 | this.layer = element.getAttribute('layer'); 2281 | 2282 | this.type = this.type === 'JOINT' ? this.type : 'NODE'; 2283 | 2284 | this.nodes = []; 2285 | this.transforms = []; 2286 | this.geometries = []; 2287 | this.cameras = []; 2288 | this.lights = []; 2289 | this.controllers = []; 2290 | this.matrix = new THREE.Matrix4(); 2291 | 2292 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2293 | 2294 | var child = element.childNodes[ i ]; 2295 | if ( child.nodeType != 1 ) continue; 2296 | 2297 | switch ( child.nodeName ) { 2298 | 2299 | case 'node': 2300 | 2301 | this.nodes.push( ( new Node() ).parse( child ) ); 2302 | break; 2303 | 2304 | case 'instance_camera': 2305 | 2306 | this.cameras.push( ( new InstanceCamera() ).parse( child ) ); 2307 | break; 2308 | 2309 | case 'instance_controller': 2310 | 2311 | this.controllers.push( ( new InstanceController() ).parse( child ) ); 2312 | break; 2313 | 2314 | case 'instance_geometry': 2315 | 2316 | this.geometries.push( ( new InstanceGeometry() ).parse( child ) ); 2317 | break; 2318 | 2319 | case 'instance_light': 2320 | 2321 | this.lights.push( ( new InstanceLight() ).parse( child ) ); 2322 | break; 2323 | 2324 | case 'instance_node': 2325 | 2326 | url = child.getAttribute( 'url' ).replace( /^#/, '' ); 2327 | var iNode = getLibraryNode( url ); 2328 | 2329 | if ( iNode ) { 2330 | 2331 | this.nodes.push( ( new Node() ).parse( iNode )) ; 2332 | 2333 | } 2334 | 2335 | break; 2336 | 2337 | case 'rotate': 2338 | case 'translate': 2339 | case 'scale': 2340 | case 'matrix': 2341 | case 'lookat': 2342 | case 'skew': 2343 | 2344 | this.transforms.push( ( new Transform() ).parse( child ) ); 2345 | break; 2346 | 2347 | case 'extra': 2348 | break; 2349 | 2350 | default: 2351 | 2352 | console.log( child.nodeName ); 2353 | break; 2354 | 2355 | } 2356 | 2357 | } 2358 | 2359 | this.channels = getChannelsForNode( this ); 2360 | bakeAnimations( this ); 2361 | 2362 | this.updateMatrix(); 2363 | 2364 | return this; 2365 | 2366 | }; 2367 | 2368 | Node.prototype.updateMatrix = function () { 2369 | 2370 | this.matrix.identity(); 2371 | 2372 | for ( var i = 0; i < this.transforms.length; i ++ ) { 2373 | 2374 | this.transforms[ i ].apply( this.matrix ); 2375 | 2376 | } 2377 | 2378 | }; 2379 | 2380 | function Transform () { 2381 | 2382 | this.sid = ""; 2383 | this.type = ""; 2384 | this.data = []; 2385 | this.obj = null; 2386 | 2387 | } 2388 | 2389 | Transform.prototype.parse = function ( element ) { 2390 | 2391 | this.sid = element.getAttribute( 'sid' ); 2392 | this.type = element.nodeName; 2393 | this.data = _floats( element.textContent ); 2394 | this.convert(); 2395 | 2396 | return this; 2397 | 2398 | }; 2399 | 2400 | Transform.prototype.convert = function () { 2401 | 2402 | switch ( this.type ) { 2403 | 2404 | case 'matrix': 2405 | 2406 | this.obj = getConvertedMat4( this.data ); 2407 | break; 2408 | 2409 | case 'rotate': 2410 | 2411 | this.angle = THREE.Math.degToRad( this.data[3] ); 2412 | 2413 | case 'translate': 2414 | 2415 | fixCoords( this.data, -1 ); 2416 | this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); 2417 | break; 2418 | 2419 | case 'scale': 2420 | 2421 | fixCoords( this.data, 1 ); 2422 | this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); 2423 | break; 2424 | 2425 | default: 2426 | console.log( 'Can not convert Transform of type ' + this.type ); 2427 | break; 2428 | 2429 | } 2430 | 2431 | }; 2432 | 2433 | Transform.prototype.apply = function () { 2434 | 2435 | var m1 = new THREE.Matrix4(); 2436 | 2437 | return function ( matrix ) { 2438 | 2439 | switch ( this.type ) { 2440 | 2441 | case 'matrix': 2442 | 2443 | matrix.multiply( this.obj ); 2444 | 2445 | break; 2446 | 2447 | case 'translate': 2448 | 2449 | matrix.multiply( m1.makeTranslation( this.obj.x, this.obj.y, this.obj.z ) ); 2450 | 2451 | break; 2452 | 2453 | case 'rotate': 2454 | 2455 | matrix.multiply( m1.makeRotationAxis( this.obj, this.angle ) ); 2456 | 2457 | break; 2458 | 2459 | case 'scale': 2460 | 2461 | matrix.scale( this.obj ); 2462 | 2463 | break; 2464 | 2465 | } 2466 | 2467 | }; 2468 | 2469 | }(); 2470 | 2471 | Transform.prototype.update = function ( data, member ) { 2472 | 2473 | var members = [ 'X', 'Y', 'Z', 'ANGLE' ]; 2474 | 2475 | switch ( this.type ) { 2476 | 2477 | case 'matrix': 2478 | 2479 | if ( ! member ) { 2480 | 2481 | this.obj.copy( data ); 2482 | 2483 | } else if ( member.length === 1 ) { 2484 | 2485 | switch ( member[ 0 ] ) { 2486 | 2487 | case 0: 2488 | 2489 | this.obj.n11 = data[ 0 ]; 2490 | this.obj.n21 = data[ 1 ]; 2491 | this.obj.n31 = data[ 2 ]; 2492 | this.obj.n41 = data[ 3 ]; 2493 | 2494 | break; 2495 | 2496 | case 1: 2497 | 2498 | this.obj.n12 = data[ 0 ]; 2499 | this.obj.n22 = data[ 1 ]; 2500 | this.obj.n32 = data[ 2 ]; 2501 | this.obj.n42 = data[ 3 ]; 2502 | 2503 | break; 2504 | 2505 | case 2: 2506 | 2507 | this.obj.n13 = data[ 0 ]; 2508 | this.obj.n23 = data[ 1 ]; 2509 | this.obj.n33 = data[ 2 ]; 2510 | this.obj.n43 = data[ 3 ]; 2511 | 2512 | break; 2513 | 2514 | case 3: 2515 | 2516 | this.obj.n14 = data[ 0 ]; 2517 | this.obj.n24 = data[ 1 ]; 2518 | this.obj.n34 = data[ 2 ]; 2519 | this.obj.n44 = data[ 3 ]; 2520 | 2521 | break; 2522 | 2523 | } 2524 | 2525 | } else if ( member.length === 2 ) { 2526 | 2527 | var propName = 'n' + ( member[ 0 ] + 1 ) + ( member[ 1 ] + 1 ); 2528 | this.obj[ propName ] = data; 2529 | 2530 | } else { 2531 | 2532 | console.log('Incorrect addressing of matrix in transform.'); 2533 | 2534 | } 2535 | 2536 | break; 2537 | 2538 | case 'translate': 2539 | case 'scale': 2540 | 2541 | if ( Object.prototype.toString.call( member ) === '[object Array]' ) { 2542 | 2543 | member = members[ member[ 0 ] ]; 2544 | 2545 | } 2546 | 2547 | switch ( member ) { 2548 | 2549 | case 'X': 2550 | 2551 | this.obj.x = data; 2552 | break; 2553 | 2554 | case 'Y': 2555 | 2556 | this.obj.y = data; 2557 | break; 2558 | 2559 | case 'Z': 2560 | 2561 | this.obj.z = data; 2562 | break; 2563 | 2564 | default: 2565 | 2566 | this.obj.x = data[ 0 ]; 2567 | this.obj.y = data[ 1 ]; 2568 | this.obj.z = data[ 2 ]; 2569 | break; 2570 | 2571 | } 2572 | 2573 | break; 2574 | 2575 | case 'rotate': 2576 | 2577 | if ( Object.prototype.toString.call( member ) === '[object Array]' ) { 2578 | 2579 | member = members[ member[ 0 ] ]; 2580 | 2581 | } 2582 | 2583 | switch ( member ) { 2584 | 2585 | case 'X': 2586 | 2587 | this.obj.x = data; 2588 | break; 2589 | 2590 | case 'Y': 2591 | 2592 | this.obj.y = data; 2593 | break; 2594 | 2595 | case 'Z': 2596 | 2597 | this.obj.z = data; 2598 | break; 2599 | 2600 | case 'ANGLE': 2601 | 2602 | this.angle = THREE.Math.degToRad( data ); 2603 | break; 2604 | 2605 | default: 2606 | 2607 | this.obj.x = data[ 0 ]; 2608 | this.obj.y = data[ 1 ]; 2609 | this.obj.z = data[ 2 ]; 2610 | this.angle = THREE.Math.degToRad( data[ 3 ] ); 2611 | break; 2612 | 2613 | } 2614 | break; 2615 | 2616 | } 2617 | 2618 | }; 2619 | 2620 | function InstanceController() { 2621 | 2622 | this.url = ""; 2623 | this.skeleton = []; 2624 | this.instance_material = []; 2625 | 2626 | } 2627 | 2628 | InstanceController.prototype.parse = function ( element ) { 2629 | 2630 | this.url = element.getAttribute('url').replace(/^#/, ''); 2631 | this.skeleton = []; 2632 | this.instance_material = []; 2633 | 2634 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2635 | 2636 | var child = element.childNodes[ i ]; 2637 | if ( child.nodeType !== 1 ) continue; 2638 | 2639 | switch ( child.nodeName ) { 2640 | 2641 | case 'skeleton': 2642 | 2643 | this.skeleton.push( child.textContent.replace(/^#/, '') ); 2644 | break; 2645 | 2646 | case 'bind_material': 2647 | 2648 | var instances = child.querySelectorAll('instance_material'); 2649 | 2650 | for ( var j = 0; j < instances.length; j ++ ) { 2651 | 2652 | var instance = instances[j]; 2653 | this.instance_material.push( (new InstanceMaterial()).parse(instance) ); 2654 | 2655 | } 2656 | 2657 | 2658 | break; 2659 | 2660 | case 'extra': 2661 | break; 2662 | 2663 | default: 2664 | break; 2665 | 2666 | } 2667 | } 2668 | 2669 | return this; 2670 | 2671 | }; 2672 | 2673 | function InstanceMaterial () { 2674 | 2675 | this.symbol = ""; 2676 | this.target = ""; 2677 | 2678 | } 2679 | 2680 | InstanceMaterial.prototype.parse = function ( element ) { 2681 | 2682 | this.symbol = element.getAttribute('symbol'); 2683 | this.target = element.getAttribute('target').replace(/^#/, ''); 2684 | return this; 2685 | 2686 | }; 2687 | 2688 | function InstanceGeometry() { 2689 | 2690 | this.url = ""; 2691 | this.instance_material = []; 2692 | 2693 | } 2694 | 2695 | InstanceGeometry.prototype.parse = function ( element ) { 2696 | 2697 | this.url = element.getAttribute('url').replace(/^#/, ''); 2698 | this.instance_material = []; 2699 | 2700 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2701 | 2702 | var child = element.childNodes[i]; 2703 | if ( child.nodeType != 1 ) continue; 2704 | 2705 | if ( child.nodeName === 'bind_material' ) { 2706 | 2707 | var instances = child.querySelectorAll('instance_material'); 2708 | 2709 | for ( var j = 0; j < instances.length; j ++ ) { 2710 | 2711 | var instance = instances[j]; 2712 | this.instance_material.push( (new InstanceMaterial()).parse(instance) ); 2713 | 2714 | } 2715 | 2716 | break; 2717 | 2718 | } 2719 | 2720 | } 2721 | 2722 | return this; 2723 | 2724 | }; 2725 | 2726 | function Geometry() { 2727 | 2728 | this.id = ""; 2729 | this.mesh = null; 2730 | 2731 | } 2732 | 2733 | Geometry.prototype.parse = function ( element ) { 2734 | 2735 | this.id = element.getAttribute('id'); 2736 | 2737 | extractDoubleSided( this, element ); 2738 | 2739 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2740 | 2741 | var child = element.childNodes[i]; 2742 | 2743 | switch ( child.nodeName ) { 2744 | 2745 | case 'mesh': 2746 | 2747 | this.mesh = (new Mesh(this)).parse(child); 2748 | break; 2749 | 2750 | case 'extra': 2751 | 2752 | // console.log( child ); 2753 | break; 2754 | 2755 | default: 2756 | break; 2757 | } 2758 | } 2759 | 2760 | return this; 2761 | 2762 | }; 2763 | 2764 | function Mesh( geometry ) { 2765 | 2766 | this.geometry = geometry.id; 2767 | this.primitives = []; 2768 | this.vertices = null; 2769 | this.geometry3js = null; 2770 | 2771 | } 2772 | 2773 | Mesh.prototype.parse = function ( element ) { 2774 | 2775 | this.primitives = []; 2776 | 2777 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2778 | 2779 | var child = element.childNodes[ i ]; 2780 | 2781 | switch ( child.nodeName ) { 2782 | 2783 | case 'source': 2784 | 2785 | _source( child ); 2786 | break; 2787 | 2788 | case 'vertices': 2789 | 2790 | this.vertices = ( new Vertices() ).parse( child ); 2791 | break; 2792 | 2793 | case 'linestrips': 2794 | 2795 | this.primitives.push( ( new LineStrips().parse( child ) ) ); 2796 | break; 2797 | 2798 | case 'triangles': 2799 | 2800 | this.primitives.push( ( new Triangles().parse( child ) ) ); 2801 | break; 2802 | 2803 | case 'polygons': 2804 | 2805 | this.primitives.push( ( new Polygons().parse( child ) ) ); 2806 | break; 2807 | 2808 | case 'polylist': 2809 | 2810 | this.primitives.push( ( new Polylist().parse( child ) ) ); 2811 | break; 2812 | 2813 | default: 2814 | break; 2815 | 2816 | } 2817 | 2818 | } 2819 | 2820 | this.geometry3js = new THREE.Geometry(); 2821 | 2822 | if ( this.vertices === null ) { 2823 | 2824 | // TODO (mrdoob): Study case when this is null (carrier.dae) 2825 | 2826 | return this; 2827 | 2828 | } 2829 | 2830 | var vertexData = sources[ this.vertices.input['POSITION'].source ].data; 2831 | 2832 | for ( var i = 0; i < vertexData.length; i += 3 ) { 2833 | 2834 | this.geometry3js.vertices.push( getConvertedVec3( vertexData, i ).clone() ); 2835 | 2836 | } 2837 | 2838 | for ( var i = 0; i < this.primitives.length; i ++ ) { 2839 | 2840 | var primitive = this.primitives[ i ]; 2841 | primitive.setVertices( this.vertices ); 2842 | this.handlePrimitive( primitive, this.geometry3js ); 2843 | 2844 | } 2845 | 2846 | if ( this.geometry3js.calcNormals ) { 2847 | 2848 | this.geometry3js.computeVertexNormals(); 2849 | delete this.geometry3js.calcNormals; 2850 | 2851 | } 2852 | 2853 | return this; 2854 | 2855 | }; 2856 | 2857 | Mesh.prototype.handlePrimitive = function ( primitive, geom ) { 2858 | 2859 | if ( primitive instanceof LineStrips ) { 2860 | 2861 | // TODO: Handle indices. Maybe easier with BufferGeometry? 2862 | 2863 | geom.isLineStrip = true; 2864 | return; 2865 | 2866 | } 2867 | 2868 | var j, k, pList = primitive.p, inputs = primitive.inputs; 2869 | var input, index, idx32; 2870 | var source, numParams; 2871 | var vcIndex = 0, vcount = 3, maxOffset = 0; 2872 | var texture_sets = []; 2873 | 2874 | for ( j = 0; j < inputs.length; j ++ ) { 2875 | 2876 | input = inputs[ j ]; 2877 | 2878 | var offset = input.offset + 1; 2879 | maxOffset = (maxOffset < offset) ? offset : maxOffset; 2880 | 2881 | switch ( input.semantic ) { 2882 | 2883 | case 'TEXCOORD': 2884 | texture_sets.push( input.set ); 2885 | break; 2886 | 2887 | } 2888 | 2889 | } 2890 | 2891 | for ( var pCount = 0; pCount < pList.length; ++ pCount ) { 2892 | 2893 | var p = pList[ pCount ], i = 0; 2894 | 2895 | while ( i < p.length ) { 2896 | 2897 | var vs = []; 2898 | var ns = []; 2899 | var ts = null; 2900 | var cs = []; 2901 | 2902 | if ( primitive.vcount ) { 2903 | 2904 | vcount = primitive.vcount.length ? primitive.vcount[ vcIndex ++ ] : primitive.vcount; 2905 | 2906 | } else { 2907 | 2908 | vcount = p.length / maxOffset; 2909 | 2910 | } 2911 | 2912 | 2913 | for ( j = 0; j < vcount; j ++ ) { 2914 | 2915 | for ( k = 0; k < inputs.length; k ++ ) { 2916 | 2917 | input = inputs[ k ]; 2918 | source = sources[ input.source ]; 2919 | 2920 | index = p[ i + ( j * maxOffset ) + input.offset ]; 2921 | numParams = source.accessor.params.length; 2922 | idx32 = index * numParams; 2923 | 2924 | switch ( input.semantic ) { 2925 | 2926 | case 'VERTEX': 2927 | 2928 | vs.push( index ); 2929 | 2930 | break; 2931 | 2932 | case 'NORMAL': 2933 | 2934 | ns.push( getConvertedVec3( source.data, idx32 ) ); 2935 | 2936 | break; 2937 | 2938 | case 'TEXCOORD': 2939 | 2940 | ts = ts || { }; 2941 | if ( ts[ input.set ] === undefined ) ts[ input.set ] = []; 2942 | // invert the V 2943 | ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], source.data[ idx32 + 1 ] ) ); 2944 | 2945 | break; 2946 | 2947 | case 'COLOR': 2948 | 2949 | cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); 2950 | 2951 | break; 2952 | 2953 | default: 2954 | 2955 | break; 2956 | 2957 | } 2958 | 2959 | } 2960 | 2961 | } 2962 | 2963 | if ( ns.length === 0 ) { 2964 | 2965 | // check the vertices inputs 2966 | input = this.vertices.input.NORMAL; 2967 | 2968 | if ( input ) { 2969 | 2970 | source = sources[ input.source ]; 2971 | numParams = source.accessor.params.length; 2972 | 2973 | for ( var ndx = 0, len = vs.length; ndx < len; ndx ++ ) { 2974 | 2975 | ns.push( getConvertedVec3( source.data, vs[ ndx ] * numParams ) ); 2976 | 2977 | } 2978 | 2979 | } else { 2980 | 2981 | geom.calcNormals = true; 2982 | 2983 | } 2984 | 2985 | } 2986 | 2987 | if ( !ts ) { 2988 | 2989 | ts = { }; 2990 | // check the vertices inputs 2991 | input = this.vertices.input.TEXCOORD; 2992 | 2993 | if ( input ) { 2994 | 2995 | texture_sets.push( input.set ); 2996 | source = sources[ input.source ]; 2997 | numParams = source.accessor.params.length; 2998 | 2999 | for ( var ndx = 0, len = vs.length; ndx < len; ndx ++ ) { 3000 | 3001 | idx32 = vs[ ndx ] * numParams; 3002 | if ( ts[ input.set ] === undefined ) ts[ input.set ] = [ ]; 3003 | // invert the V 3004 | ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], 1.0 - source.data[ idx32 + 1 ] ) ); 3005 | 3006 | } 3007 | 3008 | } 3009 | 3010 | } 3011 | 3012 | if ( cs.length === 0 ) { 3013 | 3014 | // check the vertices inputs 3015 | input = this.vertices.input.COLOR; 3016 | 3017 | if ( input ) { 3018 | 3019 | source = sources[ input.source ]; 3020 | numParams = source.accessor.params.length; 3021 | 3022 | for ( var ndx = 0, len = vs.length; ndx < len; ndx ++ ) { 3023 | 3024 | idx32 = vs[ ndx ] * numParams; 3025 | cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); 3026 | 3027 | } 3028 | 3029 | } 3030 | 3031 | } 3032 | 3033 | var face = null, faces = [], uv, uvArr; 3034 | 3035 | if ( vcount === 3 ) { 3036 | 3037 | faces.push( new THREE.Face3( vs[0], vs[1], vs[2], ns, cs.length ? cs : new THREE.Color() ) ); 3038 | 3039 | } else if ( vcount === 4 ) { 3040 | 3041 | faces.push( new THREE.Face3( vs[0], vs[1], vs[3], ns.length ? [ ns[0].clone(), ns[1].clone(), ns[3].clone() ] : [], cs.length ? [ cs[0], cs[1], cs[3] ] : new THREE.Color() ) ); 3042 | 3043 | faces.push( new THREE.Face3( vs[1], vs[2], vs[3], ns.length ? [ ns[1].clone(), ns[2].clone(), ns[3].clone() ] : [], cs.length ? [ cs[1], cs[2], cs[3] ] : new THREE.Color() ) ); 3044 | 3045 | } else if ( vcount > 4 && options.subdivideFaces ) { 3046 | 3047 | var clr = cs.length ? cs : new THREE.Color(), 3048 | vec1, vec2, vec3, v1, v2, norm; 3049 | 3050 | // subdivide into multiple Face3s 3051 | 3052 | for ( k = 1; k < vcount - 1; ) { 3053 | 3054 | faces.push( new THREE.Face3( vs[0], vs[k], vs[k + 1], ns.length ? [ ns[0].clone(), ns[k ++].clone(), ns[k].clone() ] : [], clr ) ); 3055 | 3056 | } 3057 | 3058 | } 3059 | 3060 | if ( faces.length ) { 3061 | 3062 | for ( var ndx = 0, len = faces.length; ndx < len; ndx ++ ) { 3063 | 3064 | face = faces[ndx]; 3065 | face.daeMaterial = primitive.material; 3066 | geom.faces.push( face ); 3067 | 3068 | for ( k = 0; k < texture_sets.length; k ++ ) { 3069 | 3070 | uv = ts[ texture_sets[k] ]; 3071 | 3072 | if ( vcount > 4 ) { 3073 | 3074 | // Grab the right UVs for the vertices in this face 3075 | uvArr = [ uv[0], uv[ndx + 1], uv[ndx + 2] ]; 3076 | 3077 | } else if ( vcount === 4 ) { 3078 | 3079 | if ( ndx === 0 ) { 3080 | 3081 | uvArr = [ uv[0], uv[1], uv[3] ]; 3082 | 3083 | } else { 3084 | 3085 | uvArr = [ uv[1].clone(), uv[2], uv[3].clone() ]; 3086 | 3087 | } 3088 | 3089 | } else { 3090 | 3091 | uvArr = [ uv[0], uv[1], uv[2] ]; 3092 | 3093 | } 3094 | 3095 | if ( geom.faceVertexUvs[k] === undefined ) { 3096 | 3097 | geom.faceVertexUvs[k] = []; 3098 | 3099 | } 3100 | 3101 | geom.faceVertexUvs[k].push( uvArr ); 3102 | 3103 | } 3104 | 3105 | } 3106 | 3107 | } else { 3108 | 3109 | console.log( 'dropped face with vcount ' + vcount + ' for geometry with id: ' + geom.id ); 3110 | 3111 | } 3112 | 3113 | i += maxOffset * vcount; 3114 | 3115 | } 3116 | 3117 | } 3118 | 3119 | }; 3120 | 3121 | function Polygons () { 3122 | 3123 | this.material = ""; 3124 | this.count = 0; 3125 | this.inputs = []; 3126 | this.vcount = null; 3127 | this.p = []; 3128 | this.geometry = new THREE.Geometry(); 3129 | 3130 | } 3131 | 3132 | Polygons.prototype.setVertices = function ( vertices ) { 3133 | 3134 | for ( var i = 0; i < this.inputs.length; i ++ ) { 3135 | 3136 | if ( this.inputs[ i ].source === vertices.id ) { 3137 | 3138 | this.inputs[ i ].source = vertices.input[ 'POSITION' ].source; 3139 | 3140 | } 3141 | 3142 | } 3143 | 3144 | }; 3145 | 3146 | Polygons.prototype.parse = function ( element ) { 3147 | 3148 | this.material = element.getAttribute( 'material' ); 3149 | this.count = _attr_as_int( element, 'count', 0 ); 3150 | 3151 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3152 | 3153 | var child = element.childNodes[ i ]; 3154 | 3155 | switch ( child.nodeName ) { 3156 | 3157 | case 'input': 3158 | 3159 | this.inputs.push( ( new Input() ).parse( element.childNodes[ i ] ) ); 3160 | break; 3161 | 3162 | case 'vcount': 3163 | 3164 | this.vcount = _ints( child.textContent ); 3165 | break; 3166 | 3167 | case 'p': 3168 | 3169 | this.p.push( _ints( child.textContent ) ); 3170 | break; 3171 | 3172 | case 'ph': 3173 | 3174 | console.warn( 'polygon holes not yet supported!' ); 3175 | break; 3176 | 3177 | default: 3178 | break; 3179 | 3180 | } 3181 | 3182 | } 3183 | 3184 | return this; 3185 | 3186 | }; 3187 | 3188 | function Polylist () { 3189 | 3190 | Polygons.call( this ); 3191 | 3192 | this.vcount = []; 3193 | 3194 | } 3195 | 3196 | Polylist.prototype = Object.create( Polygons.prototype ); 3197 | Polylist.prototype.constructor = Polylist; 3198 | 3199 | function LineStrips() { 3200 | 3201 | Polygons.call( this ); 3202 | 3203 | this.vcount = 1; 3204 | 3205 | } 3206 | 3207 | LineStrips.prototype = Object.create( Polygons.prototype ); 3208 | LineStrips.prototype.constructor = LineStrips; 3209 | 3210 | function Triangles () { 3211 | 3212 | Polygons.call( this ); 3213 | 3214 | this.vcount = 3; 3215 | 3216 | } 3217 | 3218 | Triangles.prototype = Object.create( Polygons.prototype ); 3219 | Triangles.prototype.constructor = Triangles; 3220 | 3221 | function Accessor() { 3222 | 3223 | this.source = ""; 3224 | this.count = 0; 3225 | this.stride = 0; 3226 | this.params = []; 3227 | 3228 | } 3229 | 3230 | Accessor.prototype.parse = function ( element ) { 3231 | 3232 | this.params = []; 3233 | this.source = element.getAttribute( 'source' ); 3234 | this.count = _attr_as_int( element, 'count', 0 ); 3235 | this.stride = _attr_as_int( element, 'stride', 0 ); 3236 | 3237 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3238 | 3239 | var child = element.childNodes[ i ]; 3240 | 3241 | if ( child.nodeName === 'param' ) { 3242 | 3243 | var param = {}; 3244 | param[ 'name' ] = child.getAttribute( 'name' ); 3245 | param[ 'type' ] = child.getAttribute( 'type' ); 3246 | this.params.push( param ); 3247 | 3248 | } 3249 | 3250 | } 3251 | 3252 | return this; 3253 | 3254 | }; 3255 | 3256 | function Vertices() { 3257 | 3258 | this.input = {}; 3259 | 3260 | } 3261 | 3262 | Vertices.prototype.parse = function ( element ) { 3263 | 3264 | this.id = element.getAttribute('id'); 3265 | 3266 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3267 | 3268 | if ( element.childNodes[i].nodeName === 'input' ) { 3269 | 3270 | var input = ( new Input() ).parse( element.childNodes[ i ] ); 3271 | this.input[ input.semantic ] = input; 3272 | 3273 | } 3274 | 3275 | } 3276 | 3277 | return this; 3278 | 3279 | }; 3280 | 3281 | function Input () { 3282 | 3283 | this.semantic = ""; 3284 | this.offset = 0; 3285 | this.source = ""; 3286 | this.set = 0; 3287 | 3288 | } 3289 | 3290 | Input.prototype.parse = function ( element ) { 3291 | 3292 | this.semantic = element.getAttribute('semantic'); 3293 | this.source = element.getAttribute('source').replace(/^#/, ''); 3294 | this.set = _attr_as_int(element, 'set', -1); 3295 | this.offset = _attr_as_int(element, 'offset', 0); 3296 | 3297 | if ( this.semantic === 'TEXCOORD' && this.set < 0 ) { 3298 | 3299 | this.set = 0; 3300 | 3301 | } 3302 | 3303 | return this; 3304 | 3305 | }; 3306 | 3307 | function Source ( id ) { 3308 | 3309 | this.id = id; 3310 | this.type = null; 3311 | 3312 | } 3313 | 3314 | Source.prototype.parse = function ( element ) { 3315 | 3316 | this.id = element.getAttribute( 'id' ); 3317 | 3318 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3319 | 3320 | var child = element.childNodes[i]; 3321 | 3322 | switch ( child.nodeName ) { 3323 | 3324 | case 'bool_array': 3325 | 3326 | this.data = _bools( child.textContent ); 3327 | this.type = child.nodeName; 3328 | break; 3329 | 3330 | case 'float_array': 3331 | 3332 | this.data = _floats( child.textContent ); 3333 | this.type = child.nodeName; 3334 | break; 3335 | 3336 | case 'int_array': 3337 | 3338 | this.data = _ints( child.textContent ); 3339 | this.type = child.nodeName; 3340 | break; 3341 | 3342 | case 'IDREF_array': 3343 | case 'Name_array': 3344 | 3345 | this.data = _strings( child.textContent ); 3346 | this.type = child.nodeName; 3347 | break; 3348 | 3349 | case 'technique_common': 3350 | 3351 | for ( var j = 0; j < child.childNodes.length; j ++ ) { 3352 | 3353 | if ( child.childNodes[ j ].nodeName === 'accessor' ) { 3354 | 3355 | this.accessor = ( new Accessor() ).parse( child.childNodes[ j ] ); 3356 | break; 3357 | 3358 | } 3359 | } 3360 | break; 3361 | 3362 | default: 3363 | // console.log(child.nodeName); 3364 | break; 3365 | 3366 | } 3367 | 3368 | } 3369 | 3370 | return this; 3371 | 3372 | }; 3373 | 3374 | Source.prototype.read = function () { 3375 | 3376 | var result = []; 3377 | 3378 | //for (var i = 0; i < this.accessor.params.length; i++) { 3379 | 3380 | var param = this.accessor.params[ 0 ]; 3381 | 3382 | //console.log(param.name + " " + param.type); 3383 | 3384 | switch ( param.type ) { 3385 | 3386 | case 'IDREF': 3387 | case 'Name': case 'name': 3388 | case 'float': 3389 | 3390 | return this.data; 3391 | 3392 | case 'float4x4': 3393 | 3394 | for ( var j = 0; j < this.data.length; j += 16 ) { 3395 | 3396 | var s = this.data.slice( j, j + 16 ); 3397 | var m = getConvertedMat4( s ); 3398 | result.push( m ); 3399 | } 3400 | 3401 | break; 3402 | 3403 | default: 3404 | 3405 | console.log( 'ColladaLoader: Source: Read dont know how to read ' + param.type + '.' ); 3406 | break; 3407 | 3408 | } 3409 | 3410 | //} 3411 | 3412 | return result; 3413 | 3414 | }; 3415 | 3416 | function Material () { 3417 | 3418 | this.id = ""; 3419 | this.name = ""; 3420 | this.instance_effect = null; 3421 | 3422 | } 3423 | 3424 | Material.prototype.parse = function ( element ) { 3425 | 3426 | this.id = element.getAttribute( 'id' ); 3427 | this.name = element.getAttribute( 'name' ); 3428 | 3429 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3430 | 3431 | if ( element.childNodes[ i ].nodeName === 'instance_effect' ) { 3432 | 3433 | this.instance_effect = ( new InstanceEffect() ).parse( element.childNodes[ i ] ); 3434 | break; 3435 | 3436 | } 3437 | 3438 | } 3439 | 3440 | return this; 3441 | 3442 | }; 3443 | 3444 | function ColorOrTexture () { 3445 | 3446 | this.color = new THREE.Color(); 3447 | this.color.setRGB( Math.random(), Math.random(), Math.random() ); 3448 | this.color.a = 1.0; 3449 | 3450 | this.texture = null; 3451 | this.texcoord = null; 3452 | this.texOpts = null; 3453 | 3454 | } 3455 | 3456 | ColorOrTexture.prototype.isColor = function () { 3457 | 3458 | return ( this.texture === null ); 3459 | 3460 | }; 3461 | 3462 | ColorOrTexture.prototype.isTexture = function () { 3463 | 3464 | return ( this.texture != null ); 3465 | 3466 | }; 3467 | 3468 | ColorOrTexture.prototype.parse = function ( element ) { 3469 | 3470 | if (element.nodeName === 'transparent') { 3471 | 3472 | this.opaque = element.getAttribute('opaque'); 3473 | 3474 | } 3475 | 3476 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3477 | 3478 | var child = element.childNodes[ i ]; 3479 | if ( child.nodeType != 1 ) continue; 3480 | 3481 | switch ( child.nodeName ) { 3482 | 3483 | case 'color': 3484 | 3485 | var rgba = _floats( child.textContent ); 3486 | this.color = new THREE.Color(); 3487 | this.color.setRGB( rgba[0], rgba[1], rgba[2] ); 3488 | this.color.a = rgba[3]; 3489 | break; 3490 | 3491 | case 'texture': 3492 | 3493 | this.texture = child.getAttribute('texture'); 3494 | this.texcoord = child.getAttribute('texcoord'); 3495 | // Defaults from: 3496 | // https://collada.org/mediawiki/index.php/Maya_texture_placement_MAYA_extension 3497 | this.texOpts = { 3498 | offsetU: 0, 3499 | offsetV: 0, 3500 | repeatU: 1, 3501 | repeatV: 1, 3502 | wrapU: 1, 3503 | wrapV: 1 3504 | }; 3505 | this.parseTexture( child ); 3506 | break; 3507 | 3508 | default: 3509 | break; 3510 | 3511 | } 3512 | 3513 | } 3514 | 3515 | return this; 3516 | 3517 | }; 3518 | 3519 | ColorOrTexture.prototype.parseTexture = function ( element ) { 3520 | 3521 | if ( ! element.childNodes ) return this; 3522 | 3523 | // This should be supported by Maya, 3dsMax, and MotionBuilder 3524 | 3525 | if ( element.childNodes[1] && element.childNodes[1].nodeName === 'extra' ) { 3526 | 3527 | element = element.childNodes[1]; 3528 | 3529 | if ( element.childNodes[1] && element.childNodes[1].nodeName === 'technique' ) { 3530 | 3531 | element = element.childNodes[1]; 3532 | 3533 | } 3534 | 3535 | } 3536 | 3537 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3538 | 3539 | var child = element.childNodes[ i ]; 3540 | 3541 | switch ( child.nodeName ) { 3542 | 3543 | case 'offsetU': 3544 | case 'offsetV': 3545 | case 'repeatU': 3546 | case 'repeatV': 3547 | 3548 | this.texOpts[ child.nodeName ] = parseFloat( child.textContent ); 3549 | 3550 | break; 3551 | 3552 | case 'wrapU': 3553 | case 'wrapV': 3554 | 3555 | // some dae have a value of true which becomes NaN via parseInt 3556 | 3557 | if ( child.textContent.toUpperCase() === 'TRUE' ) { 3558 | 3559 | this.texOpts[ child.nodeName ] = 1; 3560 | 3561 | } else { 3562 | 3563 | this.texOpts[ child.nodeName ] = parseInt( child.textContent ); 3564 | 3565 | } 3566 | break; 3567 | 3568 | default: 3569 | 3570 | this.texOpts[ child.nodeName ] = child.textContent; 3571 | 3572 | break; 3573 | 3574 | } 3575 | 3576 | } 3577 | 3578 | return this; 3579 | 3580 | }; 3581 | 3582 | function Shader ( type, effect ) { 3583 | 3584 | this.type = type; 3585 | this.effect = effect; 3586 | this.material = null; 3587 | 3588 | } 3589 | 3590 | Shader.prototype.parse = function ( element ) { 3591 | 3592 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3593 | 3594 | var child = element.childNodes[ i ]; 3595 | if ( child.nodeType != 1 ) continue; 3596 | 3597 | switch ( child.nodeName ) { 3598 | 3599 | case 'emission': 3600 | case 'diffuse': 3601 | case 'specular': 3602 | case 'transparent': 3603 | 3604 | this[ child.nodeName ] = ( new ColorOrTexture() ).parse( child ); 3605 | break; 3606 | 3607 | case 'bump': 3608 | 3609 | // If 'bumptype' is 'heightfield', create a 'bump' property 3610 | // Else if 'bumptype' is 'normalmap', create a 'normal' property 3611 | // (Default to 'bump') 3612 | var bumpType = child.getAttribute( 'bumptype' ); 3613 | if ( bumpType ) { 3614 | if ( bumpType.toLowerCase() === "heightfield" ) { 3615 | this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); 3616 | } else if ( bumpType.toLowerCase() === "normalmap" ) { 3617 | this[ 'normal' ] = ( new ColorOrTexture() ).parse( child ); 3618 | } else { 3619 | console.error( "Shader.prototype.parse: Invalid value for attribute 'bumptype' (" + bumpType + ") - valid bumptypes are 'HEIGHTFIELD' and 'NORMALMAP' - defaulting to 'HEIGHTFIELD'" ); 3620 | this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); 3621 | } 3622 | } else { 3623 | console.warn( "Shader.prototype.parse: Attribute 'bumptype' missing from bump node - defaulting to 'HEIGHTFIELD'" ); 3624 | this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); 3625 | } 3626 | 3627 | break; 3628 | 3629 | case 'shininess': 3630 | case 'reflectivity': 3631 | case 'index_of_refraction': 3632 | case 'transparency': 3633 | 3634 | var f = child.querySelectorAll('float'); 3635 | 3636 | if ( f.length > 0 ) 3637 | this[ child.nodeName ] = parseFloat( f[ 0 ].textContent ); 3638 | 3639 | break; 3640 | 3641 | default: 3642 | break; 3643 | 3644 | } 3645 | 3646 | } 3647 | 3648 | this.create(); 3649 | return this; 3650 | 3651 | }; 3652 | 3653 | Shader.prototype.create = function() { 3654 | 3655 | var props = {}; 3656 | 3657 | var transparent = false; 3658 | 3659 | if (this['transparency'] !== undefined && this['transparent'] !== undefined) { 3660 | // convert transparent color RBG to average value 3661 | var transparentColor = this['transparent']; 3662 | var transparencyLevel = (this.transparent.color.r + this.transparent.color.g + this.transparent.color.b) / 3 * this.transparency; 3663 | 3664 | if (transparencyLevel > 0) { 3665 | transparent = true; 3666 | props[ 'transparent' ] = true; 3667 | props[ 'opacity' ] = 1 - transparencyLevel; 3668 | 3669 | } 3670 | 3671 | } 3672 | 3673 | var keys = { 3674 | 'diffuse':'map', 3675 | 'ambient':'lightMap', 3676 | 'specular':'specularMap', 3677 | 'emission':'emissionMap', 3678 | 'bump':'bumpMap', 3679 | 'normal':'normalMap' 3680 | }; 3681 | 3682 | for ( var prop in this ) { 3683 | 3684 | switch ( prop ) { 3685 | 3686 | case 'ambient': 3687 | case 'emission': 3688 | case 'diffuse': 3689 | case 'specular': 3690 | case 'bump': 3691 | case 'normal': 3692 | 3693 | var cot = this[ prop ]; 3694 | 3695 | if ( cot instanceof ColorOrTexture ) { 3696 | 3697 | if ( cot.isTexture() ) { 3698 | 3699 | var samplerId = cot.texture; 3700 | var surfaceId = this.effect.sampler[samplerId]; 3701 | 3702 | if ( surfaceId !== undefined && surfaceId.source !== undefined ) { 3703 | 3704 | var surface = this.effect.surface[surfaceId.source]; 3705 | 3706 | if ( surface !== undefined ) { 3707 | 3708 | var image = images[ surface.init_from ]; 3709 | 3710 | if ( image ) { 3711 | 3712 | var url = baseUrl + image.init_from; 3713 | 3714 | var texture; 3715 | var loader = THREE.Loader.Handlers.get( url ); 3716 | 3717 | if ( loader !== null ) { 3718 | 3719 | texture = loader.load( url ); 3720 | 3721 | } else { 3722 | 3723 | texture = new THREE.Texture(); 3724 | 3725 | loadTextureImage( texture, url ); 3726 | 3727 | } 3728 | 3729 | texture.wrapS = cot.texOpts.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; 3730 | texture.wrapT = cot.texOpts.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; 3731 | texture.offset.x = cot.texOpts.offsetU; 3732 | texture.offset.y = cot.texOpts.offsetV; 3733 | texture.repeat.x = cot.texOpts.repeatU; 3734 | texture.repeat.y = cot.texOpts.repeatV; 3735 | props[keys[prop]] = texture; 3736 | 3737 | // Texture with baked lighting? 3738 | if (prop === 'emission') props['emissive'] = 0xffffff; 3739 | 3740 | } 3741 | 3742 | } 3743 | 3744 | } 3745 | 3746 | } else if ( prop === 'diffuse' || !transparent ) { 3747 | 3748 | if ( prop === 'emission' ) { 3749 | 3750 | props[ 'emissive' ] = cot.color.getHex(); 3751 | 3752 | } else { 3753 | 3754 | props[ prop ] = cot.color.getHex(); 3755 | 3756 | } 3757 | 3758 | } 3759 | 3760 | } 3761 | 3762 | break; 3763 | 3764 | case 'shininess': 3765 | 3766 | props[ prop ] = this[ prop ]; 3767 | break; 3768 | 3769 | case 'reflectivity': 3770 | 3771 | props[ prop ] = this[ prop ]; 3772 | if ( props[ prop ] > 0.0 ) props['envMap'] = options.defaultEnvMap; 3773 | props['combine'] = THREE.MixOperation; //mix regular shading with reflective component 3774 | break; 3775 | 3776 | case 'index_of_refraction': 3777 | 3778 | props[ 'refractionRatio' ] = this[ prop ]; //TODO: "index_of_refraction" becomes "refractionRatio" in shader, but I'm not sure if the two are actually comparable 3779 | if ( this[ prop ] !== 1.0 ) props['envMap'] = options.defaultEnvMap; 3780 | break; 3781 | 3782 | case 'transparency': 3783 | // gets figured out up top 3784 | break; 3785 | 3786 | default: 3787 | break; 3788 | 3789 | } 3790 | 3791 | } 3792 | 3793 | //props[ 'shading' ] = preferredShading; 3794 | props[ 'side' ] = this.effect.doubleSided ? THREE.DoubleSide : THREE.FrontSide; 3795 | 3796 | if ( props.diffuse !== undefined ) { 3797 | 3798 | props.color = props.diffuse; 3799 | delete props.diffuse; 3800 | 3801 | } 3802 | 3803 | switch ( this.type ) { 3804 | 3805 | case 'constant': 3806 | 3807 | if (props.emissive != undefined) props.color = props.emissive; 3808 | this.material = new THREE.MeshBasicMaterial( props ); 3809 | break; 3810 | 3811 | case 'phong': 3812 | case 'blinn': 3813 | 3814 | this.material = new THREE.MeshPhongMaterial( props ); 3815 | break; 3816 | 3817 | case 'lambert': 3818 | default: 3819 | 3820 | this.material = new THREE.MeshLambertMaterial( props ); 3821 | break; 3822 | 3823 | } 3824 | 3825 | return this.material; 3826 | 3827 | }; 3828 | 3829 | function Surface ( effect ) { 3830 | 3831 | this.effect = effect; 3832 | this.init_from = null; 3833 | this.format = null; 3834 | 3835 | } 3836 | 3837 | Surface.prototype.parse = function ( element ) { 3838 | 3839 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3840 | 3841 | var child = element.childNodes[ i ]; 3842 | if ( child.nodeType != 1 ) continue; 3843 | 3844 | switch ( child.nodeName ) { 3845 | 3846 | case 'init_from': 3847 | 3848 | this.init_from = child.textContent; 3849 | break; 3850 | 3851 | case 'format': 3852 | 3853 | this.format = child.textContent; 3854 | break; 3855 | 3856 | default: 3857 | 3858 | console.log( "unhandled Surface prop: " + child.nodeName ); 3859 | break; 3860 | 3861 | } 3862 | 3863 | } 3864 | 3865 | return this; 3866 | 3867 | }; 3868 | 3869 | function Sampler2D ( effect ) { 3870 | 3871 | this.effect = effect; 3872 | this.source = null; 3873 | this.wrap_s = null; 3874 | this.wrap_t = null; 3875 | this.minfilter = null; 3876 | this.magfilter = null; 3877 | this.mipfilter = null; 3878 | 3879 | } 3880 | 3881 | Sampler2D.prototype.parse = function ( element ) { 3882 | 3883 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3884 | 3885 | var child = element.childNodes[ i ]; 3886 | if ( child.nodeType != 1 ) continue; 3887 | 3888 | switch ( child.nodeName ) { 3889 | 3890 | case 'source': 3891 | 3892 | this.source = child.textContent; 3893 | break; 3894 | 3895 | case 'minfilter': 3896 | 3897 | this.minfilter = child.textContent; 3898 | break; 3899 | 3900 | case 'magfilter': 3901 | 3902 | this.magfilter = child.textContent; 3903 | break; 3904 | 3905 | case 'mipfilter': 3906 | 3907 | this.mipfilter = child.textContent; 3908 | break; 3909 | 3910 | case 'wrap_s': 3911 | 3912 | this.wrap_s = child.textContent; 3913 | break; 3914 | 3915 | case 'wrap_t': 3916 | 3917 | this.wrap_t = child.textContent; 3918 | break; 3919 | 3920 | default: 3921 | 3922 | console.log( "unhandled Sampler2D prop: " + child.nodeName ); 3923 | break; 3924 | 3925 | } 3926 | 3927 | } 3928 | 3929 | return this; 3930 | 3931 | }; 3932 | 3933 | function Effect () { 3934 | 3935 | this.id = ""; 3936 | this.name = ""; 3937 | this.shader = null; 3938 | this.surface = {}; 3939 | this.sampler = {}; 3940 | 3941 | } 3942 | 3943 | Effect.prototype.create = function () { 3944 | 3945 | if ( this.shader === null ) { 3946 | 3947 | return null; 3948 | 3949 | } 3950 | 3951 | }; 3952 | 3953 | Effect.prototype.parse = function ( element ) { 3954 | 3955 | this.id = element.getAttribute( 'id' ); 3956 | this.name = element.getAttribute( 'name' ); 3957 | 3958 | extractDoubleSided( this, element ); 3959 | 3960 | this.shader = null; 3961 | 3962 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3963 | 3964 | var child = element.childNodes[ i ]; 3965 | if ( child.nodeType != 1 ) continue; 3966 | 3967 | switch ( child.nodeName ) { 3968 | 3969 | case 'profile_COMMON': 3970 | 3971 | this.parseTechnique( this.parseProfileCOMMON( child ) ); 3972 | break; 3973 | 3974 | default: 3975 | break; 3976 | 3977 | } 3978 | 3979 | } 3980 | 3981 | return this; 3982 | 3983 | }; 3984 | 3985 | Effect.prototype.parseNewparam = function ( element ) { 3986 | 3987 | var sid = element.getAttribute( 'sid' ); 3988 | 3989 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3990 | 3991 | var child = element.childNodes[ i ]; 3992 | if ( child.nodeType != 1 ) continue; 3993 | 3994 | switch ( child.nodeName ) { 3995 | 3996 | case 'surface': 3997 | 3998 | this.surface[sid] = ( new Surface( this ) ).parse( child ); 3999 | break; 4000 | 4001 | case 'sampler2D': 4002 | 4003 | this.sampler[sid] = ( new Sampler2D( this ) ).parse( child ); 4004 | break; 4005 | 4006 | case 'extra': 4007 | 4008 | break; 4009 | 4010 | default: 4011 | 4012 | console.log( child.nodeName ); 4013 | break; 4014 | 4015 | } 4016 | 4017 | } 4018 | 4019 | }; 4020 | 4021 | Effect.prototype.parseProfileCOMMON = function ( element ) { 4022 | 4023 | var technique; 4024 | 4025 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4026 | 4027 | var child = element.childNodes[ i ]; 4028 | 4029 | if ( child.nodeType != 1 ) continue; 4030 | 4031 | switch ( child.nodeName ) { 4032 | 4033 | case 'profile_COMMON': 4034 | 4035 | this.parseProfileCOMMON( child ); 4036 | break; 4037 | 4038 | case 'technique': 4039 | 4040 | technique = child; 4041 | break; 4042 | 4043 | case 'newparam': 4044 | 4045 | this.parseNewparam( child ); 4046 | break; 4047 | 4048 | case 'image': 4049 | 4050 | var _image = ( new _Image() ).parse( child ); 4051 | images[ _image.id ] = _image; 4052 | break; 4053 | 4054 | case 'extra': 4055 | break; 4056 | 4057 | default: 4058 | 4059 | console.log( child.nodeName ); 4060 | break; 4061 | 4062 | } 4063 | 4064 | } 4065 | 4066 | return technique; 4067 | 4068 | }; 4069 | 4070 | Effect.prototype.parseTechnique = function ( element ) { 4071 | 4072 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4073 | 4074 | var child = element.childNodes[i]; 4075 | if ( child.nodeType != 1 ) continue; 4076 | 4077 | switch ( child.nodeName ) { 4078 | 4079 | case 'constant': 4080 | case 'lambert': 4081 | case 'blinn': 4082 | case 'phong': 4083 | 4084 | this.shader = ( new Shader( child.nodeName, this ) ).parse( child ); 4085 | break; 4086 | case 'extra': 4087 | this.parseExtra(child); 4088 | break; 4089 | default: 4090 | break; 4091 | 4092 | } 4093 | 4094 | } 4095 | 4096 | }; 4097 | 4098 | Effect.prototype.parseExtra = function ( element ) { 4099 | 4100 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4101 | 4102 | var child = element.childNodes[i]; 4103 | if ( child.nodeType != 1 ) continue; 4104 | 4105 | switch ( child.nodeName ) { 4106 | 4107 | case 'technique': 4108 | this.parseExtraTechnique( child ); 4109 | break; 4110 | default: 4111 | break; 4112 | 4113 | } 4114 | 4115 | } 4116 | 4117 | }; 4118 | 4119 | Effect.prototype.parseExtraTechnique = function ( element ) { 4120 | 4121 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4122 | 4123 | var child = element.childNodes[i]; 4124 | if ( child.nodeType != 1 ) continue; 4125 | 4126 | switch ( child.nodeName ) { 4127 | 4128 | case 'bump': 4129 | this.shader.parse( element ); 4130 | break; 4131 | default: 4132 | break; 4133 | 4134 | } 4135 | 4136 | } 4137 | 4138 | }; 4139 | 4140 | function InstanceEffect () { 4141 | 4142 | this.url = ""; 4143 | 4144 | } 4145 | 4146 | InstanceEffect.prototype.parse = function ( element ) { 4147 | 4148 | this.url = element.getAttribute( 'url' ).replace( /^#/, '' ); 4149 | return this; 4150 | 4151 | }; 4152 | 4153 | function Animation() { 4154 | 4155 | this.id = ""; 4156 | this.name = ""; 4157 | this.source = {}; 4158 | this.sampler = []; 4159 | this.channel = []; 4160 | 4161 | } 4162 | 4163 | Animation.prototype.parse = function ( element ) { 4164 | 4165 | this.id = element.getAttribute( 'id' ); 4166 | this.name = element.getAttribute( 'name' ); 4167 | this.source = {}; 4168 | 4169 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4170 | 4171 | var child = element.childNodes[ i ]; 4172 | 4173 | if ( child.nodeType != 1 ) continue; 4174 | 4175 | switch ( child.nodeName ) { 4176 | 4177 | case 'animation': 4178 | 4179 | var anim = ( new Animation() ).parse( child ); 4180 | 4181 | for ( var src in anim.source ) { 4182 | 4183 | this.source[ src ] = anim.source[ src ]; 4184 | 4185 | } 4186 | 4187 | for ( var j = 0; j < anim.channel.length; j ++ ) { 4188 | 4189 | this.channel.push( anim.channel[ j ] ); 4190 | this.sampler.push( anim.sampler[ j ] ); 4191 | 4192 | } 4193 | 4194 | break; 4195 | 4196 | case 'source': 4197 | 4198 | var src = ( new Source() ).parse( child ); 4199 | this.source[ src.id ] = src; 4200 | break; 4201 | 4202 | case 'sampler': 4203 | 4204 | this.sampler.push( ( new Sampler( this ) ).parse( child ) ); 4205 | break; 4206 | 4207 | case 'channel': 4208 | 4209 | this.channel.push( ( new Channel( this ) ).parse( child ) ); 4210 | break; 4211 | 4212 | default: 4213 | break; 4214 | 4215 | } 4216 | 4217 | } 4218 | 4219 | return this; 4220 | 4221 | }; 4222 | 4223 | function Channel( animation ) { 4224 | 4225 | this.animation = animation; 4226 | this.source = ""; 4227 | this.target = ""; 4228 | this.fullSid = null; 4229 | this.sid = null; 4230 | this.dotSyntax = null; 4231 | this.arrSyntax = null; 4232 | this.arrIndices = null; 4233 | this.member = null; 4234 | 4235 | } 4236 | 4237 | Channel.prototype.parse = function ( element ) { 4238 | 4239 | this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); 4240 | this.target = element.getAttribute( 'target' ); 4241 | 4242 | var parts = this.target.split( '/' ); 4243 | 4244 | var id = parts.shift(); 4245 | var sid = parts.shift(); 4246 | 4247 | var dotSyntax = ( sid.indexOf(".") >= 0 ); 4248 | var arrSyntax = ( sid.indexOf("(") >= 0 ); 4249 | 4250 | if ( dotSyntax ) { 4251 | 4252 | parts = sid.split("."); 4253 | this.sid = parts.shift(); 4254 | this.member = parts.shift(); 4255 | 4256 | } else if ( arrSyntax ) { 4257 | 4258 | var arrIndices = sid.split("("); 4259 | this.sid = arrIndices.shift(); 4260 | 4261 | for (var j = 0; j < arrIndices.length; j ++ ) { 4262 | 4263 | arrIndices[j] = parseInt( arrIndices[j].replace(/\)/, '') ); 4264 | 4265 | } 4266 | 4267 | this.arrIndices = arrIndices; 4268 | 4269 | } else { 4270 | 4271 | this.sid = sid; 4272 | 4273 | } 4274 | 4275 | this.fullSid = sid; 4276 | this.dotSyntax = dotSyntax; 4277 | this.arrSyntax = arrSyntax; 4278 | 4279 | return this; 4280 | 4281 | }; 4282 | 4283 | function Sampler ( animation ) { 4284 | 4285 | this.id = ""; 4286 | this.animation = animation; 4287 | this.inputs = []; 4288 | this.input = null; 4289 | this.output = null; 4290 | this.strideOut = null; 4291 | this.interpolation = null; 4292 | this.startTime = null; 4293 | this.endTime = null; 4294 | this.duration = 0; 4295 | 4296 | } 4297 | 4298 | Sampler.prototype.parse = function ( element ) { 4299 | 4300 | this.id = element.getAttribute( 'id' ); 4301 | this.inputs = []; 4302 | 4303 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4304 | 4305 | var child = element.childNodes[ i ]; 4306 | if ( child.nodeType != 1 ) continue; 4307 | 4308 | switch ( child.nodeName ) { 4309 | 4310 | case 'input': 4311 | 4312 | this.inputs.push( (new Input()).parse( child ) ); 4313 | break; 4314 | 4315 | default: 4316 | break; 4317 | 4318 | } 4319 | 4320 | } 4321 | 4322 | return this; 4323 | 4324 | }; 4325 | 4326 | Sampler.prototype.create = function () { 4327 | 4328 | for ( var i = 0; i < this.inputs.length; i ++ ) { 4329 | 4330 | var input = this.inputs[ i ]; 4331 | var source = this.animation.source[ input.source ]; 4332 | 4333 | switch ( input.semantic ) { 4334 | 4335 | case 'INPUT': 4336 | 4337 | this.input = source.read(); 4338 | break; 4339 | 4340 | case 'OUTPUT': 4341 | 4342 | this.output = source.read(); 4343 | this.strideOut = source.accessor.stride; 4344 | break; 4345 | 4346 | case 'INTERPOLATION': 4347 | 4348 | this.interpolation = source.read(); 4349 | break; 4350 | 4351 | case 'IN_TANGENT': 4352 | 4353 | break; 4354 | 4355 | case 'OUT_TANGENT': 4356 | 4357 | break; 4358 | 4359 | default: 4360 | 4361 | console.log(input.semantic); 4362 | break; 4363 | 4364 | } 4365 | 4366 | } 4367 | 4368 | this.startTime = 0; 4369 | this.endTime = 0; 4370 | this.duration = 0; 4371 | 4372 | if ( this.input.length ) { 4373 | 4374 | this.startTime = 100000000; 4375 | this.endTime = -100000000; 4376 | 4377 | for ( var i = 0; i < this.input.length; i ++ ) { 4378 | 4379 | this.startTime = Math.min( this.startTime, this.input[ i ] ); 4380 | this.endTime = Math.max( this.endTime, this.input[ i ] ); 4381 | 4382 | } 4383 | 4384 | this.duration = this.endTime - this.startTime; 4385 | 4386 | } 4387 | 4388 | }; 4389 | 4390 | Sampler.prototype.getData = function ( type, ndx, member ) { 4391 | 4392 | var data; 4393 | 4394 | if ( type === 'matrix' && this.strideOut === 16 ) { 4395 | 4396 | data = this.output[ ndx ]; 4397 | 4398 | } else if ( this.strideOut > 1 ) { 4399 | 4400 | data = []; 4401 | ndx *= this.strideOut; 4402 | 4403 | for ( var i = 0; i < this.strideOut; ++ i ) { 4404 | 4405 | data[ i ] = this.output[ ndx + i ]; 4406 | 4407 | } 4408 | 4409 | if ( this.strideOut === 3 ) { 4410 | 4411 | switch ( type ) { 4412 | 4413 | case 'rotate': 4414 | case 'translate': 4415 | 4416 | fixCoords( data, -1 ); 4417 | break; 4418 | 4419 | case 'scale': 4420 | 4421 | fixCoords( data, 1 ); 4422 | break; 4423 | 4424 | } 4425 | 4426 | } else if ( this.strideOut === 4 && type === 'matrix' ) { 4427 | 4428 | fixCoords( data, -1 ); 4429 | 4430 | } 4431 | 4432 | } else { 4433 | 4434 | data = this.output[ ndx ]; 4435 | 4436 | if ( member && type === 'translate' ) { 4437 | data = getConvertedTranslation( member, data ); 4438 | } 4439 | 4440 | } 4441 | 4442 | return data; 4443 | 4444 | }; 4445 | 4446 | function Key ( time ) { 4447 | 4448 | this.targets = []; 4449 | this.time = time; 4450 | 4451 | } 4452 | 4453 | Key.prototype.addTarget = function ( fullSid, transform, member, data ) { 4454 | 4455 | this.targets.push( { 4456 | sid: fullSid, 4457 | member: member, 4458 | transform: transform, 4459 | data: data 4460 | } ); 4461 | 4462 | }; 4463 | 4464 | Key.prototype.apply = function ( opt_sid ) { 4465 | 4466 | for ( var i = 0; i < this.targets.length; ++ i ) { 4467 | 4468 | var target = this.targets[ i ]; 4469 | 4470 | if ( !opt_sid || target.sid === opt_sid ) { 4471 | 4472 | target.transform.update( target.data, target.member ); 4473 | 4474 | } 4475 | 4476 | } 4477 | 4478 | }; 4479 | 4480 | Key.prototype.getTarget = function ( fullSid ) { 4481 | 4482 | for ( var i = 0; i < this.targets.length; ++ i ) { 4483 | 4484 | if ( this.targets[ i ].sid === fullSid ) { 4485 | 4486 | return this.targets[ i ]; 4487 | 4488 | } 4489 | 4490 | } 4491 | 4492 | return null; 4493 | 4494 | }; 4495 | 4496 | Key.prototype.hasTarget = function ( fullSid ) { 4497 | 4498 | for ( var i = 0; i < this.targets.length; ++ i ) { 4499 | 4500 | if ( this.targets[ i ].sid === fullSid ) { 4501 | 4502 | return true; 4503 | 4504 | } 4505 | 4506 | } 4507 | 4508 | return false; 4509 | 4510 | }; 4511 | 4512 | // TODO: Currently only doing linear interpolation. Should support full COLLADA spec. 4513 | Key.prototype.interpolate = function ( nextKey, time ) { 4514 | 4515 | for ( var i = 0, l = this.targets.length; i < l; i ++ ) { 4516 | 4517 | var target = this.targets[ i ], 4518 | nextTarget = nextKey.getTarget( target.sid ), 4519 | data; 4520 | 4521 | if ( target.transform.type !== 'matrix' && nextTarget ) { 4522 | 4523 | var scale = ( time - this.time ) / ( nextKey.time - this.time ), 4524 | nextData = nextTarget.data, 4525 | prevData = target.data; 4526 | 4527 | if ( scale < 0 ) scale = 0; 4528 | if ( scale > 1 ) scale = 1; 4529 | 4530 | if ( prevData.length ) { 4531 | 4532 | data = []; 4533 | 4534 | for ( var j = 0; j < prevData.length; ++ j ) { 4535 | 4536 | data[ j ] = prevData[ j ] + ( nextData[ j ] - prevData[ j ] ) * scale; 4537 | 4538 | } 4539 | 4540 | } else { 4541 | 4542 | data = prevData + ( nextData - prevData ) * scale; 4543 | 4544 | } 4545 | 4546 | } else { 4547 | 4548 | data = target.data; 4549 | 4550 | } 4551 | 4552 | target.transform.update( data, target.member ); 4553 | 4554 | } 4555 | 4556 | }; 4557 | 4558 | // Camera 4559 | function Camera() { 4560 | 4561 | this.id = ""; 4562 | this.name = ""; 4563 | this.technique = ""; 4564 | 4565 | } 4566 | 4567 | Camera.prototype.parse = function ( element ) { 4568 | 4569 | this.id = element.getAttribute( 'id' ); 4570 | this.name = element.getAttribute( 'name' ); 4571 | 4572 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4573 | 4574 | var child = element.childNodes[ i ]; 4575 | if ( child.nodeType != 1 ) continue; 4576 | 4577 | switch ( child.nodeName ) { 4578 | 4579 | case 'optics': 4580 | 4581 | this.parseOptics( child ); 4582 | break; 4583 | 4584 | default: 4585 | break; 4586 | 4587 | } 4588 | 4589 | } 4590 | 4591 | return this; 4592 | 4593 | }; 4594 | 4595 | Camera.prototype.parseOptics = function ( element ) { 4596 | 4597 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4598 | 4599 | if ( element.childNodes[ i ].nodeName === 'technique_common' ) { 4600 | 4601 | var technique = element.childNodes[ i ]; 4602 | 4603 | for ( var j = 0; j < technique.childNodes.length; j ++ ) { 4604 | 4605 | this.technique = technique.childNodes[ j ].nodeName; 4606 | 4607 | if ( this.technique === 'perspective' ) { 4608 | 4609 | var perspective = technique.childNodes[ j ]; 4610 | 4611 | for ( var k = 0; k < perspective.childNodes.length; k ++ ) { 4612 | 4613 | var param = perspective.childNodes[ k ]; 4614 | 4615 | switch ( param.nodeName ) { 4616 | 4617 | case 'yfov': 4618 | this.yfov = param.textContent; 4619 | break; 4620 | case 'xfov': 4621 | this.xfov = param.textContent; 4622 | break; 4623 | case 'znear': 4624 | this.znear = param.textContent; 4625 | break; 4626 | case 'zfar': 4627 | this.zfar = param.textContent; 4628 | break; 4629 | case 'aspect_ratio': 4630 | this.aspect_ratio = param.textContent; 4631 | break; 4632 | 4633 | } 4634 | 4635 | } 4636 | 4637 | } else if ( this.technique === 'orthographic' ) { 4638 | 4639 | var orthographic = technique.childNodes[ j ]; 4640 | 4641 | for ( var k = 0; k < orthographic.childNodes.length; k ++ ) { 4642 | 4643 | var param = orthographic.childNodes[ k ]; 4644 | 4645 | switch ( param.nodeName ) { 4646 | 4647 | case 'xmag': 4648 | this.xmag = param.textContent; 4649 | break; 4650 | case 'ymag': 4651 | this.ymag = param.textContent; 4652 | break; 4653 | case 'znear': 4654 | this.znear = param.textContent; 4655 | break; 4656 | case 'zfar': 4657 | this.zfar = param.textContent; 4658 | break; 4659 | case 'aspect_ratio': 4660 | this.aspect_ratio = param.textContent; 4661 | break; 4662 | 4663 | } 4664 | 4665 | } 4666 | 4667 | } 4668 | 4669 | } 4670 | 4671 | } 4672 | 4673 | } 4674 | 4675 | return this; 4676 | 4677 | }; 4678 | 4679 | function InstanceCamera() { 4680 | 4681 | this.url = ""; 4682 | 4683 | } 4684 | 4685 | InstanceCamera.prototype.parse = function ( element ) { 4686 | 4687 | this.url = element.getAttribute('url').replace(/^#/, ''); 4688 | 4689 | return this; 4690 | 4691 | }; 4692 | 4693 | // Light 4694 | 4695 | function Light() { 4696 | 4697 | this.id = ""; 4698 | this.name = ""; 4699 | this.technique = ""; 4700 | 4701 | } 4702 | 4703 | Light.prototype.parse = function ( element ) { 4704 | 4705 | this.id = element.getAttribute( 'id' ); 4706 | this.name = element.getAttribute( 'name' ); 4707 | 4708 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4709 | 4710 | var child = element.childNodes[ i ]; 4711 | if ( child.nodeType != 1 ) continue; 4712 | 4713 | switch ( child.nodeName ) { 4714 | 4715 | case 'technique_common': 4716 | 4717 | this.parseCommon( child ); 4718 | break; 4719 | 4720 | case 'technique': 4721 | 4722 | this.parseTechnique( child ); 4723 | break; 4724 | 4725 | default: 4726 | break; 4727 | 4728 | } 4729 | 4730 | } 4731 | 4732 | return this; 4733 | 4734 | }; 4735 | 4736 | Light.prototype.parseCommon = function ( element ) { 4737 | 4738 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4739 | 4740 | switch ( element.childNodes[ i ].nodeName ) { 4741 | 4742 | case 'directional': 4743 | case 'point': 4744 | case 'spot': 4745 | case 'ambient': 4746 | 4747 | this.technique = element.childNodes[ i ].nodeName; 4748 | 4749 | var light = element.childNodes[ i ]; 4750 | 4751 | for ( var j = 0; j < light.childNodes.length; j ++ ) { 4752 | 4753 | var child = light.childNodes[j]; 4754 | 4755 | switch ( child.nodeName ) { 4756 | 4757 | case 'color': 4758 | 4759 | var rgba = _floats( child.textContent ); 4760 | this.color = new THREE.Color(0); 4761 | this.color.setRGB( rgba[0], rgba[1], rgba[2] ); 4762 | this.color.a = rgba[3]; 4763 | break; 4764 | 4765 | case 'falloff_angle': 4766 | 4767 | this.falloff_angle = parseFloat( child.textContent ); 4768 | break; 4769 | 4770 | case 'quadratic_attenuation': 4771 | var f = parseFloat( child.textContent ); 4772 | this.distance = f ? Math.sqrt( 1 / f ) : 0; 4773 | } 4774 | 4775 | } 4776 | 4777 | } 4778 | 4779 | } 4780 | 4781 | return this; 4782 | 4783 | }; 4784 | 4785 | Light.prototype.parseTechnique = function ( element ) { 4786 | 4787 | this.profile = element.getAttribute( 'profile' ); 4788 | 4789 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4790 | 4791 | var child = element.childNodes[ i ]; 4792 | 4793 | switch ( child.nodeName ) { 4794 | 4795 | case 'intensity': 4796 | 4797 | this.intensity = parseFloat(child.textContent); 4798 | break; 4799 | 4800 | } 4801 | 4802 | } 4803 | 4804 | return this; 4805 | 4806 | }; 4807 | 4808 | function InstanceLight() { 4809 | 4810 | this.url = ""; 4811 | 4812 | } 4813 | 4814 | InstanceLight.prototype.parse = function ( element ) { 4815 | 4816 | this.url = element.getAttribute('url').replace(/^#/, ''); 4817 | 4818 | return this; 4819 | 4820 | }; 4821 | 4822 | function KinematicsModel( ) { 4823 | 4824 | this.id = ''; 4825 | this.name = ''; 4826 | this.joints = []; 4827 | this.links = []; 4828 | 4829 | } 4830 | 4831 | KinematicsModel.prototype.parse = function( element ) { 4832 | 4833 | this.id = element.getAttribute('id'); 4834 | this.name = element.getAttribute('name'); 4835 | this.joints = []; 4836 | this.links = []; 4837 | 4838 | for (var i = 0; i < element.childNodes.length; i ++ ) { 4839 | 4840 | var child = element.childNodes[ i ]; 4841 | if ( child.nodeType != 1 ) continue; 4842 | 4843 | switch ( child.nodeName ) { 4844 | 4845 | case 'technique_common': 4846 | 4847 | this.parseCommon(child); 4848 | break; 4849 | 4850 | default: 4851 | break; 4852 | 4853 | } 4854 | 4855 | } 4856 | 4857 | return this; 4858 | 4859 | }; 4860 | 4861 | KinematicsModel.prototype.parseCommon = function( element ) { 4862 | 4863 | for (var i = 0; i < element.childNodes.length; i ++ ) { 4864 | 4865 | var child = element.childNodes[ i ]; 4866 | if ( child.nodeType != 1 ) continue; 4867 | 4868 | switch ( element.childNodes[ i ].nodeName ) { 4869 | 4870 | case 'joint': 4871 | this.joints.push( (new Joint()).parse(child) ); 4872 | break; 4873 | 4874 | case 'link': 4875 | this.links.push( (new Link()).parse(child) ); 4876 | break; 4877 | 4878 | default: 4879 | break; 4880 | 4881 | } 4882 | 4883 | } 4884 | 4885 | return this; 4886 | 4887 | }; 4888 | 4889 | function Joint( ) { 4890 | 4891 | this.sid = ''; 4892 | this.name = ''; 4893 | this.axis = new THREE.Vector3(); 4894 | this.limits = { 4895 | min: 0, 4896 | max: 0 4897 | }; 4898 | this.type = ''; 4899 | this.static = false; 4900 | this.zeroPosition = 0.0; 4901 | this.middlePosition = 0.0; 4902 | 4903 | } 4904 | 4905 | Joint.prototype.parse = function( element ) { 4906 | 4907 | this.sid = element.getAttribute('sid'); 4908 | this.name = element.getAttribute('name'); 4909 | this.axis = new THREE.Vector3(); 4910 | this.limits = { 4911 | min: 0, 4912 | max: 0 4913 | }; 4914 | this.type = ''; 4915 | this.static = false; 4916 | this.zeroPosition = 0.0; 4917 | this.middlePosition = 0.0; 4918 | 4919 | var axisElement = element.querySelector('axis'); 4920 | var _axis = _floats(axisElement.textContent); 4921 | this.axis = getConvertedVec3(_axis, 0); 4922 | 4923 | var min = element.querySelector('limits min') ? parseFloat(element.querySelector('limits min').textContent) : -360; 4924 | var max = element.querySelector('limits max') ? parseFloat(element.querySelector('limits max').textContent) : 360; 4925 | 4926 | this.limits = { 4927 | min: min, 4928 | max: max 4929 | }; 4930 | 4931 | var jointTypes = [ 'prismatic', 'revolute' ]; 4932 | for (var i = 0; i < jointTypes.length; i ++ ) { 4933 | 4934 | var type = jointTypes[ i ]; 4935 | 4936 | var jointElement = element.querySelector(type); 4937 | 4938 | if ( jointElement ) { 4939 | 4940 | this.type = type; 4941 | 4942 | } 4943 | 4944 | } 4945 | 4946 | // if the min is equal to or somehow greater than the max, consider the joint static 4947 | if ( this.limits.min >= this.limits.max ) { 4948 | 4949 | this.static = true; 4950 | 4951 | } 4952 | 4953 | this.middlePosition = (this.limits.min + this.limits.max) / 2.0; 4954 | return this; 4955 | 4956 | }; 4957 | 4958 | function Link( ) { 4959 | 4960 | this.sid = ''; 4961 | this.name = ''; 4962 | this.transforms = []; 4963 | this.attachments = []; 4964 | 4965 | } 4966 | 4967 | Link.prototype.parse = function( element ) { 4968 | 4969 | this.sid = element.getAttribute('sid'); 4970 | this.name = element.getAttribute('name'); 4971 | this.transforms = []; 4972 | this.attachments = []; 4973 | 4974 | for (var i = 0; i < element.childNodes.length; i ++ ) { 4975 | 4976 | var child = element.childNodes[ i ]; 4977 | if ( child.nodeType != 1 ) continue; 4978 | 4979 | switch ( child.nodeName ) { 4980 | 4981 | case 'attachment_full': 4982 | this.attachments.push( (new Attachment()).parse(child) ); 4983 | break; 4984 | 4985 | case 'rotate': 4986 | case 'translate': 4987 | case 'matrix': 4988 | 4989 | this.transforms.push( (new Transform()).parse(child) ); 4990 | break; 4991 | 4992 | default: 4993 | 4994 | break; 4995 | 4996 | } 4997 | 4998 | } 4999 | 5000 | return this; 5001 | 5002 | }; 5003 | 5004 | function Attachment( ) { 5005 | 5006 | this.joint = ''; 5007 | this.transforms = []; 5008 | this.links = []; 5009 | 5010 | } 5011 | 5012 | Attachment.prototype.parse = function( element ) { 5013 | 5014 | this.joint = element.getAttribute('joint').split('/').pop(); 5015 | this.links = []; 5016 | 5017 | for (var i = 0; i < element.childNodes.length; i ++ ) { 5018 | 5019 | var child = element.childNodes[ i ]; 5020 | if ( child.nodeType != 1 ) continue; 5021 | 5022 | switch ( child.nodeName ) { 5023 | 5024 | case 'link': 5025 | this.links.push( (new Link()).parse(child) ); 5026 | break; 5027 | 5028 | case 'rotate': 5029 | case 'translate': 5030 | case 'matrix': 5031 | 5032 | this.transforms.push( (new Transform()).parse(child) ); 5033 | break; 5034 | 5035 | default: 5036 | 5037 | break; 5038 | 5039 | } 5040 | 5041 | } 5042 | 5043 | return this; 5044 | 5045 | }; 5046 | 5047 | function _source( element ) { 5048 | 5049 | var id = element.getAttribute( 'id' ); 5050 | 5051 | if ( sources[ id ] != undefined ) { 5052 | 5053 | return sources[ id ]; 5054 | 5055 | } 5056 | 5057 | sources[ id ] = ( new Source(id )).parse( element ); 5058 | return sources[ id ]; 5059 | 5060 | } 5061 | 5062 | function _nsResolver( nsPrefix ) { 5063 | 5064 | if ( nsPrefix === "dae" ) { 5065 | 5066 | return "http://www.collada.org/2005/11/COLLADASchema"; 5067 | 5068 | } 5069 | 5070 | return null; 5071 | 5072 | } 5073 | 5074 | function _bools( str ) { 5075 | 5076 | var raw = _strings( str ); 5077 | var data = []; 5078 | 5079 | for ( var i = 0, l = raw.length; i < l; i ++ ) { 5080 | 5081 | data.push( (raw[i] === 'true' || raw[i] === '1') ? true : false ); 5082 | 5083 | } 5084 | 5085 | return data; 5086 | 5087 | } 5088 | 5089 | function _floats( str ) { 5090 | 5091 | var raw = _strings(str); 5092 | var data = []; 5093 | 5094 | for ( var i = 0, l = raw.length; i < l; i ++ ) { 5095 | 5096 | data.push( parseFloat( raw[ i ] ) ); 5097 | 5098 | } 5099 | 5100 | return data; 5101 | 5102 | } 5103 | 5104 | function _ints( str ) { 5105 | 5106 | var raw = _strings( str ); 5107 | var data = []; 5108 | 5109 | for ( var i = 0, l = raw.length; i < l; i ++ ) { 5110 | 5111 | data.push( parseInt( raw[ i ], 10 ) ); 5112 | 5113 | } 5114 | 5115 | return data; 5116 | 5117 | } 5118 | 5119 | function _strings( str ) { 5120 | 5121 | return ( str.length > 0 ) ? _trimString( str ).split( /\s+/ ) : []; 5122 | 5123 | } 5124 | 5125 | function _trimString( str ) { 5126 | 5127 | return str.replace( /^\s+/, "" ).replace( /\s+$/, "" ); 5128 | 5129 | } 5130 | 5131 | function _attr_as_float( element, name, defaultValue ) { 5132 | 5133 | if ( element.hasAttribute( name ) ) { 5134 | 5135 | return parseFloat( element.getAttribute( name ) ); 5136 | 5137 | } else { 5138 | 5139 | return defaultValue; 5140 | 5141 | } 5142 | 5143 | } 5144 | 5145 | function _attr_as_int( element, name, defaultValue ) { 5146 | 5147 | if ( element.hasAttribute( name ) ) { 5148 | 5149 | return parseInt( element.getAttribute( name ), 10) ; 5150 | 5151 | } else { 5152 | 5153 | return defaultValue; 5154 | 5155 | } 5156 | 5157 | } 5158 | 5159 | function _attr_as_string( element, name, defaultValue ) { 5160 | 5161 | if ( element.hasAttribute( name ) ) { 5162 | 5163 | return element.getAttribute( name ); 5164 | 5165 | } else { 5166 | 5167 | return defaultValue; 5168 | 5169 | } 5170 | 5171 | } 5172 | 5173 | function _format_float( f, num ) { 5174 | 5175 | if ( f === undefined ) { 5176 | 5177 | var s = '0.'; 5178 | 5179 | while ( s.length < num + 2 ) { 5180 | 5181 | s += '0'; 5182 | 5183 | } 5184 | 5185 | return s; 5186 | 5187 | } 5188 | 5189 | num = num || 2; 5190 | 5191 | var parts = f.toString().split( '.' ); 5192 | parts[ 1 ] = parts.length > 1 ? parts[ 1 ].substr( 0, num ) : "0"; 5193 | 5194 | while ( parts[ 1 ].length < num ) { 5195 | 5196 | parts[ 1 ] += '0'; 5197 | 5198 | } 5199 | 5200 | return parts.join( '.' ); 5201 | 5202 | } 5203 | 5204 | function loadTextureImage ( texture, url ) { 5205 | 5206 | var loader = new THREE.ImageLoader(); 5207 | 5208 | loader.load( url, function ( image ) { 5209 | 5210 | texture.image = image; 5211 | texture.needsUpdate = true; 5212 | 5213 | } ); 5214 | 5215 | } 5216 | 5217 | function extractDoubleSided( obj, element ) { 5218 | 5219 | obj.doubleSided = false; 5220 | 5221 | var node = element.querySelectorAll('extra double_sided')[0]; 5222 | 5223 | if ( node ) { 5224 | 5225 | if ( node && parseInt( node.textContent, 10 ) === 1 ) { 5226 | 5227 | obj.doubleSided = true; 5228 | 5229 | } 5230 | 5231 | } 5232 | 5233 | } 5234 | 5235 | // Up axis conversion 5236 | 5237 | function setUpConversion() { 5238 | 5239 | if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { 5240 | 5241 | upConversion = null; 5242 | 5243 | } else { 5244 | 5245 | switch ( colladaUp ) { 5246 | 5247 | case 'X': 5248 | 5249 | upConversion = options.upAxis === 'Y' ? 'XtoY' : 'XtoZ'; 5250 | break; 5251 | 5252 | case 'Y': 5253 | 5254 | upConversion = options.upAxis === 'X' ? 'YtoX' : 'YtoZ'; 5255 | break; 5256 | 5257 | case 'Z': 5258 | 5259 | upConversion = options.upAxis === 'X' ? 'ZtoX' : 'ZtoY'; 5260 | break; 5261 | 5262 | } 5263 | 5264 | } 5265 | 5266 | } 5267 | 5268 | function fixCoords( data, sign ) { 5269 | 5270 | if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { 5271 | 5272 | return; 5273 | 5274 | } 5275 | 5276 | switch ( upConversion ) { 5277 | 5278 | case 'XtoY': 5279 | 5280 | var tmp = data[ 0 ]; 5281 | data[ 0 ] = sign * data[ 1 ]; 5282 | data[ 1 ] = tmp; 5283 | break; 5284 | 5285 | case 'XtoZ': 5286 | 5287 | var tmp = data[ 2 ]; 5288 | data[ 2 ] = data[ 1 ]; 5289 | data[ 1 ] = data[ 0 ]; 5290 | data[ 0 ] = tmp; 5291 | break; 5292 | 5293 | case 'YtoX': 5294 | 5295 | var tmp = data[ 0 ]; 5296 | data[ 0 ] = data[ 1 ]; 5297 | data[ 1 ] = sign * tmp; 5298 | break; 5299 | 5300 | case 'YtoZ': 5301 | 5302 | var tmp = data[ 1 ]; 5303 | data[ 1 ] = sign * data[ 2 ]; 5304 | data[ 2 ] = tmp; 5305 | break; 5306 | 5307 | case 'ZtoX': 5308 | 5309 | var tmp = data[ 0 ]; 5310 | data[ 0 ] = data[ 1 ]; 5311 | data[ 1 ] = data[ 2 ]; 5312 | data[ 2 ] = tmp; 5313 | break; 5314 | 5315 | case 'ZtoY': 5316 | 5317 | var tmp = data[ 1 ]; 5318 | data[ 1 ] = data[ 2 ]; 5319 | data[ 2 ] = sign * tmp; 5320 | break; 5321 | 5322 | } 5323 | 5324 | } 5325 | 5326 | function getConvertedTranslation( axis, data ) { 5327 | 5328 | if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { 5329 | 5330 | return data; 5331 | 5332 | } 5333 | 5334 | switch ( axis ) { 5335 | case 'X': 5336 | data = upConversion === 'XtoY' ? data * -1 : data; 5337 | break; 5338 | case 'Y': 5339 | data = upConversion === 'YtoZ' || upConversion === 'YtoX' ? data * -1 : data; 5340 | break; 5341 | case 'Z': 5342 | data = upConversion === 'ZtoY' ? data * -1 : data ; 5343 | break; 5344 | default: 5345 | break; 5346 | } 5347 | 5348 | return data; 5349 | } 5350 | 5351 | function getConvertedVec3( data, offset ) { 5352 | 5353 | var arr = [ data[ offset ], data[ offset + 1 ], data[ offset + 2 ] ]; 5354 | fixCoords( arr, -1 ); 5355 | return new THREE.Vector3( arr[ 0 ], arr[ 1 ], arr[ 2 ] ); 5356 | 5357 | } 5358 | 5359 | function getConvertedMat4( data ) { 5360 | 5361 | if ( options.convertUpAxis ) { 5362 | 5363 | // First fix rotation and scale 5364 | 5365 | // Columns first 5366 | var arr = [ data[ 0 ], data[ 4 ], data[ 8 ] ]; 5367 | fixCoords( arr, -1 ); 5368 | data[ 0 ] = arr[ 0 ]; 5369 | data[ 4 ] = arr[ 1 ]; 5370 | data[ 8 ] = arr[ 2 ]; 5371 | arr = [ data[ 1 ], data[ 5 ], data[ 9 ] ]; 5372 | fixCoords( arr, -1 ); 5373 | data[ 1 ] = arr[ 0 ]; 5374 | data[ 5 ] = arr[ 1 ]; 5375 | data[ 9 ] = arr[ 2 ]; 5376 | arr = [ data[ 2 ], data[ 6 ], data[ 10 ] ]; 5377 | fixCoords( arr, -1 ); 5378 | data[ 2 ] = arr[ 0 ]; 5379 | data[ 6 ] = arr[ 1 ]; 5380 | data[ 10 ] = arr[ 2 ]; 5381 | // Rows second 5382 | arr = [ data[ 0 ], data[ 1 ], data[ 2 ] ]; 5383 | fixCoords( arr, -1 ); 5384 | data[ 0 ] = arr[ 0 ]; 5385 | data[ 1 ] = arr[ 1 ]; 5386 | data[ 2 ] = arr[ 2 ]; 5387 | arr = [ data[ 4 ], data[ 5 ], data[ 6 ] ]; 5388 | fixCoords( arr, -1 ); 5389 | data[ 4 ] = arr[ 0 ]; 5390 | data[ 5 ] = arr[ 1 ]; 5391 | data[ 6 ] = arr[ 2 ]; 5392 | arr = [ data[ 8 ], data[ 9 ], data[ 10 ] ]; 5393 | fixCoords( arr, -1 ); 5394 | data[ 8 ] = arr[ 0 ]; 5395 | data[ 9 ] = arr[ 1 ]; 5396 | data[ 10 ] = arr[ 2 ]; 5397 | 5398 | // Now fix translation 5399 | arr = [ data[ 3 ], data[ 7 ], data[ 11 ] ]; 5400 | fixCoords( arr, -1 ); 5401 | data[ 3 ] = arr[ 0 ]; 5402 | data[ 7 ] = arr[ 1 ]; 5403 | data[ 11 ] = arr[ 2 ]; 5404 | 5405 | } 5406 | 5407 | return new THREE.Matrix4().set( 5408 | data[0], data[1], data[2], data[3], 5409 | data[4], data[5], data[6], data[7], 5410 | data[8], data[9], data[10], data[11], 5411 | data[12], data[13], data[14], data[15] 5412 | ); 5413 | 5414 | } 5415 | 5416 | function getConvertedIndex( index ) { 5417 | 5418 | if ( index > -1 && index < 3 ) { 5419 | 5420 | var members = [ 'X', 'Y', 'Z' ], 5421 | indices = { X: 0, Y: 1, Z: 2 }; 5422 | 5423 | index = getConvertedMember( members[ index ] ); 5424 | index = indices[ index ]; 5425 | 5426 | } 5427 | 5428 | return index; 5429 | 5430 | } 5431 | 5432 | function getConvertedMember( member ) { 5433 | 5434 | if ( options.convertUpAxis ) { 5435 | 5436 | switch ( member ) { 5437 | 5438 | case 'X': 5439 | 5440 | switch ( upConversion ) { 5441 | 5442 | case 'XtoY': 5443 | case 'XtoZ': 5444 | case 'YtoX': 5445 | 5446 | member = 'Y'; 5447 | break; 5448 | 5449 | case 'ZtoX': 5450 | 5451 | member = 'Z'; 5452 | break; 5453 | 5454 | } 5455 | 5456 | break; 5457 | 5458 | case 'Y': 5459 | 5460 | switch ( upConversion ) { 5461 | 5462 | case 'XtoY': 5463 | case 'YtoX': 5464 | case 'ZtoX': 5465 | 5466 | member = 'X'; 5467 | break; 5468 | 5469 | case 'XtoZ': 5470 | case 'YtoZ': 5471 | case 'ZtoY': 5472 | 5473 | member = 'Z'; 5474 | break; 5475 | 5476 | } 5477 | 5478 | break; 5479 | 5480 | case 'Z': 5481 | 5482 | switch ( upConversion ) { 5483 | 5484 | case 'XtoZ': 5485 | 5486 | member = 'X'; 5487 | break; 5488 | 5489 | case 'YtoZ': 5490 | case 'ZtoX': 5491 | case 'ZtoY': 5492 | 5493 | member = 'Y'; 5494 | break; 5495 | 5496 | } 5497 | 5498 | break; 5499 | 5500 | } 5501 | 5502 | } 5503 | 5504 | return member; 5505 | 5506 | } 5507 | 5508 | return { 5509 | 5510 | load: load, 5511 | parse: parse, 5512 | setPreferredShading: setPreferredShading, 5513 | applySkin: applySkin, 5514 | geometries : geometries, 5515 | options: options 5516 | 5517 | }; 5518 | 5519 | }; 5520 | -------------------------------------------------------------------------------- /lib/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 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 14 | // Pan - right mouse, or arrow keys / touch: three finter swipe 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 63 | 64 | // Set to true to automatically rotate around the target 65 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 66 | this.autoRotate = false; 67 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 68 | 69 | // Set to false to disable use of the keys 70 | this.enableKeys = true; 71 | 72 | // The four arrow keys 73 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 74 | 75 | // Mouse buttons 76 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 77 | 78 | // for reset 79 | this.target0 = this.target.clone(); 80 | this.position0 = this.object.position.clone(); 81 | this.zoom0 = this.object.zoom; 82 | 83 | // 84 | // public methods 85 | // 86 | 87 | this.getPolarAngle = function () { 88 | 89 | return phi; 90 | 91 | }; 92 | 93 | this.getAzimuthalAngle = function () { 94 | 95 | return theta; 96 | 97 | }; 98 | 99 | this.reset = function () { 100 | 101 | scope.target.copy( scope.target0 ); 102 | scope.object.position.copy( scope.position0 ); 103 | scope.object.zoom = scope.zoom0; 104 | 105 | scope.object.updateProjectionMatrix(); 106 | scope.dispatchEvent( changeEvent ); 107 | 108 | scope.update(); 109 | 110 | state = STATE.NONE; 111 | 112 | }; 113 | 114 | // this method is exposed, but perhaps it would be better if we can make it private... 115 | this.update = function() { 116 | 117 | var offset = new THREE.Vector3(); 118 | 119 | // so camera.up is the orbit axis 120 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 121 | var quatInverse = quat.clone().inverse(); 122 | 123 | var lastPosition = new THREE.Vector3(); 124 | var lastQuaternion = new THREE.Quaternion(); 125 | 126 | return function () { 127 | 128 | var position = scope.object.position; 129 | 130 | offset.copy( position ).sub( scope.target ); 131 | 132 | // rotate offset to "y-axis-is-up" space 133 | offset.applyQuaternion( quat ); 134 | 135 | // angle from z-axis around y-axis 136 | spherical.setFromVector3( offset ); 137 | 138 | if ( scope.autoRotate && state === STATE.NONE ) { 139 | 140 | rotateLeft( getAutoRotationAngle() ); 141 | 142 | } 143 | 144 | spherical.theta += sphericalDelta.theta; 145 | spherical.phi += sphericalDelta.phi; 146 | 147 | // restrict theta to be between desired limits 148 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 149 | 150 | // restrict phi to be between desired limits 151 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 152 | 153 | spherical.makeSafe(); 154 | 155 | 156 | spherical.radius *= scale; 157 | 158 | // restrict radius to be between desired limits 159 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 160 | 161 | // move target to panned location 162 | scope.target.add( panOffset ); 163 | 164 | offset.setFromSpherical( spherical ); 165 | 166 | // rotate offset back to "camera-up-vector-is-up" space 167 | offset.applyQuaternion( quatInverse ); 168 | 169 | position.copy( scope.target ).add( offset ); 170 | 171 | scope.object.lookAt( scope.target ); 172 | 173 | if ( scope.enableDamping === true ) { 174 | 175 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 176 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 177 | 178 | } else { 179 | 180 | sphericalDelta.set( 0, 0, 0 ); 181 | 182 | } 183 | 184 | scale = 1; 185 | panOffset.set( 0, 0, 0 ); 186 | 187 | // update condition is: 188 | // min(camera displacement, camera rotation in radians)^2 > EPS 189 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 190 | 191 | if ( zoomChanged || 192 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 193 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 194 | 195 | scope.dispatchEvent( changeEvent ); 196 | 197 | lastPosition.copy( scope.object.position ); 198 | lastQuaternion.copy( scope.object.quaternion ); 199 | zoomChanged = false; 200 | 201 | return true; 202 | 203 | } 204 | 205 | return false; 206 | 207 | }; 208 | 209 | }(); 210 | 211 | this.dispose = function() { 212 | 213 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 214 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 215 | scope.domElement.removeEventListener( 'mousewheel', onMouseWheel, false ); 216 | scope.domElement.removeEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox 217 | 218 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 219 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 220 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 221 | 222 | document.removeEventListener( 'mousemove', onMouseMove, false ); 223 | document.removeEventListener( 'mouseup', onMouseUp, false ); 224 | document.removeEventListener( 'mouseout', onMouseUp, false ); 225 | 226 | window.removeEventListener( 'keydown', onKeyDown, false ); 227 | 228 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 229 | 230 | }; 231 | 232 | // 233 | // internals 234 | // 235 | 236 | var scope = this; 237 | 238 | var changeEvent = { type: 'change' }; 239 | var startEvent = { type: 'start' }; 240 | var endEvent = { type: 'end' }; 241 | 242 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 243 | 244 | var state = STATE.NONE; 245 | 246 | var EPS = 0.000001; 247 | 248 | // current position in spherical coordinates 249 | var spherical = new THREE.Spherical(); 250 | var sphericalDelta = new THREE.Spherical(); 251 | 252 | var scale = 1; 253 | var panOffset = new THREE.Vector3(); 254 | var zoomChanged = false; 255 | 256 | var rotateStart = new THREE.Vector2(); 257 | var rotateEnd = new THREE.Vector2(); 258 | var rotateDelta = new THREE.Vector2(); 259 | 260 | var panStart = new THREE.Vector2(); 261 | var panEnd = new THREE.Vector2(); 262 | var panDelta = new THREE.Vector2(); 263 | 264 | var dollyStart = new THREE.Vector2(); 265 | var dollyEnd = new THREE.Vector2(); 266 | var dollyDelta = new THREE.Vector2(); 267 | 268 | function getAutoRotationAngle() { 269 | 270 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 271 | 272 | } 273 | 274 | function getZoomScale() { 275 | 276 | return Math.pow( 0.95, scope.zoomSpeed ); 277 | 278 | } 279 | 280 | function rotateLeft( angle ) { 281 | 282 | sphericalDelta.theta -= angle; 283 | 284 | } 285 | 286 | function rotateUp( angle ) { 287 | 288 | sphericalDelta.phi -= angle; 289 | 290 | } 291 | 292 | var panLeft = function() { 293 | 294 | var v = new THREE.Vector3(); 295 | 296 | return function panLeft( distance, objectMatrix ) { 297 | 298 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 299 | v.multiplyScalar( - distance ); 300 | 301 | panOffset.add( v ); 302 | 303 | }; 304 | 305 | }(); 306 | 307 | var panUp = function() { 308 | 309 | var v = new THREE.Vector3(); 310 | 311 | return function panUp( distance, objectMatrix ) { 312 | 313 | v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix 314 | v.multiplyScalar( distance ); 315 | 316 | panOffset.add( v ); 317 | 318 | }; 319 | 320 | }(); 321 | 322 | // deltaX and deltaY are in pixels; right and down are positive 323 | var pan = function() { 324 | 325 | var offset = new THREE.Vector3(); 326 | 327 | return function( deltaX, deltaY ) { 328 | 329 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 330 | 331 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 332 | 333 | // perspective 334 | var position = scope.object.position; 335 | offset.copy( position ).sub( scope.target ); 336 | var targetDistance = offset.length(); 337 | 338 | // half of the fov is center to top of screen 339 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 340 | 341 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 342 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 343 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 344 | 345 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 346 | 347 | // orthographic 348 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 349 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 350 | 351 | } else { 352 | 353 | // camera neither orthographic nor perspective 354 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 355 | scope.enablePan = false; 356 | 357 | } 358 | 359 | }; 360 | 361 | }(); 362 | 363 | function dollyIn( dollyScale ) { 364 | 365 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 366 | 367 | scale /= dollyScale; 368 | 369 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 370 | 371 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 372 | scope.object.updateProjectionMatrix(); 373 | zoomChanged = true; 374 | 375 | } else { 376 | 377 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 378 | scope.enableZoom = false; 379 | 380 | } 381 | 382 | } 383 | 384 | function dollyOut( dollyScale ) { 385 | 386 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 387 | 388 | scale *= dollyScale; 389 | 390 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 391 | 392 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 393 | scope.object.updateProjectionMatrix(); 394 | zoomChanged = true; 395 | 396 | } else { 397 | 398 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 399 | scope.enableZoom = false; 400 | 401 | } 402 | 403 | } 404 | 405 | // 406 | // event callbacks - update the object state 407 | // 408 | 409 | function handleMouseDownRotate( event ) { 410 | 411 | //console.log( 'handleMouseDownRotate' ); 412 | 413 | rotateStart.set( event.clientX, event.clientY ); 414 | 415 | } 416 | 417 | function handleMouseDownDolly( event ) { 418 | 419 | //console.log( 'handleMouseDownDolly' ); 420 | 421 | dollyStart.set( event.clientX, event.clientY ); 422 | 423 | } 424 | 425 | function handleMouseDownPan( event ) { 426 | 427 | //console.log( 'handleMouseDownPan' ); 428 | 429 | panStart.set( event.clientX, event.clientY ); 430 | 431 | } 432 | 433 | function handleMouseMoveRotate( event ) { 434 | 435 | //console.log( 'handleMouseMoveRotate' ); 436 | 437 | rotateEnd.set( event.clientX, event.clientY ); 438 | rotateDelta.subVectors( rotateEnd, rotateStart ); 439 | 440 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 441 | 442 | // rotating across whole screen goes 360 degrees around 443 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 444 | 445 | // rotating up and down along whole screen attempts to go 360, but limited to 180 446 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 447 | 448 | rotateStart.copy( rotateEnd ); 449 | 450 | scope.update(); 451 | 452 | } 453 | 454 | function handleMouseMoveDolly( event ) { 455 | 456 | //console.log( 'handleMouseMoveDolly' ); 457 | 458 | dollyEnd.set( event.clientX, event.clientY ); 459 | 460 | dollyDelta.subVectors( dollyEnd, dollyStart ); 461 | 462 | if ( dollyDelta.y > 0 ) { 463 | 464 | dollyIn( getZoomScale() ); 465 | 466 | } else if ( dollyDelta.y < 0 ) { 467 | 468 | dollyOut( getZoomScale() ); 469 | 470 | } 471 | 472 | dollyStart.copy( dollyEnd ); 473 | 474 | scope.update(); 475 | 476 | } 477 | 478 | function handleMouseMovePan( event ) { 479 | 480 | //console.log( 'handleMouseMovePan' ); 481 | 482 | panEnd.set( event.clientX, event.clientY ); 483 | 484 | panDelta.subVectors( panEnd, panStart ); 485 | 486 | pan( panDelta.x, panDelta.y ); 487 | 488 | panStart.copy( panEnd ); 489 | 490 | scope.update(); 491 | 492 | } 493 | 494 | function handleMouseUp( event ) { 495 | 496 | //console.log( 'handleMouseUp' ); 497 | 498 | } 499 | 500 | function handleMouseWheel( event ) { 501 | 502 | //console.log( 'handleMouseWheel' ); 503 | 504 | var delta = 0; 505 | 506 | if ( event.wheelDelta !== undefined ) { 507 | 508 | // WebKit / Opera / Explorer 9 509 | 510 | delta = event.wheelDelta; 511 | 512 | } else if ( event.detail !== undefined ) { 513 | 514 | // Firefox 515 | 516 | delta = - event.detail; 517 | 518 | } 519 | 520 | if ( delta > 0 ) { 521 | 522 | dollyOut( getZoomScale() ); 523 | 524 | } else if ( delta < 0 ) { 525 | 526 | dollyIn( getZoomScale() ); 527 | 528 | } 529 | 530 | scope.update(); 531 | 532 | } 533 | 534 | function handleKeyDown( event ) { 535 | 536 | //console.log( 'handleKeyDown' ); 537 | 538 | switch ( event.keyCode ) { 539 | 540 | case scope.keys.UP: 541 | pan( 0, scope.keyPanSpeed ); 542 | scope.update(); 543 | break; 544 | 545 | case scope.keys.BOTTOM: 546 | pan( 0, - scope.keyPanSpeed ); 547 | scope.update(); 548 | break; 549 | 550 | case scope.keys.LEFT: 551 | pan( scope.keyPanSpeed, 0 ); 552 | scope.update(); 553 | break; 554 | 555 | case scope.keys.RIGHT: 556 | pan( - scope.keyPanSpeed, 0 ); 557 | scope.update(); 558 | break; 559 | 560 | } 561 | 562 | } 563 | 564 | function handleTouchStartRotate( event ) { 565 | 566 | //console.log( 'handleTouchStartRotate' ); 567 | 568 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 569 | 570 | } 571 | 572 | function handleTouchStartDolly( event ) { 573 | 574 | //console.log( 'handleTouchStartDolly' ); 575 | 576 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 577 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 578 | 579 | var distance = Math.sqrt( dx * dx + dy * dy ); 580 | 581 | dollyStart.set( 0, distance ); 582 | 583 | } 584 | 585 | function handleTouchStartPan( event ) { 586 | 587 | //console.log( 'handleTouchStartPan' ); 588 | 589 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 590 | 591 | } 592 | 593 | function handleTouchMoveRotate( event ) { 594 | 595 | //console.log( 'handleTouchMoveRotate' ); 596 | 597 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 598 | rotateDelta.subVectors( rotateEnd, rotateStart ); 599 | 600 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 601 | 602 | // rotating across whole screen goes 360 degrees around 603 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 604 | 605 | // rotating up and down along whole screen attempts to go 360, but limited to 180 606 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 607 | 608 | rotateStart.copy( rotateEnd ); 609 | 610 | scope.update(); 611 | 612 | } 613 | 614 | function handleTouchMoveDolly( event ) { 615 | 616 | //console.log( 'handleTouchMoveDolly' ); 617 | 618 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 619 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 620 | 621 | var distance = Math.sqrt( dx * dx + dy * dy ); 622 | 623 | dollyEnd.set( 0, distance ); 624 | 625 | dollyDelta.subVectors( dollyEnd, dollyStart ); 626 | 627 | if ( dollyDelta.y > 0 ) { 628 | 629 | dollyOut( getZoomScale() ); 630 | 631 | } else if ( dollyDelta.y < 0 ) { 632 | 633 | dollyIn( getZoomScale() ); 634 | 635 | } 636 | 637 | dollyStart.copy( dollyEnd ); 638 | 639 | scope.update(); 640 | 641 | } 642 | 643 | function handleTouchMovePan( event ) { 644 | 645 | //console.log( 'handleTouchMovePan' ); 646 | 647 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 648 | 649 | panDelta.subVectors( panEnd, panStart ); 650 | 651 | pan( panDelta.x, panDelta.y ); 652 | 653 | panStart.copy( panEnd ); 654 | 655 | scope.update(); 656 | 657 | } 658 | 659 | function handleTouchEnd( event ) { 660 | 661 | //console.log( 'handleTouchEnd' ); 662 | 663 | } 664 | 665 | // 666 | // event handlers - FSM: listen for events and reset state 667 | // 668 | 669 | function onMouseDown( event ) { 670 | 671 | if ( scope.enabled === false ) return; 672 | 673 | event.preventDefault(); 674 | 675 | if ( event.button === scope.mouseButtons.ORBIT ) { 676 | 677 | if ( scope.enableRotate === false ) return; 678 | 679 | handleMouseDownRotate( event ); 680 | 681 | state = STATE.ROTATE; 682 | 683 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 684 | 685 | if ( scope.enableZoom === false ) return; 686 | 687 | handleMouseDownDolly( event ); 688 | 689 | state = STATE.DOLLY; 690 | 691 | } else if ( event.button === scope.mouseButtons.PAN ) { 692 | 693 | if ( scope.enablePan === false ) return; 694 | 695 | handleMouseDownPan( event ); 696 | 697 | state = STATE.PAN; 698 | 699 | } 700 | 701 | if ( state !== STATE.NONE ) { 702 | 703 | document.addEventListener( 'mousemove', onMouseMove, false ); 704 | document.addEventListener( 'mouseup', onMouseUp, false ); 705 | document.addEventListener( 'mouseout', onMouseUp, false ); 706 | 707 | scope.dispatchEvent( startEvent ); 708 | 709 | } 710 | 711 | } 712 | 713 | function onMouseMove( event ) { 714 | 715 | if ( scope.enabled === false ) return; 716 | 717 | event.preventDefault(); 718 | 719 | if ( state === STATE.ROTATE ) { 720 | 721 | if ( scope.enableRotate === false ) return; 722 | 723 | handleMouseMoveRotate( event ); 724 | 725 | } else if ( state === STATE.DOLLY ) { 726 | 727 | if ( scope.enableZoom === false ) return; 728 | 729 | handleMouseMoveDolly( event ); 730 | 731 | } else if ( state === STATE.PAN ) { 732 | 733 | if ( scope.enablePan === false ) return; 734 | 735 | handleMouseMovePan( event ); 736 | 737 | } 738 | 739 | } 740 | 741 | function onMouseUp( event ) { 742 | 743 | if ( scope.enabled === false ) return; 744 | 745 | handleMouseUp( event ); 746 | 747 | document.removeEventListener( 'mousemove', onMouseMove, false ); 748 | document.removeEventListener( 'mouseup', onMouseUp, false ); 749 | document.removeEventListener( 'mouseout', onMouseUp, false ); 750 | 751 | scope.dispatchEvent( endEvent ); 752 | 753 | state = STATE.NONE; 754 | 755 | } 756 | 757 | function onMouseWheel( event ) { 758 | 759 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 760 | 761 | event.preventDefault(); 762 | event.stopPropagation(); 763 | 764 | handleMouseWheel( event ); 765 | 766 | scope.dispatchEvent( startEvent ); // not sure why these are here... 767 | scope.dispatchEvent( endEvent ); 768 | 769 | } 770 | 771 | function onKeyDown( event ) { 772 | 773 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 774 | 775 | handleKeyDown( event ); 776 | 777 | } 778 | 779 | function onTouchStart( event ) { 780 | 781 | if ( scope.enabled === false ) return; 782 | 783 | switch ( event.touches.length ) { 784 | 785 | case 1: // one-fingered touch: rotate 786 | 787 | if ( scope.enableRotate === false ) return; 788 | 789 | handleTouchStartRotate( event ); 790 | 791 | state = STATE.TOUCH_ROTATE; 792 | 793 | break; 794 | 795 | case 2: // two-fingered touch: dolly 796 | 797 | if ( scope.enableZoom === false ) return; 798 | 799 | handleTouchStartDolly( event ); 800 | 801 | state = STATE.TOUCH_DOLLY; 802 | 803 | break; 804 | 805 | case 3: // three-fingered touch: pan 806 | 807 | if ( scope.enablePan === false ) return; 808 | 809 | handleTouchStartPan( event ); 810 | 811 | state = STATE.TOUCH_PAN; 812 | 813 | break; 814 | 815 | default: 816 | 817 | state = STATE.NONE; 818 | 819 | } 820 | 821 | if ( state !== STATE.NONE ) { 822 | 823 | scope.dispatchEvent( startEvent ); 824 | 825 | } 826 | 827 | } 828 | 829 | function onTouchMove( event ) { 830 | 831 | if ( scope.enabled === false ) return; 832 | 833 | event.preventDefault(); 834 | event.stopPropagation(); 835 | 836 | switch ( event.touches.length ) { 837 | 838 | case 1: // one-fingered touch: rotate 839 | 840 | if ( scope.enableRotate === false ) return; 841 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... 842 | 843 | handleTouchMoveRotate( event ); 844 | 845 | break; 846 | 847 | case 2: // two-fingered touch: dolly 848 | 849 | if ( scope.enableZoom === false ) return; 850 | if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... 851 | 852 | handleTouchMoveDolly( event ); 853 | 854 | break; 855 | 856 | case 3: // three-fingered touch: pan 857 | 858 | if ( scope.enablePan === false ) return; 859 | if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... 860 | 861 | handleTouchMovePan( event ); 862 | 863 | break; 864 | 865 | default: 866 | 867 | state = STATE.NONE; 868 | 869 | } 870 | 871 | } 872 | 873 | function onTouchEnd( event ) { 874 | 875 | if ( scope.enabled === false ) return; 876 | 877 | handleTouchEnd( event ); 878 | 879 | scope.dispatchEvent( endEvent ); 880 | 881 | state = STATE.NONE; 882 | 883 | } 884 | 885 | function onContextMenu( event ) { 886 | 887 | event.preventDefault(); 888 | 889 | } 890 | 891 | // 892 | 893 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 894 | 895 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 896 | scope.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 897 | scope.domElement.addEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox 898 | 899 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 900 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 901 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 902 | 903 | window.addEventListener( 'keydown', onKeyDown, false ); 904 | 905 | // force an update at start 906 | 907 | this.update(); 908 | 909 | }; 910 | 911 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 912 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 913 | 914 | Object.defineProperties( THREE.OrbitControls.prototype, { 915 | 916 | center: { 917 | 918 | get: function () { 919 | 920 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 921 | return this.target; 922 | 923 | } 924 | 925 | }, 926 | 927 | // backward compatibility 928 | 929 | noZoom: { 930 | 931 | get: function () { 932 | 933 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 934 | return ! this.enableZoom; 935 | 936 | }, 937 | 938 | set: function ( value ) { 939 | 940 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 941 | this.enableZoom = ! value; 942 | 943 | } 944 | 945 | }, 946 | 947 | noRotate: { 948 | 949 | get: function () { 950 | 951 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 952 | return ! this.enableRotate; 953 | 954 | }, 955 | 956 | set: function ( value ) { 957 | 958 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 959 | this.enableRotate = ! value; 960 | 961 | } 962 | 963 | }, 964 | 965 | noPan: { 966 | 967 | get: function () { 968 | 969 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 970 | return ! this.enablePan; 971 | 972 | }, 973 | 974 | set: function ( value ) { 975 | 976 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 977 | this.enablePan = ! value; 978 | 979 | } 980 | 981 | }, 982 | 983 | noKeys: { 984 | 985 | get: function () { 986 | 987 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 988 | return ! this.enableKeys; 989 | 990 | }, 991 | 992 | set: function ( value ) { 993 | 994 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 995 | this.enableKeys = ! value; 996 | 997 | } 998 | 999 | }, 1000 | 1001 | staticMoving : { 1002 | 1003 | get: function () { 1004 | 1005 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1006 | return ! this.constraint.enableDamping; 1007 | 1008 | }, 1009 | 1010 | set: function ( value ) { 1011 | 1012 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1013 | this.constraint.enableDamping = ! value; 1014 | 1015 | } 1016 | 1017 | }, 1018 | 1019 | dynamicDampingFactor : { 1020 | 1021 | get: function () { 1022 | 1023 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1024 | return this.constraint.dampingFactor; 1025 | 1026 | }, 1027 | 1028 | set: function ( value ) { 1029 | 1030 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1031 | this.constraint.dampingFactor = value; 1032 | 1033 | } 1034 | 1035 | } 1036 | 1037 | } ); 1038 | -------------------------------------------------------------------------------- /lib/three-license.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright © 2010-2016 three.js authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /screenshot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daign/clipping-with-caps/4909816e0bcd931d5b0fd2e1dc9714ec9e10de93/screenshot_01.png -------------------------------------------------------------------------------- /screenshot_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daign/clipping-with-caps/4909816e0bcd931d5b0fd2e1dc9714ec9e10de93/screenshot_02.png --------------------------------------------------------------------------------