Flat, round, designer-friendly pseudo-3D engine for canvas & SVG
1/**
2 * Cone composite shape
3 */
4
5( function( root, factory ) {
6 // module definition
7 if ( typeof module == 'object' && module.exports ) {
8 // CommonJS
9 module.exports = factory( require('./boilerplate'), require('./vector'),
10 require('./path-command'), require('./anchor'), require('./ellipse') );
11 } else {
12 // browser global
13 var Zdog = root.Zdog;
14 Zdog.Cone = factory( Zdog, Zdog.Vector, Zdog.PathCommand,
15 Zdog.Anchor, Zdog.Ellipse );
16 }
17}( this, function factory( utils, Vector, PathCommand, Anchor, Ellipse ) {
18
19var Cone = Ellipse.subclass({
20 length: 1,
21 fill: true,
22});
23Cone.type = 'Cone';
24
25var TAU = utils.TAU;
26
27Cone.prototype.create = function( /* options */) {
28 // call super
29 Ellipse.prototype.create.apply( this, arguments );
30 // composite shape, create child shapes
31 this.apex = new Anchor({
32 addTo: this,
33 translate: { z: this.length },
34 });
35
36 // vectors used for calculation
37 this.renderApex = new Vector();
38 this.renderCentroid = new Vector();
39 this.tangentA = new Vector();
40 this.tangentB = new Vector();
41
42 this.surfacePathCommands = [
43 new PathCommand( 'move', [ {} ] ), // points set in renderConeSurface
44 new PathCommand( 'line', [ {} ] ),
45 new PathCommand( 'line', [ {} ] ),
46 ];
47};
48
49Cone.prototype.updateSortValue = function() {
50 // center of cone is one third of its length
51 this.renderCentroid.set( this.renderOrigin )
52 .lerp( this.apex.renderOrigin, 1/3 );
53 this.sortValue = this.renderCentroid.z;
54};
55
56Cone.prototype.render = function( ctx, renderer ) {
57 this.renderConeSurface( ctx, renderer );
58 Ellipse.prototype.render.apply( this, arguments );
59};
60
61Cone.prototype.renderConeSurface = function( ctx, renderer ) {
62 if ( !this.visible ) {
63 return;
64 }
65 this.renderApex.set( this.apex.renderOrigin )
66 .subtract( this.renderOrigin );
67
68 var scale = this.renderNormal.magnitude();
69 var apexDistance = this.renderApex.magnitude2d();
70 var normalDistance = this.renderNormal.magnitude2d();
71 // eccentricity
72 var eccenAngle = Math.acos( normalDistance/scale );
73 var eccen = Math.sin( eccenAngle );
74 var radius = this.diameter / 2 * scale;
75 // does apex extend beyond eclipse of face
76 var isApexVisible = radius * eccen < apexDistance;
77 if ( !isApexVisible ) {
78 return;
79 }
80 // update tangents
81 var apexAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x ) +
82 TAU/2;
83 var projectLength = apexDistance/eccen;
84 var projectAngle = Math.acos( radius/projectLength );
85 // set tangent points
86 var tangentA = this.tangentA;
87 var tangentB = this.tangentB;
88
89 tangentA.x = Math.cos( projectAngle ) * radius * eccen;
90 tangentA.y = Math.sin( projectAngle ) * radius;
91
92 tangentB.set( this.tangentA );
93 tangentB.y *= -1;
94
95 tangentA.rotateZ( apexAngle );
96 tangentB.rotateZ( apexAngle );
97 tangentA.add( this.renderOrigin );
98 tangentB.add( this.renderOrigin );
99
100 this.setSurfaceRenderPoint( 0, tangentA );
101 this.setSurfaceRenderPoint( 1, this.apex.renderOrigin );
102 this.setSurfaceRenderPoint( 2, tangentB );
103
104 // render
105 var elem = this.getSurfaceRenderElement( ctx, renderer );
106 renderer.renderPath( ctx, elem, this.surfacePathCommands );
107 renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );
108 renderer.fill( ctx, elem, this.fill, this.color );
109 renderer.end( ctx, elem );
110};
111
112var svgURI = 'http://www.w3.org/2000/svg';
113
114Cone.prototype.getSurfaceRenderElement = function( ctx, renderer ) {
115 if ( !renderer.isSvg ) {
116 return;
117 }
118 if ( !this.surfaceSvgElement ) {
119 // create svgElement
120 this.surfaceSvgElement = document.createElementNS( svgURI, 'path' );
121 this.surfaceSvgElement.setAttribute( 'stroke-linecap', 'round' );
122 this.surfaceSvgElement.setAttribute( 'stroke-linejoin', 'round' );
123 }
124 return this.surfaceSvgElement;
125};
126
127Cone.prototype.setSurfaceRenderPoint = function( index, point ) {
128 var renderPoint = this.surfacePathCommands[ index ].renderPoints[0];
129 renderPoint.set( point );
130};
131
132return Cone;
133
134} ) );