UNPKG

12.5 kBJavaScriptView Raw
1/**
2 * @author mrdoob / http://mrdoob.com/
3 */
4
5THREE.SVGObject = function ( node ) {
6
7 THREE.Object3D.call( this );
8
9 this.node = node;
10
11};
12
13THREE.SVGObject.prototype = Object.create( THREE.Object3D.prototype );
14THREE.SVGObject.prototype.constructor = THREE.SVGObject;
15
16THREE.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(), // Needed for PointLight
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 // reset accumulated path
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(); // just to flush last svg:path
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 // Hide anti-alias gaps
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' ); //optimizeSpeed
522
523 }
524
525 return _svgPathPool[ id ];
526
527 }
528
529 return _svgPathPool[ id ];
530
531 }
532
533};