Flat, round, designer-friendly pseudo-3D engine for canvas & SVG
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 179 lines 4.8 kB view raw
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} ) );