1 |
|
2 |
|
3 |
|
4 |
|
5 | THREE.SVGObject = function ( node ) {
|
6 |
|
7 | THREE.Object3D.call( this );
|
8 |
|
9 | this.node = node;
|
10 |
|
11 | };
|
12 |
|
13 | THREE.SVGObject.prototype = Object.create( THREE.Object3D.prototype );
|
14 | THREE.SVGObject.prototype.constructor = THREE.SVGObject;
|
15 |
|
16 | THREE.SVGRenderer = function () {
|
17 |
|
18 | var _this = this,
|
19 | _renderData, _elements, _lights,
|
20 | _projector = new THREE.Projector(),
|
21 | _svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ),
|
22 | _svgWidth, _svgHeight, _svgWidthHalf, _svgHeightHalf,
|
23 |
|
24 | _v1, _v2, _v3,
|
25 |
|
26 | _clipBox = new THREE.Box2(),
|
27 | _elemBox = new THREE.Box2(),
|
28 |
|
29 | _color = new THREE.Color(),
|
30 | _diffuseColor = new THREE.Color(),
|
31 | _ambientLight = new THREE.Color(),
|
32 | _directionalLights = new THREE.Color(),
|
33 | _pointLights = new THREE.Color(),
|
34 | _clearColor = new THREE.Color(),
|
35 | _clearAlpha = 1,
|
36 |
|
37 | _vector3 = new THREE.Vector3(),
|
38 | _centroid = new THREE.Vector3(),
|
39 | _normal = new THREE.Vector3(),
|
40 | _normalViewMatrix = new THREE.Matrix3(),
|
41 |
|
42 | _viewMatrix = new THREE.Matrix4(),
|
43 | _viewProjectionMatrix = new THREE.Matrix4(),
|
44 |
|
45 | _svgPathPool = [],
|
46 | _svgNode, _pathCount = 0,
|
47 |
|
48 | _currentPath, _currentStyle,
|
49 |
|
50 | _quality = 1, _precision = null;
|
51 |
|
52 | this.domElement = _svg;
|
53 |
|
54 | this.autoClear = true;
|
55 | this.sortObjects = true;
|
56 | this.sortElements = true;
|
57 |
|
58 | this.overdraw = 0.5;
|
59 |
|
60 | this.info = {
|
61 |
|
62 | render: {
|
63 |
|
64 | vertices: 0,
|
65 | faces: 0
|
66 |
|
67 | }
|
68 |
|
69 | };
|
70 |
|
71 | this.setQuality = function ( quality ) {
|
72 |
|
73 | switch ( quality ) {
|
74 |
|
75 | case "high": _quality = 1; break;
|
76 | case "low": _quality = 0; break;
|
77 |
|
78 | }
|
79 |
|
80 | };
|
81 |
|
82 | this.setClearColor = function ( color, alpha ) {
|
83 |
|
84 | _clearColor.set( color );
|
85 | _clearAlpha = alpha !== undefined ? alpha : 1;
|
86 |
|
87 | };
|
88 |
|
89 | this.setPixelRatio = function () {};
|
90 |
|
91 | this.setSize = function ( width, height ) {
|
92 |
|
93 | _svgWidth = width; _svgHeight = height;
|
94 | _svgWidthHalf = _svgWidth / 2; _svgHeightHalf = _svgHeight / 2;
|
95 |
|
96 | _svg.setAttribute( 'viewBox', ( - _svgWidthHalf ) + ' ' + ( - _svgHeightHalf ) + ' ' + _svgWidth + ' ' + _svgHeight );
|
97 | _svg.setAttribute( 'width', _svgWidth );
|
98 | _svg.setAttribute( 'height', _svgHeight );
|
99 |
|
100 | _clipBox.min.set( - _svgWidthHalf, - _svgHeightHalf );
|
101 | _clipBox.max.set( _svgWidthHalf, _svgHeightHalf );
|
102 |
|
103 | };
|
104 |
|
105 | this.setPrecision = function ( precision ) {
|
106 |
|
107 | _precision = precision;
|
108 |
|
109 | };
|
110 |
|
111 | function removeChildNodes() {
|
112 |
|
113 | _pathCount = 0;
|
114 |
|
115 | while ( _svg.childNodes.length > 0 ) {
|
116 |
|
117 | _svg.removeChild( _svg.childNodes[ 0 ] );
|
118 |
|
119 | }
|
120 |
|
121 | }
|
122 |
|
123 | function getSvgColor( color, opacity, asStroke ) {
|
124 |
|
125 | var arg = Math.floor( color.r * 255 ) + ',' + Math.floor( color.g * 255 ) + ',' + Math.floor( color.b * 255 );
|
126 |
|
127 | if ( opacity === undefined || opacity === 1 ) return 'rgb(' + arg + ')';
|
128 |
|
129 | return 'rgb(' + arg + ');' + ( asStroke ? 'stroke-opacity' : 'fill-opacity' ) + ':' + opacity;
|
130 |
|
131 | }
|
132 |
|
133 | function convert( c ) {
|
134 |
|
135 | return _precision !== null ? c.toFixed( _precision ) : c;
|
136 |
|
137 | }
|
138 |
|
139 | this.clear = function () {
|
140 |
|
141 | removeChildNodes();
|
142 | _svg.style.backgroundColor = getSvgColor( _clearColor, _clearAlpha );
|
143 |
|
144 | };
|
145 |
|
146 | this.render = function ( scene, camera ) {
|
147 |
|
148 | if ( camera instanceof THREE.Camera === false ) {
|
149 |
|
150 | console.error( 'THREE.SVGRenderer.render: camera is not an instance of THREE.Camera.' );
|
151 | return;
|
152 |
|
153 | }
|
154 |
|
155 | var background = scene.background;
|
156 |
|
157 | if ( background && background.isColor ) {
|
158 |
|
159 | removeChildNodes();
|
160 | _svg.style.backgroundColor = getSvgColor( background );
|
161 |
|
162 | } else if ( this.autoClear === true ) {
|
163 |
|
164 | this.clear();
|
165 |
|
166 | }
|
167 |
|
168 | _this.info.render.vertices = 0;
|
169 | _this.info.render.faces = 0;
|
170 |
|
171 | _viewMatrix.copy( camera.matrixWorldInverse );
|
172 | _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
|
173 |
|
174 | _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements );
|
175 | _elements = _renderData.elements;
|
176 | _lights = _renderData.lights;
|
177 |
|
178 | _normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse );
|
179 |
|
180 | calculateLights( _lights );
|
181 |
|
182 |
|
183 |
|
184 | _currentPath = '';
|
185 | _currentStyle = '';
|
186 |
|
187 | for ( var e = 0, el = _elements.length; e < el; e ++ ) {
|
188 |
|
189 | var element = _elements[ e ];
|
190 | var material = element.material;
|
191 |
|
192 | if ( material === undefined || material.opacity === 0 ) continue;
|
193 |
|
194 | _elemBox.makeEmpty();
|
195 |
|
196 | if ( element instanceof THREE.RenderableSprite ) {
|
197 |
|
198 | _v1 = element;
|
199 | _v1.x *= _svgWidthHalf; _v1.y *= - _svgHeightHalf;
|
200 |
|
201 | renderSprite( _v1, element, material );
|
202 |
|
203 | } else if ( element instanceof THREE.RenderableLine ) {
|
204 |
|
205 | _v1 = element.v1; _v2 = element.v2;
|
206 |
|
207 | _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
|
208 | _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
|
209 |
|
210 | _elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] );
|
211 |
|
212 | if ( _clipBox.intersectsBox( _elemBox ) === true ) {
|
213 |
|
214 | renderLine( _v1, _v2, element, material );
|
215 |
|
216 | }
|
217 |
|
218 | } else if ( element instanceof THREE.RenderableFace ) {
|
219 |
|
220 | _v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
|
221 |
|
222 | if ( _v1.positionScreen.z < - 1 || _v1.positionScreen.z > 1 ) continue;
|
223 | if ( _v2.positionScreen.z < - 1 || _v2.positionScreen.z > 1 ) continue;
|
224 | if ( _v3.positionScreen.z < - 1 || _v3.positionScreen.z > 1 ) continue;
|
225 |
|
226 | _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
|
227 | _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
|
228 | _v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf;
|
229 |
|
230 | if ( this.overdraw > 0 ) {
|
231 |
|
232 | expand( _v1.positionScreen, _v2.positionScreen, this.overdraw );
|
233 | expand( _v2.positionScreen, _v3.positionScreen, this.overdraw );
|
234 | expand( _v3.positionScreen, _v1.positionScreen, this.overdraw );
|
235 |
|
236 | }
|
237 |
|
238 | _elemBox.setFromPoints( [
|
239 | _v1.positionScreen,
|
240 | _v2.positionScreen,
|
241 | _v3.positionScreen
|
242 | ] );
|
243 |
|
244 | if ( _clipBox.intersectsBox( _elemBox ) === true ) {
|
245 |
|
246 | renderFace3( _v1, _v2, _v3, element, material );
|
247 |
|
248 | }
|
249 |
|
250 | }
|
251 |
|
252 | }
|
253 |
|
254 | flushPath();
|
255 |
|
256 | scene.traverseVisible( function ( object ) {
|
257 |
|
258 | if ( object instanceof THREE.SVGObject ) {
|
259 |
|
260 | _vector3.setFromMatrixPosition( object.matrixWorld );
|
261 | _vector3.applyMatrix4( _viewProjectionMatrix );
|
262 |
|
263 | if ( _vector3.z < - 1 || _vector3.z > 1 ) return;
|
264 |
|
265 | var x = _vector3.x * _svgWidthHalf;
|
266 | var y = - _vector3.y * _svgHeightHalf;
|
267 |
|
268 | var node = object.node;
|
269 | node.setAttribute( 'transform', 'translate(' + x + ',' + y + ')' );
|
270 |
|
271 | _svg.appendChild( node );
|
272 |
|
273 | }
|
274 |
|
275 | } );
|
276 |
|
277 | };
|
278 |
|
279 | function calculateLights( lights ) {
|
280 |
|
281 | _ambientLight.setRGB( 0, 0, 0 );
|
282 | _directionalLights.setRGB( 0, 0, 0 );
|
283 | _pointLights.setRGB( 0, 0, 0 );
|
284 |
|
285 | for ( var l = 0, ll = lights.length; l < ll; l ++ ) {
|
286 |
|
287 | var light = lights[ l ];
|
288 | var lightColor = light.color;
|
289 |
|
290 | if ( light.isAmbientLight ) {
|
291 |
|
292 | _ambientLight.r += lightColor.r;
|
293 | _ambientLight.g += lightColor.g;
|
294 | _ambientLight.b += lightColor.b;
|
295 |
|
296 | } else if ( light.isDirectionalLight ) {
|
297 |
|
298 | _directionalLights.r += lightColor.r;
|
299 | _directionalLights.g += lightColor.g;
|
300 | _directionalLights.b += lightColor.b;
|
301 |
|
302 | } else if ( light.isPointLight ) {
|
303 |
|
304 | _pointLights.r += lightColor.r;
|
305 | _pointLights.g += lightColor.g;
|
306 | _pointLights.b += lightColor.b;
|
307 |
|
308 | }
|
309 |
|
310 | }
|
311 |
|
312 | }
|
313 |
|
314 | function calculateLight( lights, position, normal, color ) {
|
315 |
|
316 | for ( var l = 0, ll = lights.length; l < ll; l ++ ) {
|
317 |
|
318 | var light = lights[ l ];
|
319 | var lightColor = light.color;
|
320 |
|
321 | if ( light.isDirectionalLight ) {
|
322 |
|
323 | var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize();
|
324 |
|
325 | var amount = normal.dot( lightPosition );
|
326 |
|
327 | if ( amount <= 0 ) continue;
|
328 |
|
329 | amount *= light.intensity;
|
330 |
|
331 | color.r += lightColor.r * amount;
|
332 | color.g += lightColor.g * amount;
|
333 | color.b += lightColor.b * amount;
|
334 |
|
335 | } else if ( light.isPointLight ) {
|
336 |
|
337 | var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld );
|
338 |
|
339 | var amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() );
|
340 |
|
341 | if ( amount <= 0 ) continue;
|
342 |
|
343 | amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 );
|
344 |
|
345 | if ( amount == 0 ) continue;
|
346 |
|
347 | amount *= light.intensity;
|
348 |
|
349 | color.r += lightColor.r * amount;
|
350 | color.g += lightColor.g * amount;
|
351 | color.b += lightColor.b * amount;
|
352 |
|
353 | }
|
354 |
|
355 | }
|
356 |
|
357 | }
|
358 |
|
359 | function renderSprite( v1, element, material ) {
|
360 |
|
361 | var scaleX = element.scale.x * _svgWidthHalf;
|
362 | var scaleY = element.scale.y * _svgHeightHalf;
|
363 |
|
364 | if ( material.isPointsMaterial ) {
|
365 |
|
366 | scaleX *= material.size;
|
367 | scaleY *= material.size;
|
368 |
|
369 | }
|
370 |
|
371 | var path = 'M' + convert( v1.x - scaleX * 0.5 ) + ',' + convert( v1.y - scaleY * 0.5 ) + 'h' + convert( scaleX ) + 'v' + convert( scaleY ) + 'h' + convert( - scaleX ) + 'z';
|
372 | var style = "";
|
373 |
|
374 | if ( material.isSpriteMaterial || material.isPointsMaterial ) {
|
375 |
|
376 | style = 'fill:' + getSvgColor( material.color, material.opacity );
|
377 |
|
378 | }
|
379 |
|
380 | addPath( style, path );
|
381 |
|
382 | }
|
383 |
|
384 | function renderLine( v1, v2, element, material ) {
|
385 |
|
386 | var path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y );
|
387 |
|
388 | if ( material.isLineBasicMaterial ) {
|
389 |
|
390 | var style = 'fill:none;stroke:' + getSvgColor( material.color, material.opacity, true ) + ';stroke-width:' + material.linewidth + ';stroke-linecap:' + material.linecap;
|
391 |
|
392 | if ( material.isLineDashedMaterial ) {
|
393 |
|
394 | style = style + ';stroke-dasharray:' + material.dashSize + "," + material.gapSize;
|
395 |
|
396 | }
|
397 |
|
398 | addPath( style, path );
|
399 |
|
400 | }
|
401 |
|
402 | }
|
403 |
|
404 | function renderFace3( v1, v2, v3, element, material ) {
|
405 |
|
406 | _this.info.render.vertices += 3;
|
407 | _this.info.render.faces ++;
|
408 |
|
409 | var path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y ) + 'L' + convert( v3.positionScreen.x ) + ',' + convert( v3.positionScreen.y ) + 'z';
|
410 | var style = '';
|
411 |
|
412 | if ( material.isMeshBasicMaterial ) {
|
413 |
|
414 | _color.copy( material.color );
|
415 |
|
416 | if ( material.vertexColors === THREE.FaceColors || material.vertexColors === THREE.VertexColors ) {
|
417 |
|
418 | _color.multiply( element.color );
|
419 |
|
420 | }
|
421 |
|
422 | } else if ( material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) {
|
423 |
|
424 | _diffuseColor.copy( material.color );
|
425 |
|
426 | if ( material.vertexColors === THREE.FaceColors || material.vertexColors === THREE.VertexColors ) {
|
427 |
|
428 | _diffuseColor.multiply( element.color );
|
429 |
|
430 | }
|
431 |
|
432 | _color.copy( _ambientLight );
|
433 |
|
434 | _centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 );
|
435 |
|
436 | calculateLight( _lights, _centroid, element.normalModel, _color );
|
437 |
|
438 | _color.multiply( _diffuseColor ).add( material.emissive );
|
439 |
|
440 | } else if ( material.isMeshNormalMaterial ) {
|
441 |
|
442 | _normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix ).normalize();
|
443 |
|
444 | _color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
|
445 |
|
446 | }
|
447 |
|
448 | if ( material.wireframe ) {
|
449 |
|
450 | style = 'fill:none;stroke:' + getSvgColor( _color, material.opacity, true ) + ';stroke-width:' + material.wireframeLinewidth + ';stroke-linecap:' + material.wireframeLinecap + ';stroke-linejoin:' + material.wireframeLinejoin;
|
451 |
|
452 | } else {
|
453 |
|
454 | style = 'fill:' + getSvgColor( _color, material.opacity );
|
455 |
|
456 | }
|
457 |
|
458 | addPath( style, path );
|
459 |
|
460 | }
|
461 |
|
462 |
|
463 |
|
464 | function expand( v1, v2, pixels ) {
|
465 |
|
466 | var x = v2.x - v1.x, y = v2.y - v1.y,
|
467 | det = x * x + y * y, idet;
|
468 |
|
469 | if ( det === 0 ) return;
|
470 |
|
471 | idet = pixels / Math.sqrt( det );
|
472 |
|
473 | x *= idet; y *= idet;
|
474 |
|
475 | v2.x += x; v2.y += y;
|
476 | v1.x -= x; v1.y -= y;
|
477 |
|
478 | }
|
479 |
|
480 | function addPath( style, path ) {
|
481 |
|
482 | if ( _currentStyle === style ) {
|
483 |
|
484 | _currentPath += path;
|
485 |
|
486 | } else {
|
487 |
|
488 | flushPath();
|
489 |
|
490 | _currentStyle = style;
|
491 | _currentPath = path;
|
492 |
|
493 | }
|
494 |
|
495 | }
|
496 |
|
497 | function flushPath() {
|
498 |
|
499 | if ( _currentPath ) {
|
500 |
|
501 | _svgNode = getPathNode( _pathCount ++ );
|
502 | _svgNode.setAttribute( 'd', _currentPath );
|
503 | _svgNode.setAttribute( 'style', _currentStyle );
|
504 | _svg.appendChild( _svgNode );
|
505 |
|
506 | }
|
507 |
|
508 | _currentPath = '';
|
509 | _currentStyle = '';
|
510 |
|
511 | }
|
512 |
|
513 | function getPathNode( id ) {
|
514 |
|
515 | if ( _svgPathPool[ id ] == null ) {
|
516 |
|
517 | _svgPathPool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
|
518 |
|
519 | if ( _quality == 0 ) {
|
520 |
|
521 | _svgPathPool[ id ].setAttribute( 'shape-rendering', 'crispEdges' );
|
522 |
|
523 | }
|
524 |
|
525 | return _svgPathPool[ id ];
|
526 |
|
527 | }
|
528 |
|
529 | return _svgPathPool[ id ];
|
530 |
|
531 | }
|
532 |
|
533 | };
|