Flat, round, designer-friendly pseudo-3D engine for canvas & SVG
1/**
2 * Cylinder 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'),
10 require('./path-command'), require('./shape'), require('./group'),
11 require('./ellipse') );
12 } else {
13 // browser global
14 var Zdog = root.Zdog;
15 Zdog.Cylinder = factory( Zdog, Zdog.PathCommand, Zdog.Shape,
16 Zdog.Group, Zdog.Ellipse );
17 }
18}( this, function factory( utils, PathCommand, Shape, Group, Ellipse ) {
19
20function noop() {}
21
22// ----- CylinderGroup ----- //
23
24var CylinderGroup = Group.subclass({
25 color: '#333',
26 updateSort: true,
27});
28
29CylinderGroup.type = 'CylinderGroup';
30
31CylinderGroup.prototype.create = function() {
32 Group.prototype.create.apply( this, arguments );
33 this.pathCommands = [
34 new PathCommand( 'move', [ {} ] ),
35 new PathCommand( 'line', [ {} ] ),
36 ];
37};
38
39CylinderGroup.prototype.render = function( ctx, renderer ) {
40 this.renderCylinderSurface( ctx, renderer );
41 Group.prototype.render.apply( this, arguments );
42};
43
44CylinderGroup.prototype.renderCylinderSurface = function( ctx, renderer ) {
45 if ( !this.visible ) {
46 return;
47 }
48 // render cylinder surface
49 var elem = this.getRenderElement( ctx, renderer );
50 var frontBase = this.frontBase;
51 var rearBase = this.rearBase;
52 var scale = frontBase.renderNormal.magnitude();
53 var strokeWidth = frontBase.diameter * scale + frontBase.getLineWidth();
54 // set path command render points
55 this.pathCommands[0].renderPoints[0].set( frontBase.renderOrigin );
56 this.pathCommands[1].renderPoints[0].set( rearBase.renderOrigin );
57
58 if ( renderer.isCanvas ) {
59 ctx.lineCap = 'butt'; // nice
60 }
61 renderer.renderPath( ctx, elem, this.pathCommands );
62 renderer.stroke( ctx, elem, true, this.color, strokeWidth );
63 renderer.end( ctx, elem );
64
65 if ( renderer.isCanvas ) {
66 ctx.lineCap = 'round'; // reset
67 }
68};
69
70var svgURI = 'http://www.w3.org/2000/svg';
71
72CylinderGroup.prototype.getRenderElement = function( ctx, renderer ) {
73 if ( !renderer.isSvg ) {
74 return;
75 }
76 if ( !this.svgElement ) {
77 // create svgElement
78 this.svgElement = document.createElementNS( svgURI, 'path' );
79 }
80 return this.svgElement;
81};
82
83// prevent double-creation in parent.copyGraph()
84// only create in Cylinder.create()
85CylinderGroup.prototype.copyGraph = noop;
86
87// ----- CylinderEllipse ----- //
88
89var CylinderEllipse = Ellipse.subclass();
90
91CylinderEllipse.prototype.copyGraph = noop;
92
93// ----- Cylinder ----- //
94
95var Cylinder = Shape.subclass({
96 diameter: 1,
97 length: 1,
98 frontFace: undefined,
99 fill: true,
100});
101
102Cylinder.type = 'Cylinder';
103
104
105var TAU = utils.TAU;
106
107Cylinder.prototype.create = function( /* options */) {
108 // call super
109 Shape.prototype.create.apply( this, arguments );
110 // composite shape, create child shapes
111 // CylinderGroup to render cylinder surface then bases
112 this.group = new CylinderGroup({
113 addTo: this,
114 color: this.color,
115 visible: this.visible,
116 });
117 var baseZ = this.length / 2;
118 var baseColor = this.backface || true;
119 // front outside base
120 this.frontBase = this.group.frontBase = new Ellipse({
121 addTo: this.group,
122 diameter: this.diameter,
123 translate: { z: baseZ },
124 rotate: { y: TAU/2 },
125 color: this.color,
126 stroke: this.stroke,
127 fill: this.fill,
128 backface: utils.cloneColor(this.frontFace || baseColor),
129 visible: this.visible,
130 });
131 // back outside base
132 this.rearBase = this.group.rearBase = this.frontBase.copy({
133 translate: { z: -baseZ },
134 rotate: { y: 0 },
135 backface: utils.cloneColor(baseColor),
136 });
137};
138
139// Cylinder shape does not render anything
140Cylinder.prototype.render = function() {};
141
142Cylinder.prototype.setPath = function() {
143 if ( !this.frontBase || !this.rearBase ) return;
144 var baseZ = this.length / 2;
145 this.frontBase.translate = { z: baseZ };
146 this.frontBase.diameter = this.diameter;
147 this.rearBase.translate = { z: -baseZ };
148 this.rearBase.diameter = this.diameter;
149 this.frontBase.updatePath();
150 this.rearBase.updatePath();
151};
152
153// ----- set child properties ----- //
154
155var childProperties = [ 'stroke', 'fill', 'color', 'visible' ];
156childProperties.forEach( function( property ) {
157 // use proxy property for custom getter & setter
158 var _prop = '_' + property;
159 Object.defineProperty( Cylinder.prototype, property, {
160 get: function() {
161 return this[ _prop ];
162 },
163 set: function( value ) {
164 this[ _prop ] = value;
165 // set property on children
166 if ( this.frontBase ) {
167 this.frontBase[ property ] = value;
168 this.rearBase[ property ] = value;
169 this.group[ property ] = value;
170 }
171 },
172 } );
173} );
174
175// TODO child property setter for backface, frontBaseColor, & rearBaseColor
176
177return Cylinder;
178
179} ) );