/** * @author mrdoob / http://mrdoob.com/ * @author supereggbert / http://www.paulbrunt.co.uk/ * @author julianwa / https://github.com/julianwa */ THREE.Projector = function () { var _object, _objectCount, _objectPool = [], _objectPoolLength = 0, _vertex, _vertexCount, _vertexPool = [], _vertexPoolLength = 0, _face, _face3Count, _face3Pool = [], _face3PoolLength = 0, _face4Count, _face4Pool = [], _face4PoolLength = 0, _line, _lineCount, _linePool = [], _linePoolLength = 0, _particle, _particleCount, _particlePool = [], _particlePoolLength = 0, _renderData = { objects: [], sprites: [], lights: [], elements: [] }, _vector3 = new THREE.Vector3(), _vector4 = new THREE.Vector4(), _clipBox = new THREE.Box3( new THREE.Vector3( -1, -1, -1 ), new THREE.Vector3( 1, 1, 1 ) ), _boundingBox = new THREE.Box3(), _points3 = new Array( 3 ), _points4 = new Array( 4 ), _viewMatrix = new THREE.Matrix4(), _viewProjectionMatrix = new THREE.Matrix4(), _modelMatrix, _modelViewProjectionMatrix = new THREE.Matrix4(), _normalMatrix = new THREE.Matrix3(), _normalViewMatrix = new THREE.Matrix3(), _centroid = new THREE.Vector3(), _frustum = new THREE.Frustum(), _clippedVertex1PositionScreen = new THREE.Vector4(), _clippedVertex2PositionScreen = new THREE.Vector4(); this.projectVector = function ( vector, camera ) { camera.matrixWorldInverse.getInverse( camera.matrixWorld ); _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); return vector.applyProjection( _viewProjectionMatrix ); }; this.unprojectVector = function ( vector, camera ) { camera.projectionMatrixInverse.getInverse( camera.projectionMatrix ); _viewProjectionMatrix.multiplyMatrices( camera.matrixWorld, camera.projectionMatrixInverse ); return vector.applyProjection( _viewProjectionMatrix ); }; this.pickingRay = function ( vector, camera ) { // set two vectors with opposing z values vector.z = -1.0; var end = new THREE.Vector3( vector.x, vector.y, 1.0 ); this.unprojectVector( vector, camera ); this.unprojectVector( end, camera ); // find direction from vector to end end.sub( vector ).normalize(); return new THREE.Raycaster( vector, end ); }; var getObject = function ( object ) { _object = getNextObjectInPool(); _object.id = object.id; _object.object = object; if ( object.renderDepth !== null ) { _object.z = object.renderDepth; } else { _vector3.getPositionFromMatrix( object.matrixWorld ); _vector3.applyProjection( _viewProjectionMatrix ); _object.z = _vector3.z; } return _object; }; var projectObject = function ( object ) { if ( object.visible === false ) return; if ( object instanceof THREE.Light ) { _renderData.lights.push( object ); } else if ( object instanceof THREE.Mesh || object instanceof THREE.Line ) { if ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) { _renderData.objects.push( getObject( object ) ); } } else if ( object instanceof THREE.Sprite || object instanceof THREE.Particle ) { _renderData.sprites.push( getObject( object ) ); } for ( var i = 0, l = object.children.length; i < l; i ++ ) { projectObject( object.children[ i ] ); } }; var projectGraph = function ( root, sortObjects ) { _objectCount = 0; _renderData.objects.length = 0; _renderData.sprites.length = 0; _renderData.lights.length = 0; projectObject( root ); if ( sortObjects === true ) { _renderData.objects.sort( painterSort ); } }; this.projectScene = function ( scene, camera, sortObjects, sortElements ) { var visible = false, o, ol, v, vl, f, fl, n, nl, c, cl, u, ul, object, geometry, vertices, faces, face, faceVertexNormals, faceVertexUvs, uvs, v1, v2, v3, v4, isFaceMaterial, objectMaterials; _face3Count = 0; _face4Count = 0; _lineCount = 0; _particleCount = 0; _renderData.elements.length = 0; if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); if ( camera.parent === undefined ) camera.updateMatrixWorld(); _viewMatrix.copy( camera.matrixWorldInverse.getInverse( camera.matrixWorld ) ); _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); _normalViewMatrix.getNormalMatrix( _viewMatrix ); _frustum.setFromMatrix( _viewProjectionMatrix ); projectGraph( scene, sortObjects ); for ( o = 0, ol = _renderData.objects.length; o < ol; o ++ ) { object = _renderData.objects[ o ].object; _modelMatrix = object.matrixWorld; _vertexCount = 0; if ( object instanceof THREE.Mesh ) { geometry = object.geometry; vertices = geometry.vertices; faces = geometry.faces; faceVertexUvs = geometry.faceVertexUvs; _normalMatrix.getNormalMatrix( _modelMatrix ); isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial; objectMaterials = isFaceMaterial === true ? object.material : null; for ( v = 0, vl = vertices.length; v < vl; v ++ ) { _vertex = getNextVertexInPool(); _vertex.positionWorld.copy( vertices[ v ] ).applyMatrix4( _modelMatrix ); _vertex.positionScreen.copy( _vertex.positionWorld ).applyMatrix4( _viewProjectionMatrix ); _vertex.positionScreen.x /= _vertex.positionScreen.w; _vertex.positionScreen.y /= _vertex.positionScreen.w; _vertex.positionScreen.z /= _vertex.positionScreen.w; _vertex.visible = ! ( _vertex.positionScreen.x < -1 || _vertex.positionScreen.x > 1 || _vertex.positionScreen.y < -1 || _vertex.positionScreen.y > 1 || _vertex.positionScreen.z < -1 || _vertex.positionScreen.z > 1 ); } for ( f = 0, fl = faces.length; f < fl; f ++ ) { face = faces[ f ]; var material = isFaceMaterial === true ? objectMaterials.materials[ face.materialIndex ] : object.material; if ( material === undefined ) continue; var side = material.side; if ( face instanceof THREE.Face3 ) { v1 = _vertexPool[ face.a ]; v2 = _vertexPool[ face.b ]; v3 = _vertexPool[ face.c ]; _points3[ 0 ] = v1.positionScreen; _points3[ 1 ] = v2.positionScreen; _points3[ 2 ] = v3.positionScreen; if ( v1.visible === true || v2.visible === true || v3.visible === true || _clipBox.isIntersectionBox( _boundingBox.setFromPoints( _points3 ) ) ) { visible = ( ( v3.positionScreen.x - v1.positionScreen.x ) * ( v2.positionScreen.y - v1.positionScreen.y ) - ( v3.positionScreen.y - v1.positionScreen.y ) * ( v2.positionScreen.x - v1.positionScreen.x ) ) < 0; if ( side === THREE.DoubleSide || visible === ( side === THREE.FrontSide ) ) { _face = getNextFace3InPool(); _face.id = object.id; _face.v1.copy( v1 ); _face.v2.copy( v2 ); _face.v3.copy( v3 ); } else { continue; } } else { continue; } } else if ( face instanceof THREE.Face4 ) { v1 = _vertexPool[ face.a ]; v2 = _vertexPool[ face.b ]; v3 = _vertexPool[ face.c ]; v4 = _vertexPool[ face.d ]; _points4[ 0 ] = v1.positionScreen; _points4[ 1 ] = v2.positionScreen; _points4[ 2 ] = v3.positionScreen; _points4[ 3 ] = v4.positionScreen; if ( v1.visible === true || v2.visible === true || v3.visible === true || v4.visible === true || _clipBox.isIntersectionBox( _boundingBox.setFromPoints( _points4 ) ) ) { visible = ( v4.positionScreen.x - v1.positionScreen.x ) * ( v2.positionScreen.y - v1.positionScreen.y ) - ( v4.positionScreen.y - v1.positionScreen.y ) * ( v2.positionScreen.x - v1.positionScreen.x ) < 0 || ( v2.positionScreen.x - v3.positionScreen.x ) * ( v4.positionScreen.y - v3.positionScreen.y ) - ( v2.positionScreen.y - v3.positionScreen.y ) * ( v4.positionScreen.x - v3.positionScreen.x ) < 0; if ( side === THREE.DoubleSide || visible === ( side === THREE.FrontSide ) ) { _face = getNextFace4InPool(); _face.id = object.id; _face.v1.copy( v1 ); _face.v2.copy( v2 ); _face.v3.copy( v3 ); _face.v4.copy( v4 ); } else { continue; } } else { continue; } } _face.normalModel.copy( face.normal ); if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) { _face.normalModel.negate(); } _face.normalModel.applyMatrix3( _normalMatrix ).normalize(); _face.normalModelView.copy( _face.normalModel ).applyMatrix3( _normalViewMatrix ); _face.centroidModel.copy( face.centroid ).applyMatrix4( _modelMatrix ); faceVertexNormals = face.vertexNormals; for ( n = 0, nl = faceVertexNormals.length; n < nl; n ++ ) { var normalModel = _face.vertexNormalsModel[ n ]; normalModel.copy( faceVertexNormals[ n ] ); if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) { normalModel.negate(); } normalModel.applyMatrix3( _normalMatrix ).normalize(); var normalModelView = _face.vertexNormalsModelView[ n ]; normalModelView.copy( normalModel ).applyMatrix3( _normalViewMatrix ); } _face.vertexNormalsLength = faceVertexNormals.length; for ( c = 0, cl = faceVertexUvs.length; c < cl; c ++ ) { uvs = faceVertexUvs[ c ][ f ]; if ( uvs === undefined ) continue; for ( u = 0, ul = uvs.length; u < ul; u ++ ) { _face.uvs[ c ][ u ] = uvs[ u ]; } } _face.color = face.color; _face.material = material; _centroid.copy( _face.centroidModel ).applyProjection( _viewProjectionMatrix ); _face.z = _centroid.z; _renderData.elements.push( _face ); } } else if ( object instanceof THREE.Line ) { _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); vertices = object.geometry.vertices; v1 = getNextVertexInPool(); v1.positionScreen.copy( vertices[ 0 ] ).applyMatrix4( _modelViewProjectionMatrix ); // Handle LineStrip and LinePieces var step = object.type === THREE.LinePieces ? 2 : 1; for ( v = 1, vl = vertices.length; v < vl; v ++ ) { v1 = getNextVertexInPool(); v1.positionScreen.copy( vertices[ v ] ).applyMatrix4( _modelViewProjectionMatrix ); if ( ( v + 1 ) % step > 0 ) continue; v2 = _vertexPool[ _vertexCount - 2 ]; _clippedVertex1PositionScreen.copy( v1.positionScreen ); _clippedVertex2PositionScreen.copy( v2.positionScreen ); if ( clipLine( _clippedVertex1PositionScreen, _clippedVertex2PositionScreen ) === true ) { // Perform the perspective divide _clippedVertex1PositionScreen.multiplyScalar( 1 / _clippedVertex1PositionScreen.w ); _clippedVertex2PositionScreen.multiplyScalar( 1 / _clippedVertex2PositionScreen.w ); _line = getNextLineInPool(); _line.id = object.id; _line.v1.positionScreen.copy( _clippedVertex1PositionScreen ); _line.v2.positionScreen.copy( _clippedVertex2PositionScreen ); _line.z = Math.max( _clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z ); _line.material = object.material; if ( object.material.vertexColors === THREE.VertexColors ) { _line.vertexColors[ 0 ].copy( object.geometry.colors[ v ] ); _line.vertexColors[ 1 ].copy( object.geometry.colors[ v - 1 ] ); } _renderData.elements.push( _line ); } } } } for ( o = 0, ol = _renderData.sprites.length; o < ol; o++ ) { object = _renderData.sprites[ o ].object; _modelMatrix = object.matrixWorld; if ( object instanceof THREE.Particle ) { _vector4.set( _modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1 ); _vector4.applyMatrix4( _viewProjectionMatrix ); _vector4.z /= _vector4.w; if ( _vector4.z > 0 && _vector4.z < 1 ) { _particle = getNextParticleInPool(); _particle.id = object.id; _particle.x = _vector4.x / _vector4.w; _particle.y = _vector4.y / _vector4.w; _particle.z = _vector4.z; _particle.object = object; _particle.rotation = object.rotation.z; _particle.scale.x = object.scale.x * Math.abs( _particle.x - ( _vector4.x + camera.projectionMatrix.elements[0] ) / ( _vector4.w + camera.projectionMatrix.elements[12] ) ); _particle.scale.y = object.scale.y * Math.abs( _particle.y - ( _vector4.y + camera.projectionMatrix.elements[5] ) / ( _vector4.w + camera.projectionMatrix.elements[13] ) ); _particle.material = object.material; _renderData.elements.push( _particle ); } } } if ( sortElements === true ) _renderData.elements.sort( painterSort ); return _renderData; }; // Pools function getNextObjectInPool() { if ( _objectCount === _objectPoolLength ) { var object = new THREE.RenderableObject(); _objectPool.push( object ); _objectPoolLength ++; _objectCount ++; return object; } return _objectPool[ _objectCount ++ ]; } function getNextVertexInPool() { if ( _vertexCount === _vertexPoolLength ) { var vertex = new THREE.RenderableVertex(); _vertexPool.push( vertex ); _vertexPoolLength ++; _vertexCount ++; return vertex; } return _vertexPool[ _vertexCount ++ ]; } function getNextFace3InPool() { if ( _face3Count === _face3PoolLength ) { var face = new THREE.RenderableFace3(); _face3Pool.push( face ); _face3PoolLength ++; _face3Count ++; return face; } return _face3Pool[ _face3Count ++ ]; } function getNextFace4InPool() { if ( _face4Count === _face4PoolLength ) { var face = new THREE.RenderableFace4(); _face4Pool.push( face ); _face4PoolLength ++; _face4Count ++; return face; } return _face4Pool[ _face4Count ++ ]; } function getNextLineInPool() { if ( _lineCount === _linePoolLength ) { var line = new THREE.RenderableLine(); _linePool.push( line ); _linePoolLength ++; _lineCount ++ return line; } return _linePool[ _lineCount ++ ]; } function getNextParticleInPool() { if ( _particleCount === _particlePoolLength ) { var particle = new THREE.RenderableParticle(); _particlePool.push( particle ); _particlePoolLength ++; _particleCount ++ return particle; } return _particlePool[ _particleCount ++ ]; } // function painterSort( a, b ) { if ( a.z !== b.z ) { return b.z - a.z; } else if ( a.id !== b.id ) { return a.id - b.id; } else { return 0; } } function clipLine( s1, s2 ) { var alpha1 = 0, alpha2 = 1, // Calculate the boundary coordinate of each vertex for the near and far clip planes, // Z = -1 and Z = +1, respectively. bc1near = s1.z + s1.w, bc2near = s2.z + s2.w, bc1far = - s1.z + s1.w, bc2far = - s2.z + s2.w; if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) { // Both vertices lie entirely within all clip planes. return true; } else if ( ( bc1near < 0 && bc2near < 0) || (bc1far < 0 && bc2far < 0 ) ) { // Both vertices lie entirely outside one of the clip planes. return false; } else { // The line segment spans at least one clip plane. if ( bc1near < 0 ) { // v1 lies outside the near plane, v2 inside alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) ); } else if ( bc2near < 0 ) { // v2 lies outside the near plane, v1 inside alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) ); } if ( bc1far < 0 ) { // v1 lies outside the far plane, v2 inside alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) ); } else if ( bc2far < 0 ) { // v2 lies outside the far plane, v2 inside alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) ); } if ( alpha2 < alpha1 ) { // The line segment spans two boundaries, but is outside both of them. // (This can't happen when we're only clipping against just near/far but good // to leave the check here for future usage if other clip planes are added.) return false; } else { // Update the s1 and s2 vertices to match the clipped line segment. s1.lerp( s2, alpha1 ); s2.lerp( s1, 1 - alpha2 ); return true; } } } };