1/**
2 * @author mrdoob / http://mrdoob.com/
3 */
4
5THREE.SpriteCanvasMaterial = function ( parameters ) {
6
7	THREE.Material.call( this );
8
9	this.type = 'SpriteCanvasMaterial';
10
11	this.color = new THREE.Color( 0xffffff );
12	this.program = function () {};
13
14	this.setValues( parameters );
15
16};
17
18THREE.SpriteCanvasMaterial.prototype = Object.create( THREE.Material.prototype );
19THREE.SpriteCanvasMaterial.prototype.constructor = THREE.SpriteCanvasMaterial;
20THREE.SpriteCanvasMaterial.prototype.isSpriteCanvasMaterial = true;
21
22THREE.SpriteCanvasMaterial.prototype.clone = function () {
23
24	var material = new THREE.SpriteCanvasMaterial();
25
26	material.copy( this );
27	material.color.copy( this.color );
28	material.program = this.program;
29
30	return material;
31
32};
33
34//
35
36THREE.CanvasRenderer = function ( parameters ) {
37
38	console.log( 'THREE.CanvasRenderer', THREE.REVISION );
39
40	parameters = parameters || {};
41
42	var _this = this,
43		_renderData, _elements, _lights,
44		_projector = new THREE.Projector(),
45
46		_canvas = parameters.canvas !== undefined
47				 ? parameters.canvas
48				 : document.createElement( 'canvas' ),
49
50		_canvasWidth = _canvas.width,
51		_canvasHeight = _canvas.height,
52		_canvasWidthHalf = Math.floor( _canvasWidth / 2 ),
53		_canvasHeightHalf = Math.floor( _canvasHeight / 2 ),
54
55		_viewportX = 0,
56		_viewportY = 0,
57		_viewportWidth = _canvasWidth,
58		_viewportHeight = _canvasHeight,
59
60		_pixelRatio = 1,
61
62		_context = _canvas.getContext( '2d', {
63			alpha: parameters.alpha === true
64		} ),
65
66		_clearColor = new THREE.Color( 0x000000 ),
67		_clearAlpha = parameters.alpha === true ? 0 : 1,
68
69		_contextGlobalAlpha = 1,
70		_contextGlobalCompositeOperation = 0,
71		_contextStrokeStyle = null,
72		_contextFillStyle = null,
73		_contextLineWidth = null,
74		_contextLineCap = null,
75		_contextLineJoin = null,
76		_contextLineDash = [],
77
78		_v1, _v2, _v3,
79
80		_v1x, _v1y, _v2x, _v2y, _v3x, _v3y,
81
82		_color = new THREE.Color(),
83
84		_diffuseColor = new THREE.Color(),
85		_emissiveColor = new THREE.Color(),
86
87		_lightColor = new THREE.Color(),
88
89		_patterns = {},
90
91		_uvs,
92		_uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y,
93
94		_clipBox = new THREE.Box2(),
95		_clearBox = new THREE.Box2(),
96		_elemBox = new THREE.Box2(),
97
98		_ambientLight = new THREE.Color(),
99		_directionalLights = new THREE.Color(),
100		_pointLights = new THREE.Color(),
101
102		_vector3 = new THREE.Vector3(), // Needed for PointLight
103		_centroid = new THREE.Vector3(),
104		_normal = new THREE.Vector3(),
105		_normalViewMatrix = new THREE.Matrix3();
106
107	/* TODO
108	_canvas.mozImageSmoothingEnabled = false;
109	_canvas.webkitImageSmoothingEnabled = false;
110	_canvas.msImageSmoothingEnabled = false;
111	_canvas.imageSmoothingEnabled = false;
112	*/
113
114	// dash+gap fallbacks for Firefox and everything else
115
116	if ( _context.setLineDash === undefined ) {
117
118		_context.setLineDash = function () {};
119
120	}
121
122	this.domElement = _canvas;
123
124	this.autoClear = true;
125	this.sortObjects = true;
126	this.sortElements = true;
127
128	this.info = {
129
130		render: {
131
132			vertices: 0,
133			faces: 0
134
135		}
136
137	};
138
139	// WebGLRenderer compatibility
140
141	this.supportsVertexTextures = function () {};
142	this.setFaceCulling = function () {};
143
144	// API
145
146	this.getContext = function () {
147
148		return _context;
149
150	};
151
152	this.getContextAttributes = function () {
153
154		return _context.getContextAttributes();
155
156	};
157
158	this.getPixelRatio = function () {
159
160		return _pixelRatio;
161
162	};
163
164	this.setPixelRatio = function ( value ) {
165
166		if ( value !== undefined ) _pixelRatio = value;
167
168	};
169
170	this.setSize = function ( width, height, updateStyle ) {
171
172		_canvasWidth = width * _pixelRatio;
173		_canvasHeight = height * _pixelRatio;
174
175		_canvas.width = _canvasWidth;
176		_canvas.height = _canvasHeight;
177
178		_canvasWidthHalf = Math.floor( _canvasWidth / 2 );
179		_canvasHeightHalf = Math.floor( _canvasHeight / 2 );
180
181		if ( updateStyle !== false ) {
182
183			_canvas.style.width = width + 'px';
184			_canvas.style.height = height + 'px';
185
186		}
187
188		_clipBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf );
189		_clipBox.max.set(   _canvasWidthHalf,   _canvasHeightHalf );
190
191		_clearBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf );
192		_clearBox.max.set(   _canvasWidthHalf,   _canvasHeightHalf );
193
194		_contextGlobalAlpha = 1;
195		_contextGlobalCompositeOperation = 0;
196		_contextStrokeStyle = null;
197		_contextFillStyle = null;
198		_contextLineWidth = null;
199		_contextLineCap = null;
200		_contextLineJoin = null;
201
202		this.setViewport( 0, 0, width, height );
203
204	};
205
206	this.setViewport = function ( x, y, width, height ) {
207
208		_viewportX = x * _pixelRatio;
209		_viewportY = y * _pixelRatio;
210
211		_viewportWidth = width * _pixelRatio;
212		_viewportHeight = height * _pixelRatio;
213
214	};
215
216	this.setScissor = function () {};
217	this.setScissorTest = function () {};
218
219	this.setClearColor = function ( color, alpha ) {
220
221		_clearColor.set( color );
222		_clearAlpha = alpha !== undefined ? alpha : 1;
223
224		_clearBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf );
225		_clearBox.max.set(   _canvasWidthHalf,   _canvasHeightHalf );
226
227	};
228
229	this.setClearColorHex = function ( hex, alpha ) {
230
231		console.warn( 'THREE.CanvasRenderer: .setClearColorHex() is being removed. Use .setClearColor() instead.' );
232		this.setClearColor( hex, alpha );
233
234	};
235
236	this.getClearColor = function () {
237
238		return _clearColor;
239
240	};
241
242	this.getClearAlpha = function () {
243
244		return _clearAlpha;
245
246	};
247
248	this.getMaxAnisotropy = function () {
249
250		return 0;
251
252	};
253
254	this.clear = function () {
255
256		if ( _clearBox.isEmpty() === false ) {
257
258			_clearBox.intersect( _clipBox );
259			_clearBox.expandByScalar( 2 );
260
261			_clearBox.min.x =   _clearBox.min.x + _canvasWidthHalf;
262			_clearBox.min.y = - _clearBox.min.y + _canvasHeightHalf;		// higher y value !
263			_clearBox.max.x =   _clearBox.max.x + _canvasWidthHalf;
264			_clearBox.max.y = - _clearBox.max.y + _canvasHeightHalf;		// lower y value !
265
266			if ( _clearAlpha < 1 ) {
267
268				_context.clearRect(
269					_clearBox.min.x | 0,
270					_clearBox.max.y | 0,
271					( _clearBox.max.x - _clearBox.min.x ) | 0,
272					( _clearBox.min.y - _clearBox.max.y ) | 0
273				);
274
275			}
276
277			if ( _clearAlpha > 0 ) {
278
279				setOpacity( 1 );
280				setBlending( THREE.NormalBlending );
281
282				setFillStyle( 'rgba(' + Math.floor( _clearColor.r * 255 ) + ',' + Math.floor( _clearColor.g * 255 ) + ',' + Math.floor( _clearColor.b * 255 ) + ',' + _clearAlpha + ')' );
283
284				_context.fillRect(
285					_clearBox.min.x | 0,
286					_clearBox.max.y | 0,
287					( _clearBox.max.x - _clearBox.min.x ) | 0,
288					( _clearBox.min.y - _clearBox.max.y ) | 0
289				);
290
291			}
292
293			_clearBox.makeEmpty();
294
295		}
296
297	};
298
299	// compatibility
300
301	this.clearColor = function () {};
302	this.clearDepth = function () {};
303	this.clearStencil = function () {};
304
305	this.render = function ( scene, camera ) {
306
307		if ( camera.isCamera === undefined ) {
308
309			console.error( 'THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.' );
310			return;
311
312		}
313
314		var background = scene.background;
315
316		if ( background && background.isColor ) {
317
318			setOpacity( 1 );
319			setBlending( THREE.NormalBlending );
320
321			setFillStyle( background.getStyle() );
322			_context.fillRect( 0, 0, _canvasWidth, _canvasHeight );
323
324		} else if ( this.autoClear === true ) {
325
326			this.clear();
327
328		}
329
330		_this.info.render.vertices = 0;
331		_this.info.render.faces = 0;
332
333		_context.setTransform( _viewportWidth / _canvasWidth, 0, 0, - _viewportHeight / _canvasHeight, _viewportX, _canvasHeight - _viewportY );
334		_context.translate( _canvasWidthHalf, _canvasHeightHalf );
335
336		_renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements );
337		_elements = _renderData.elements;
338		_lights = _renderData.lights;
339
340		_normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse );
341
342		/* DEBUG
343		setFillStyle( 'rgba( 0, 255, 255, 0.5 )' );
344		_context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y );
345		*/
346
347		calculateLights();
348
349		for ( var e = 0, el = _elements.length; e < el; e ++ ) {
350
351			var element = _elements[ e ];
352
353			var material = element.material;
354
355			if ( material === undefined || material.opacity === 0 ) continue;
356
357			_elemBox.makeEmpty();
358
359			if ( element instanceof THREE.RenderableSprite ) {
360
361				_v1 = element;
362				_v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf;
363
364				renderSprite( _v1, element, material );
365
366			} else if ( element instanceof THREE.RenderableLine ) {
367
368				_v1 = element.v1; _v2 = element.v2;
369
370				_v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
371				_v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
372
373				_elemBox.setFromPoints( [
374					_v1.positionScreen,
375					_v2.positionScreen
376				] );
377
378				if ( _clipBox.intersectsBox( _elemBox ) === true ) {
379
380					renderLine( _v1, _v2, element, material );
381
382				}
383
384			} else if ( element instanceof THREE.RenderableFace ) {
385
386				_v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
387
388				if ( _v1.positionScreen.z < - 1 || _v1.positionScreen.z > 1 ) continue;
389				if ( _v2.positionScreen.z < - 1 || _v2.positionScreen.z > 1 ) continue;
390				if ( _v3.positionScreen.z < - 1 || _v3.positionScreen.z > 1 ) continue;
391
392				_v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
393				_v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
394				_v3.positionScreen.x *= _canvasWidthHalf; _v3.positionScreen.y *= _canvasHeightHalf;
395
396				if ( material.overdraw > 0 ) {
397
398					expand( _v1.positionScreen, _v2.positionScreen, material.overdraw );
399					expand( _v2.positionScreen, _v3.positionScreen, material.overdraw );
400					expand( _v3.positionScreen, _v1.positionScreen, material.overdraw );
401
402				}
403
404				_elemBox.setFromPoints( [
405					_v1.positionScreen,
406					_v2.positionScreen,
407					_v3.positionScreen
408				] );
409
410				if ( _clipBox.intersectsBox( _elemBox ) === true ) {
411
412					renderFace3( _v1, _v2, _v3, 0, 1, 2, element, material );
413
414				}
415
416			}
417
418			/* DEBUG
419			setLineWidth( 1 );
420			setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' );
421			_context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y );
422			*/
423
424			_clearBox.union( _elemBox );
425
426		}
427
428		/* DEBUG
429		setLineWidth( 1 );
430		setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' );
431		_context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y );
432		*/
433
434		_context.setTransform( 1, 0, 0, 1, 0, 0 );
435
436	};
437
438	//
439
440	function calculateLights() {
441
442		_ambientLight.setRGB( 0, 0, 0 );
443		_directionalLights.setRGB( 0, 0, 0 );
444		_pointLights.setRGB( 0, 0, 0 );
445
446		for ( var l = 0, ll = _lights.length; l < ll; l ++ ) {
447
448			var light = _lights[ l ];
449			var lightColor = light.color;
450
451			if ( light.isAmbientLight ) {
452
453				_ambientLight.add( lightColor );
454
455			} else if ( light.isDirectionalLight ) {
456
457				// for sprites
458
459				_directionalLights.add( lightColor );
460
461			} else if ( light.isPointLight ) {
462
463				// for sprites
464
465				_pointLights.add( lightColor );
466
467			}
468
469		}
470
471	}
472
473	function calculateLight( position, normal, color ) {
474
475		for ( var l = 0, ll = _lights.length; l < ll; l ++ ) {
476
477			var light = _lights[ l ];
478
479			_lightColor.copy( light.color );
480
481			if ( light.isDirectionalLight ) {
482
483				var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize();
484
485				var amount = normal.dot( lightPosition );
486
487				if ( amount <= 0 ) continue;
488
489				amount *= light.intensity;
490
491				color.add( _lightColor.multiplyScalar( amount ) );
492
493			} else if ( light.isPointLight ) {
494
495				var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld );
496
497				var amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() );
498
499				if ( amount <= 0 ) continue;
500
501				amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 );
502
503				if ( amount == 0 ) continue;
504
505				amount *= light.intensity;
506
507				color.add( _lightColor.multiplyScalar( amount ) );
508
509			}
510
511		}
512
513	}
514
515	function renderSprite( v1, element, material ) {
516
517		setOpacity( material.opacity );
518		setBlending( material.blending );
519
520		var scaleX = element.scale.x * _canvasWidthHalf;
521		var scaleY = element.scale.y * _canvasHeightHalf;
522
523		var dist = Math.sqrt( scaleX * scaleX + scaleY * scaleY ); // allow for rotated sprite
524		_elemBox.min.set( v1.x - dist, v1.y - dist );
525		_elemBox.max.set( v1.x + dist, v1.y + dist );
526
527		if ( material.isSpriteMaterial ) {
528
529			var texture = material.map;
530
531			if ( texture !== null ) {
532
533				var pattern = _patterns[ texture.id ];
534
535				if ( pattern === undefined || pattern.version !== texture.version ) {
536
537					pattern = textureToPattern( texture );
538					_patterns[ texture.id ] = pattern;
539
540				}
541
542				if ( pattern.canvas !== undefined ) {
543
544					setFillStyle( pattern.canvas );
545
546					var bitmap = texture.image;
547
548					var ox = bitmap.width * texture.offset.x;
549					var oy = bitmap.height * texture.offset.y;
550
551					var sx = bitmap.width * texture.repeat.x;
552					var sy = bitmap.height * texture.repeat.y;
553
554					var cx = scaleX / sx;
555					var cy = scaleY / sy;
556
557					_context.save();
558					_context.translate( v1.x, v1.y );
559					if ( material.rotation !== 0 ) _context.rotate( material.rotation );
560					_context.translate( - scaleX / 2, - scaleY / 2 );
561					_context.scale( cx, cy );
562					_context.translate( - ox, - oy );
563					_context.fillRect( ox, oy, sx, sy );
564					_context.restore();
565
566				}
567
568			} else {
569
570				// no texture
571
572				setFillStyle( material.color.getStyle() );
573
574				_context.save();
575				_context.translate( v1.x, v1.y );
576				if ( material.rotation !== 0 ) _context.rotate( material.rotation );
577				_context.scale( scaleX, - scaleY );
578				_context.fillRect( - 0.5, - 0.5, 1, 1 );
579				_context.restore();
580
581			}
582
583		} else if ( material.isSpriteCanvasMaterial ) {
584
585			setStrokeStyle( material.color.getStyle() );
586			setFillStyle( material.color.getStyle() );
587
588			_context.save();
589			_context.translate( v1.x, v1.y );
590			if ( material.rotation !== 0 ) _context.rotate( material.rotation );
591			_context.scale( scaleX, scaleY );
592
593			material.program( _context );
594
595			_context.restore();
596
597		} else if ( material.isPointsMaterial ) {
598
599			setFillStyle( material.color.getStyle() );
600
601			_context.save();
602			_context.translate( v1.x, v1.y );
603			if ( material.rotation !== 0 ) _context.rotate( material.rotation );
604			_context.scale( scaleX * material.size, - scaleY * material.size );
605			_context.fillRect( - 0.5, - 0.5, 1, 1 );
606			_context.restore();
607
608		}
609
610		/* DEBUG
611		setStrokeStyle( 'rgb(255,255,0)' );
612		_context.beginPath();
613		_context.moveTo( v1.x - 10, v1.y );
614		_context.lineTo( v1.x + 10, v1.y );
615		_context.moveTo( v1.x, v1.y - 10 );
616		_context.lineTo( v1.x, v1.y + 10 );
617		_context.stroke();
618		*/
619
620	}
621
622	function renderLine( v1, v2, element, material ) {
623
624		setOpacity( material.opacity );
625		setBlending( material.blending );
626
627		_context.beginPath();
628		_context.moveTo( v1.positionScreen.x, v1.positionScreen.y );
629		_context.lineTo( v2.positionScreen.x, v2.positionScreen.y );
630
631		if ( material.isLineBasicMaterial ) {
632
633			setLineWidth( material.linewidth );
634			setLineCap( material.linecap );
635			setLineJoin( material.linejoin );
636
637			if ( material.vertexColors !== THREE.VertexColors ) {
638
639				setStrokeStyle( material.color.getStyle() );
640
641			} else {
642
643				var colorStyle1 = element.vertexColors[ 0 ].getStyle();
644				var colorStyle2 = element.vertexColors[ 1 ].getStyle();
645
646				if ( colorStyle1 === colorStyle2 ) {
647
648					setStrokeStyle( colorStyle1 );
649
650				} else {
651
652					try {
653
654						var grad = _context.createLinearGradient(
655							v1.positionScreen.x,
656							v1.positionScreen.y,
657							v2.positionScreen.x,
658							v2.positionScreen.y
659						);
660						grad.addColorStop( 0, colorStyle1 );
661						grad.addColorStop( 1, colorStyle2 );
662
663					} catch ( exception ) {
664
665						grad = colorStyle1;
666
667					}
668
669					setStrokeStyle( grad );
670
671				}
672
673			}
674
675			if ( material.isLineDashedMaterial ) {
676
677				setLineDash( [ material.dashSize, material.gapSize ] );
678
679			}
680
681			_context.stroke();
682			_elemBox.expandByScalar( material.linewidth * 2 );
683
684			if ( material.isLineDashedMaterial ) {
685
686				setLineDash( [] );
687
688			}
689
690		}
691
692	}
693
694	function renderFace3( v1, v2, v3, uv1, uv2, uv3, element, material ) {
695
696		_this.info.render.vertices += 3;
697		_this.info.render.faces ++;
698
699		setOpacity( material.opacity );
700		setBlending( material.blending );
701
702		_v1x = v1.positionScreen.x; _v1y = v1.positionScreen.y;
703		_v2x = v2.positionScreen.x; _v2y = v2.positionScreen.y;
704		_v3x = v3.positionScreen.x; _v3y = v3.positionScreen.y;
705
706		drawTriangle( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y );
707
708		if ( ( material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) && material.map === null ) {
709
710			_diffuseColor.copy( material.color );
711			_emissiveColor.copy( material.emissive );
712
713			if ( material.vertexColors === THREE.FaceColors ) {
714
715				_diffuseColor.multiply( element.color );
716
717			}
718
719			_color.copy( _ambientLight );
720
721			_centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 );
722
723			calculateLight( _centroid, element.normalModel, _color );
724
725			_color.multiply( _diffuseColor ).add( _emissiveColor );
726
727			material.wireframe === true
728				 ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
729				 : fillPath( _color );
730
731		} else if ( material.isMeshBasicMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) {
732
733			if ( material.map !== null ) {
734
735				var mapping = material.map.mapping;
736
737				if ( mapping === THREE.UVMapping ) {
738
739					_uvs = element.uvs;
740					patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[ uv1 ].x, _uvs[ uv1 ].y, _uvs[ uv2 ].x, _uvs[ uv2 ].y, _uvs[ uv3 ].x, _uvs[ uv3 ].y, material.map );
741
742				}
743
744			} else if ( material.envMap !== null ) {
745
746				if ( material.envMap.mapping === THREE.SphericalReflectionMapping ) {
747
748					_normal.copy( element.vertexNormalsModel[ uv1 ] ).applyMatrix3( _normalViewMatrix );
749					_uv1x = 0.5 * _normal.x + 0.5;
750					_uv1y = 0.5 * _normal.y + 0.5;
751
752					_normal.copy( element.vertexNormalsModel[ uv2 ] ).applyMatrix3( _normalViewMatrix );
753					_uv2x = 0.5 * _normal.x + 0.5;
754					_uv2y = 0.5 * _normal.y + 0.5;
755
756					_normal.copy( element.vertexNormalsModel[ uv3 ] ).applyMatrix3( _normalViewMatrix );
757					_uv3x = 0.5 * _normal.x + 0.5;
758					_uv3y = 0.5 * _normal.y + 0.5;
759
760					patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap );
761
762				}
763
764			} else {
765
766				_color.copy( material.color );
767
768				if ( material.vertexColors === THREE.FaceColors ) {
769
770					_color.multiply( element.color );
771
772				}
773
774				material.wireframe === true
775					 ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
776					 : fillPath( _color );
777
778			}
779
780		} else if ( material.isMeshNormalMaterial ) {
781
782			_normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix );
783
784			_color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
785
786			material.wireframe === true
787				 ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
788				 : fillPath( _color );
789
790		} else {
791
792			_color.setRGB( 1, 1, 1 );
793
794			material.wireframe === true
795				 ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
796				 : fillPath( _color );
797
798		}
799
800	}
801
802	//
803
804	function drawTriangle( x0, y0, x1, y1, x2, y2 ) {
805
806		_context.beginPath();
807		_context.moveTo( x0, y0 );
808		_context.lineTo( x1, y1 );
809		_context.lineTo( x2, y2 );
810		_context.closePath();
811
812	}
813
814	function strokePath( color, linewidth, linecap, linejoin ) {
815
816		setLineWidth( linewidth );
817		setLineCap( linecap );
818		setLineJoin( linejoin );
819		setStrokeStyle( color.getStyle() );
820
821		_context.stroke();
822
823		_elemBox.expandByScalar( linewidth * 2 );
824
825	}
826
827	function fillPath( color ) {
828
829		setFillStyle( color.getStyle() );
830		_context.fill();
831
832	}
833
834	function textureToPattern( texture ) {
835
836		if ( texture.version === 0 ||
837			texture instanceof THREE.CompressedTexture ||
838			texture instanceof THREE.DataTexture ) {
839
840			return {
841				canvas: undefined,
842				version: texture.version
843			};
844
845		}
846
847		var image = texture.image;
848
849		if ( image.complete === false ) {
850
851			return {
852				canvas: undefined,
853				version: 0
854			};
855
856		}
857
858		var repeatX = texture.wrapS === THREE.RepeatWrapping || texture.wrapS === THREE.MirroredRepeatWrapping;
859		var repeatY = texture.wrapT === THREE.RepeatWrapping || texture.wrapT === THREE.MirroredRepeatWrapping;
860
861		var mirrorX = texture.wrapS === THREE.MirroredRepeatWrapping;
862		var mirrorY = texture.wrapT === THREE.MirroredRepeatWrapping;
863
864		//
865
866		var canvas = document.createElement( 'canvas' );
867		canvas.width = image.width * ( mirrorX ? 2 : 1 );
868		canvas.height = image.height * ( mirrorY ? 2 : 1 );
869
870		var context = canvas.getContext( '2d' );
871		context.setTransform( 1, 0, 0, - 1, 0, image.height );
872		context.drawImage( image, 0, 0 );
873
874		if ( mirrorX === true ) {
875
876			context.setTransform( - 1, 0, 0, - 1, image.width, image.height );
877			context.drawImage( image, - image.width, 0 );
878
879		}
880
881		if ( mirrorY === true ) {
882
883			context.setTransform( 1, 0, 0, 1, 0, 0 );
884			context.drawImage( image, 0, image.height );
885
886		}
887
888		if ( mirrorX === true && mirrorY === true ) {
889
890			context.setTransform( - 1, 0, 0, 1, image.width, 0 );
891			context.drawImage( image, - image.width, image.height );
892
893		}
894
895		var repeat = 'no-repeat';
896
897		if ( repeatX === true && repeatY === true ) {
898
899			repeat = 'repeat';
900
901		} else if ( repeatX === true ) {
902
903			repeat = 'repeat-x';
904
905		} else if ( repeatY === true ) {
906
907			repeat = 'repeat-y';
908
909		}
910
911		var pattern = _context.createPattern( canvas, repeat );
912
913		if ( texture.onUpdate ) texture.onUpdate( texture );
914
915		return {
916			canvas: pattern,
917			version: texture.version
918		};
919
920	}
921
922	function patternPath( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture ) {
923
924		var pattern = _patterns[ texture.id ];
925
926		if ( pattern === undefined || pattern.version !== texture.version ) {
927
928			pattern = textureToPattern( texture );
929			_patterns[ texture.id ] = pattern;
930
931		}
932
933		if ( pattern.canvas !== undefined ) {
934
935			setFillStyle( pattern.canvas );
936
937		} else {
938
939			setFillStyle( 'rgba( 0, 0, 0, 1)' );
940			_context.fill();
941			return;
942
943		}
944
945		// http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
946
947		var a, b, c, d, e, f, det, idet,
948			offsetX = texture.offset.x / texture.repeat.x,
949			offsetY = texture.offset.y / texture.repeat.y,
950			width = texture.image.width * texture.repeat.x,
951			height = texture.image.height * texture.repeat.y;
952
953		u0 = ( u0 + offsetX ) * width;
954		v0 = ( v0 + offsetY ) * height;
955
956		u1 = ( u1 + offsetX ) * width;
957		v1 = ( v1 + offsetY ) * height;
958
959		u2 = ( u2 + offsetX ) * width;
960		v2 = ( v2 + offsetY ) * height;
961
962		x1 -= x0; y1 -= y0;
963		x2 -= x0; y2 -= y0;
964
965		u1 -= u0; v1 -= v0;
966		u2 -= u0; v2 -= v0;
967
968		det = u1 * v2 - u2 * v1;
969
970		if ( det === 0 ) return;
971
972		idet = 1 / det;
973
974		a = ( v2 * x1 - v1 * x2 ) * idet;
975		b = ( v2 * y1 - v1 * y2 ) * idet;
976		c = ( u1 * x2 - u2 * x1 ) * idet;
977		d = ( u1 * y2 - u2 * y1 ) * idet;
978
979		e = x0 - a * u0 - c * v0;
980		f = y0 - b * u0 - d * v0;
981
982		_context.save();
983		_context.transform( a, b, c, d, e, f );
984		_context.fill();
985		_context.restore();
986
987	}
988
989	/*
990	function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) {
991
992		// http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
993
994		var a, b, c, d, e, f, det, idet,
995		width = image.width - 1,
996		height = image.height - 1;
997
998		u0 *= width; v0 *= height;
999		u1 *= width; v1 *= height;
1000		u2 *= width; v2 *= height;
1001
1002		x1 -= x0; y1 -= y0;
1003		x2 -= x0; y2 -= y0;
1004
1005		u1 -= u0; v1 -= v0;
1006		u2 -= u0; v2 -= v0;
1007
1008		det = u1 * v2 - u2 * v1;
1009
1010		idet = 1 / det;
1011
1012		a = ( v2 * x1 - v1 * x2 ) * idet;
1013		b = ( v2 * y1 - v1 * y2 ) * idet;
1014		c = ( u1 * x2 - u2 * x1 ) * idet;
1015		d = ( u1 * y2 - u2 * y1 ) * idet;
1016
1017		e = x0 - a * u0 - c * v0;
1018		f = y0 - b * u0 - d * v0;
1019
1020		_context.save();
1021		_context.transform( a, b, c, d, e, f );
1022		_context.clip();
1023		_context.drawImage( image, 0, 0 );
1024		_context.restore();
1025
1026	}
1027	*/
1028
1029	// Hide anti-alias gaps
1030
1031	function expand( v1, v2, pixels ) {
1032
1033		var x = v2.x - v1.x, y = v2.y - v1.y,
1034			det = x * x + y * y, idet;
1035
1036		if ( det === 0 ) return;
1037
1038		idet = pixels / Math.sqrt( det );
1039
1040		x *= idet; y *= idet;
1041
1042		v2.x += x; v2.y += y;
1043		v1.x -= x; v1.y -= y;
1044
1045	}
1046
1047	// Context cached methods.
1048
1049	function setOpacity( value ) {
1050
1051		if ( _contextGlobalAlpha !== value ) {
1052
1053			_context.globalAlpha = value;
1054			_contextGlobalAlpha = value;
1055
1056		}
1057
1058	}
1059
1060	function setBlending( value ) {
1061
1062		if ( _contextGlobalCompositeOperation !== value ) {
1063
1064			if ( value === THREE.NormalBlending ) {
1065
1066				_context.globalCompositeOperation = 'source-over';
1067
1068			} else if ( value === THREE.AdditiveBlending ) {
1069
1070				_context.globalCompositeOperation = 'lighter';
1071
1072			} else if ( value === THREE.SubtractiveBlending ) {
1073
1074				_context.globalCompositeOperation = 'darker';
1075
1076			} else if ( value === THREE.MultiplyBlending ) {
1077
1078				_context.globalCompositeOperation = 'multiply';
1079
1080			}
1081
1082			_contextGlobalCompositeOperation = value;
1083
1084		}
1085
1086	}
1087
1088	function setLineWidth( value ) {
1089
1090		if ( _contextLineWidth !== value ) {
1091
1092			_context.lineWidth = value;
1093			_contextLineWidth = value;
1094
1095		}
1096
1097	}
1098
1099	function setLineCap( value ) {
1100
1101		// "butt", "round", "square"
1102
1103		if ( _contextLineCap !== value ) {
1104
1105			_context.lineCap = value;
1106			_contextLineCap = value;
1107
1108		}
1109
1110	}
1111
1112	function setLineJoin( value ) {
1113
1114		// "round", "bevel", "miter"
1115
1116		if ( _contextLineJoin !== value ) {
1117
1118			_context.lineJoin = value;
1119			_contextLineJoin = value;
1120
1121		}
1122
1123	}
1124
1125	function setStrokeStyle( value ) {
1126
1127		if ( _contextStrokeStyle !== value ) {
1128
1129			_context.strokeStyle = value;
1130			_contextStrokeStyle = value;
1131
1132		}
1133
1134	}
1135
1136	function setFillStyle( value ) {
1137
1138		if ( _contextFillStyle !== value ) {
1139
1140			_context.fillStyle = value;
1141			_contextFillStyle = value;
1142
1143		}
1144
1145	}
1146
1147	function setLineDash( value ) {
1148
1149		if ( _contextLineDash.length !== value.length ) {
1150
1151			_context.setLineDash( value );
1152			_contextLineDash = value;
1153
1154		}
1155
1156	}
1157
1158};