/** * @author mrdoob / http://mrdoob.com/ */ THREE.SVGObject = function ( node ) { THREE.Object3D.call( this ); this.node = node; }; THREE.SVGObject.prototype = Object.create( THREE.Object3D.prototype ); THREE.SVGObject.prototype.constructor = THREE.SVGObject; THREE.SVGRenderer = function () { console.log( 'THREE.SVGRenderer', THREE.REVISION ); var _this = this, _renderData, _elements, _lights, _projector = new THREE.Projector(), _svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), _svgWidth, _svgHeight, _svgWidthHalf, _svgHeightHalf, _v1, _v2, _v3, _v4, _clipBox = new THREE.Box2(), _elemBox = new THREE.Box2(), _color = new THREE.Color(), _diffuseColor = new THREE.Color(), _ambientLight = new THREE.Color(), _directionalLights = new THREE.Color(), _pointLights = new THREE.Color(), _clearColor = new THREE.Color(), _clearAlpha = 1, _w, // z-buffer to w-buffer _vector3 = new THREE.Vector3(), // Needed for PointLight _centroid = new THREE.Vector3(), _normal = new THREE.Vector3(), _normalViewMatrix = new THREE.Matrix3(), _viewMatrix = new THREE.Matrix4(), _viewProjectionMatrix = new THREE.Matrix4(), _svgPathPool = [], _svgLinePool = [], _svgRectPool = [], _svgNode, _pathCount = 0, _lineCount = 0, _rectCount = 0, _quality = 1; this.domElement = _svg; this.autoClear = true; this.sortObjects = true; this.sortElements = true; this.info = { render: { vertices: 0, faces: 0 } } this.setQuality = function( quality ) { switch (quality) { case "high": _quality = 1; break; case "low": _quality = 0; break; } }; // WebGLRenderer compatibility this.supportsVertexTextures = function () {}; this.setFaceCulling = function () {}; this.setClearColor = function ( color, alpha ) { _clearColor.set( color ); _clearAlpha = alpha !== undefined ? alpha : 1; }; this.setPixelRatio = function () {}; this.setSize = function( width, height ) { _svgWidth = width; _svgHeight = height; _svgWidthHalf = _svgWidth / 2; _svgHeightHalf = _svgHeight / 2; _svg.setAttribute( 'viewBox', ( - _svgWidthHalf ) + ' ' + ( - _svgHeightHalf ) + ' ' + _svgWidth + ' ' + _svgHeight ); _svg.setAttribute( 'width', _svgWidth ); _svg.setAttribute( 'height', _svgHeight ); _clipBox.min.set( - _svgWidthHalf, - _svgHeightHalf ); _clipBox.max.set( _svgWidthHalf, _svgHeightHalf ); }; this.clear = function () { _pathCount = 0; _lineCount = 0; _rectCount = 0; while ( _svg.childNodes.length > 0 ) { _svg.removeChild( _svg.childNodes[ 0 ] ); } _svg.style.backgroundColor = 'rgba(' + ( ( _clearColor.r * 255 ) | 0 ) + ',' + ( ( _clearColor.g * 255 ) | 0 ) + ',' + ( ( _clearColor.b * 255 ) | 0 ) + ',' + _clearAlpha + ')'; }; this.render = function ( scene, camera ) { if ( camera instanceof THREE.Camera === false ) { console.error( 'THREE.SVGRenderer.render: camera is not an instance of THREE.Camera.' ); return; } if ( this.autoClear === true ) this.clear(); _this.info.render.vertices = 0; _this.info.render.faces = 0; _viewMatrix.copy( camera.matrixWorldInverse.getInverse( camera.matrixWorld ) ); _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements ); _elements = _renderData.elements; _lights = _renderData.lights; _normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse ); calculateLights( _lights ); for ( var e = 0, el = _elements.length; e < el; e ++ ) { var element = _elements[ e ]; var material = element.material; if ( material === undefined || material.opacity === 0 ) continue; _elemBox.makeEmpty(); if ( element instanceof THREE.RenderableSprite ) { _v1 = element; _v1.x *= _svgWidthHalf; _v1.y *= - _svgHeightHalf; renderSprite( _v1, element, material ); } else if ( element instanceof THREE.RenderableLine ) { _v1 = element.v1; _v2 = element.v2; _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf; _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf; _elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] ); if ( _clipBox.isIntersectionBox( _elemBox ) === true ) { renderLine( _v1, _v2, element, material ); } } else if ( element instanceof THREE.RenderableFace ) { _v1 = element.v1; _v2 = element.v2; _v3 = element.v3; if ( _v1.positionScreen.z < -1 || _v1.positionScreen.z > 1 ) continue; if ( _v2.positionScreen.z < -1 || _v2.positionScreen.z > 1 ) continue; if ( _v3.positionScreen.z < -1 || _v3.positionScreen.z > 1 ) continue; _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf; _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf; _v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf; _elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen, _v3.positionScreen ] ); if ( _clipBox.isIntersectionBox( _elemBox ) === true ) { renderFace3( _v1, _v2, _v3, element, material ); } } } scene.traverseVisible( function ( object ) { if ( object instanceof THREE.SVGObject ) { _vector3.setFromMatrixPosition( object.matrixWorld ); _vector3.applyProjection( _viewProjectionMatrix ); var x = _vector3.x * _svgWidthHalf; var y = - _vector3.y * _svgHeightHalf; var node = object.node; node.setAttribute( 'transform', 'translate(' + x + ',' + y + ')' ); _svg.appendChild( node ); } } ); }; function calculateLights( lights ) { _ambientLight.setRGB( 0, 0, 0 ); _directionalLights.setRGB( 0, 0, 0 ); _pointLights.setRGB( 0, 0, 0 ); for ( var l = 0, ll = lights.length; l < ll; l ++ ) { var light = lights[ l ]; var lightColor = light.color; if ( light instanceof THREE.AmbientLight ) { _ambientLight.r += lightColor.r; _ambientLight.g += lightColor.g; _ambientLight.b += lightColor.b; } else if ( light instanceof THREE.DirectionalLight ) { _directionalLights.r += lightColor.r; _directionalLights.g += lightColor.g; _directionalLights.b += lightColor.b; } else if ( light instanceof THREE.PointLight ) { _pointLights.r += lightColor.r; _pointLights.g += lightColor.g; _pointLights.b += lightColor.b; } } } function calculateLight( lights, position, normal, color ) { for ( var l = 0, ll = lights.length; l < ll; l ++ ) { var light = lights[ l ]; var lightColor = light.color; if ( light instanceof THREE.DirectionalLight ) { var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize(); var amount = normal.dot( lightPosition ); if ( amount <= 0 ) continue; amount *= light.intensity; color.r += lightColor.r * amount; color.g += lightColor.g * amount; color.b += lightColor.b * amount; } else if ( light instanceof THREE.PointLight ) { var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ); var amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() ); if ( amount <= 0 ) continue; amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 ); if ( amount == 0 ) continue; amount *= light.intensity; color.r += lightColor.r * amount; color.g += lightColor.g * amount; color.b += lightColor.b * amount; } } } function renderSprite( v1, element, material ) { var scaleX = element.scale.x * _svgWidthHalf; var scaleY = element.scale.y * _svgHeightHalf; _svgNode = getRectNode( _rectCount ++ ); _svgNode.setAttribute( 'x', v1.x - ( scaleX * 0.5 ) ); _svgNode.setAttribute( 'y', v1.y - ( scaleY * 0.5 ) ); _svgNode.setAttribute( 'width', scaleX ); _svgNode.setAttribute( 'height', scaleY ); if ( material instanceof THREE.SpriteMaterial ) { _svgNode.setAttribute( 'style', 'fill: ' + material.color.getStyle() ); } _svg.appendChild( _svgNode ); } function renderLine( v1, v2, element, material ) { _svgNode = getLineNode( _lineCount ++ ); _svgNode.setAttribute( 'x1', v1.positionScreen.x ); _svgNode.setAttribute( 'y1', v1.positionScreen.y ); _svgNode.setAttribute( 'x2', v2.positionScreen.x ); _svgNode.setAttribute( 'y2', v2.positionScreen.y ); if ( material instanceof THREE.LineBasicMaterial ) { _svgNode.setAttribute( 'style', 'fill: none; stroke: ' + material.color.getStyle() + '; stroke-width: ' + material.linewidth + '; stroke-opacity: ' + material.opacity + '; stroke-linecap: ' + material.linecap + '; stroke-linejoin: ' + material.linejoin ); _svg.appendChild( _svgNode ); } } function renderFace3( v1, v2, v3, element, material ) { _this.info.render.vertices += 3; _this.info.render.faces ++; _svgNode = getPathNode( _pathCount ++ ); _svgNode.setAttribute( 'd', 'M ' + v1.positionScreen.x + ' ' + v1.positionScreen.y + ' L ' + v2.positionScreen.x + ' ' + v2.positionScreen.y + ' L ' + v3.positionScreen.x + ',' + v3.positionScreen.y + 'z' ); if ( material instanceof THREE.MeshBasicMaterial ) { _color.copy( material.color ); if ( material.vertexColors === THREE.FaceColors ) { _color.multiply( element.color ); } } else if ( material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) { _diffuseColor.copy( material.color ); if ( material.vertexColors === THREE.FaceColors ) { _diffuseColor.multiply( element.color ); } _color.copy( _ambientLight ); _centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 ); calculateLight( _lights, _centroid, element.normalModel, _color ); _color.multiply( _diffuseColor ).add( material.emissive ); } else if ( material instanceof THREE.MeshDepthMaterial ) { _w = 1 - ( material.__2near / (material.__farPlusNear - element.z * material.__farMinusNear) ); _color.setRGB( _w, _w, _w ); } else if ( material instanceof THREE.MeshNormalMaterial ) { _normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix ); _color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); } if ( material.wireframe ) { _svgNode.setAttribute( 'style', 'fill: none; stroke: ' + _color.getStyle() + '; stroke-width: ' + material.wireframeLinewidth + '; stroke-opacity: ' + material.opacity + '; stroke-linecap: ' + material.wireframeLinecap + '; stroke-linejoin: ' + material.wireframeLinejoin ); } else { _svgNode.setAttribute( 'style', 'fill: ' + _color.getStyle() + '; fill-opacity: ' + material.opacity ); } _svg.appendChild( _svgNode ); } function getLineNode( id ) { if ( _svgLinePool[ id ] == null ) { _svgLinePool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'line' ); if ( _quality == 0 ) { _svgLinePool[ id ].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed } return _svgLinePool[ id ]; } return _svgLinePool[ id ]; } function getPathNode( id ) { if ( _svgPathPool[ id ] == null ) { _svgPathPool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); if ( _quality == 0 ) { _svgPathPool[ id ].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed } return _svgPathPool[ id ]; } return _svgPathPool[ id ]; } function getRectNode( id ) { if ( _svgRectPool[ id ] == null ) { _svgRectPool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'rect' ); if ( _quality == 0 ) { _svgRectPool[ id ].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed } return _svgRectPool[ id ]; } return _svgRectPool[ id ]; } };