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

Configure Feed

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

Merge pull request #4 from different55/funnel

Split Funnel from Horn, improve Horn rendering.

authored by

Different55 and committed by
GitHub
e668d3b3 ab3615c8

+1255 -101
+927 -17
dist/zdog.dist.js
··· 70 70 return isFirstHalf ? curve : 1 - curve; 71 71 }; 72 72 73 + Zdog.isColor = function(value) { 74 + return (typeof value == 'string') || (value && value.isTexture); 75 + } 76 + Zdog.cloneColor = function(value) { 77 + return (value && value.clone) ? value.clone() : value; 78 + } 79 + 80 + Zdog.exportGraph = function(model) { 81 + return JSON.parse( JSON.stringify( model ) ); 82 + }; 83 + 73 84 return Zdog; 74 85 75 86 } ) ); ··· 126 137 if ( !isStroke ) { 127 138 return; 128 139 } 129 - ctx.strokeStyle = color; 130 140 ctx.lineWidth = lineWidth; 131 - ctx.stroke(); 141 + if (color && color.getCanvasFill) { 142 + ctx.save(); 143 + ctx.strokeStyle = color.getCanvasFill(ctx); 144 + ctx.stroke(); 145 + ctx.restore(); 146 + } else { 147 + ctx.strokeStyle = color; 148 + ctx.stroke(); 149 + } 132 150 }; 133 151 134 152 CanvasRenderer.fill = function( ctx, elem, isFill, color ) { 135 153 if ( !isFill ) { 136 154 return; 137 155 } 138 - ctx.fillStyle = color; 139 - ctx.fill(); 156 + if (color && color.getCanvasFill) { 157 + ctx.save(); 158 + ctx.fillStyle = color.getCanvasFill(ctx); 159 + ctx.fill(); 160 + ctx.restore(); 161 + } else { 162 + ctx.fillStyle = color; 163 + ctx.fill(); 164 + } 140 165 }; 141 166 142 167 CanvasRenderer.end = function() {}; ··· 208 233 if ( !isStroke ) { 209 234 return; 210 235 } 236 + if (color && color.getSvgFill) { 237 + color = color.getSvgFill(svg); 238 + } 211 239 elem.setAttribute( 'stroke', color ); 212 240 elem.setAttribute( 'stroke-width', lineWidth ); 213 241 }; 214 242 215 243 SvgRenderer.fill = function( svg, elem, isFill, color ) { 216 244 var fillColor = isFill ? color : 'none'; 245 + if (fillColor && fillColor.getSvgFill) { 246 + fillColor = fillColor.getSvgFill(svg); 247 + } 217 248 elem.setAttribute( 'fill', fillColor ); 218 249 }; 219 250 ··· 382 413 return new Vector( this ); 383 414 }; 384 415 416 + function round( num ) { 417 + return Math.round( num * 1000 ) / 1000; 418 + } 419 + 420 + Vector.prototype.toJSON = function() { 421 + var x = this.x; 422 + var y = this.y; 423 + var z = this.z; 424 + 425 + if ( x === y && y === z ) { 426 + return x !== 0 ? round( x ) : undefined; 427 + } 428 + 429 + var obj = { x: x, y: y, z: z }; 430 + var result = {}; 431 + 432 + Object.keys( obj ).forEach( function( key ) { 433 + var value = obj[ key ]; 434 + if ( value !== 0 ) { 435 + result[ key ] = round( value ); 436 + } 437 + }); 438 + 439 + return Object.keys( result ).length ? result : undefined; 440 + }; 441 + 385 442 return Vector; 386 443 387 444 } ) ); ··· 409 466 function Anchor( options ) { 410 467 this.create( options || {} ); 411 468 } 469 + Anchor.type = 'Anchor'; 412 470 413 471 Anchor.prototype.create = function( options ) { 414 472 this.children = []; ··· 427 485 if ( this.addTo ) { 428 486 this.addTo.addChild( this ); 429 487 } 488 + if ( options.importGraph ) { 489 + this.importGraph( options.importGraph ); 490 + } 430 491 }; 431 492 432 493 Anchor.defaults = {}; ··· 437 498 'scale', 438 499 'addTo', 439 500 ]); 501 + 502 + Anchor.ignoreKeysJSON = [ 503 + 'addTo', 504 + ]; 440 505 441 506 Anchor.prototype.setOptions = function( options ) { 442 507 var optionKeys = this.constructor.optionKeys; ··· 593 658 return clone; 594 659 }; 595 660 661 + Anchor.prototype.importGraph = function( model ) { 662 + this.addChild( revive( model ) ); 663 + 664 + function revive( graph ) { 665 + graph = utils.extend( {}, graph ); 666 + // quick hack to avoid nested Illustration items 667 + var type = graph.type === 'Illustration' ? 'Anchor' : graph.type; 668 + var children = (graph.children || []).slice( 0 ); 669 + delete graph.children; 670 + 671 + var Item = utils[ type ]; 672 + var rootGraph; 673 + if ( Item ) { 674 + rootGraph = new Item( graph ); 675 + children.forEach( function( child ) { 676 + revive( utils.extend( child, { addTo: rootGraph } ) ); 677 + } ); 678 + } 679 + return rootGraph; 680 + } 681 + }; 682 + 596 683 Anchor.prototype.normalizeRotate = function() { 597 684 this.rotate.x = utils.modulo( this.rotate.x, TAU ); 598 685 this.rotate.y = utils.modulo( this.rotate.y, TAU ); 599 686 this.rotate.z = utils.modulo( this.rotate.z, TAU ); 600 687 }; 601 688 689 + Anchor.prototype.toJSON = function() { 690 + var type = this.constructor.type; 691 + var result = { type: type }; 692 + var defaults = this.constructor.defaults; 693 + var optionKeys = this.constructor.optionKeys.slice(0).concat('children'); 694 + var ignoreKeys = Anchor.ignoreKeysJSON 695 + .slice(0) 696 + .concat(this.constructor.ignoreKeysJSON || []); 697 + 698 + optionKeys.forEach(function( key ) { 699 + if (ignoreKeys.indexOf(key) > -1) { 700 + return; 701 + } 702 + var value = this[key]; 703 + 704 + if ( 705 + ![ 'undefined', 'function' ].indexOf(typeof value) > -1 && 706 + value !== defaults[key] 707 + ) { 708 + if (Array.isArray(value) && value.length === 0) { 709 + return; 710 + } 711 + if (value.toJSON) { 712 + var serialized = value.toJSON(); 713 + if (typeof serialized !== 'undefined') { 714 + if (key === 'scale' && serialized === 1) { 715 + return; 716 + } 717 + result[key] = serialized; 718 + } 719 + } else { 720 + result[key] = value; 721 + } 722 + } 723 + }, this); 724 + 725 + return result; 726 + }; 727 + 602 728 // ----- subclass ----- // 603 729 604 730 function getSubclass( Super ) { ··· 620 746 if ( !Item.optionKeys.indexOf( key ) != 1 ) { 621 747 Item.optionKeys.push( key ); 622 748 } 623 - } ); 749 + }); 750 + // create ignoreKeysJSON 751 + Item.ignoreKeysJSON = Super.ignoreKeysJSON.slice(0); 624 752 625 753 Item.subclass = getSubclass( Item ); 626 754 ··· 791 919 onDragEnd: noop, 792 920 onResize: noop, 793 921 }); 922 + Illustration.ignoreKeysJSON = [ 'dragRotate', 'element', 'resize' ]; 923 + Illustration.type = 'Illustration'; 794 924 795 925 utils.extend( Illustration.prototype, Dragger.prototype ); 796 926 ··· 1126 1256 backface: true, 1127 1257 }); 1128 1258 1259 + Shape.type = 'Shape'; 1260 + 1129 1261 Shape.prototype.create = function( options ) { 1130 1262 Anchor.prototype.create.call( this, options ); 1131 1263 this.updatePath(); ··· 1191 1323 this.pathCommands.forEach( function( command ) { 1192 1324 command.reset(); 1193 1325 } ); 1326 + 1327 + if (this.backface && this.backface.reset) { 1328 + this.backface.reset(); 1329 + } 1330 + if (this.color && this.color.reset) { 1331 + this.color.reset(); 1332 + } 1194 1333 }; 1195 1334 1196 1335 Shape.prototype.transform = function( translation, rotation, scale ) { ··· 1206 1345 this.children.forEach( function( child ) { 1207 1346 child.transform( translation, rotation, scale ); 1208 1347 } ); 1348 + if (this.backface && this.backface.transform) { 1349 + this.backface.transform( translation, rotation, scale ); 1350 + } 1351 + if (this.color && this.color.transform) { 1352 + this.color.transform( translation, rotation, scale ); 1353 + } 1209 1354 }; 1210 1355 1211 1356 Shape.prototype.updateSortValue = function() { ··· 1253 1398 1254 1399 var TAU = utils.TAU; 1255 1400 // Safari does not render lines with no size, have to render circle instead 1256 - Shape.prototype.renderCanvasDot = function( ctx ) { 1401 + Shape.prototype.renderCanvasDot = function( ctx , renderer) { 1257 1402 var lineWidth = this.getLineWidth(); 1258 1403 if ( !lineWidth ) { 1259 1404 return; 1260 1405 } 1261 - ctx.fillStyle = this.getRenderColor(); 1262 1406 var point = this.pathCommands[0].endRenderPoint; 1263 1407 ctx.beginPath(); 1264 1408 var radius = lineWidth/2; 1265 1409 ctx.arc( point.x, point.y, radius, 0, TAU ); 1266 - ctx.fill(); 1410 + renderer.fill(ctx, null, true, this.getRenderColor() ); 1267 1411 }; 1268 1412 1269 1413 Shape.prototype.getLineWidth = function() { ··· 1278 1422 1279 1423 Shape.prototype.getRenderColor = function() { 1280 1424 // use backface color if applicable 1281 - var isBackfaceColor = typeof this.backface == 'string' && this.isFacingBack; 1425 + var isBackfaceColor = utils.isColor(this.backface) && this.isFacingBack; 1282 1426 var color = isBackfaceColor ? this.backface : this.color; 1283 1427 return color; 1284 1428 }; ··· 1335 1479 visible: true, 1336 1480 }); 1337 1481 1482 + Group.type = 'Group'; 1483 + 1484 + 1338 1485 // ----- update ----- // 1339 1486 1340 1487 Group.prototype.updateSortValue = function() { ··· 1399 1546 width: 1, 1400 1547 height: 1, 1401 1548 }); 1549 + 1550 + Rect.type = 'Rect'; 1402 1551 1403 1552 Rect.prototype.setPath = function() { 1404 1553 var x = this.width / 2; ··· 1438 1587 closed: false, 1439 1588 }); 1440 1589 1590 + RoundedRect.type = 'RoundedRect'; 1591 + 1441 1592 RoundedRect.prototype.setPath = function() { 1442 1593 /* eslint 1443 1594 id-length: [ "error", { "min": 2, "exceptions": [ "x", "y" ] }], ··· 1517 1668 closed: false, 1518 1669 }); 1519 1670 1671 + Ellipse.type = 'Ellipse'; 1672 + 1520 1673 Ellipse.prototype.setPath = function() { 1521 1674 var width = this.width != undefined ? this.width : this.diameter; 1522 1675 var height = this.height != undefined ? this.height : this.diameter; ··· 1576 1729 radius: 0.5, 1577 1730 }); 1578 1731 1732 + Polygon.type = 'Polygon'; 1733 + 1579 1734 var TAU = utils.TAU; 1580 1735 1581 1736 Polygon.prototype.setPath = function() { ··· 1611 1766 var Hemisphere = Ellipse.subclass({ 1612 1767 fill: true, 1613 1768 }); 1769 + 1770 + Hemisphere.type = 'Hemisphere'; 1614 1771 1615 1772 var TAU = utils.TAU; 1616 1773 ··· 1714 1871 color: '#333', 1715 1872 updateSort: true, 1716 1873 }); 1874 + 1875 + CylinderGroup.type = 'CylinderGroup'; 1717 1876 1718 1877 CylinderGroup.prototype.create = function() { 1719 1878 Group.prototype.create.apply( this, arguments ); ··· 1786 1945 fill: true, 1787 1946 }); 1788 1947 1948 + Cylinder.type = 'Cylinder'; 1949 + 1950 + 1789 1951 var TAU = utils.TAU; 1790 1952 1791 1953 Cylinder.prototype.create = function( /* options */) { ··· 1809 1971 color: this.color, 1810 1972 stroke: this.stroke, 1811 1973 fill: this.fill, 1812 - backface: this.frontFace || baseColor, 1974 + backface: utils.cloneColor(this.frontFace || baseColor), 1813 1975 visible: this.visible, 1814 1976 }); 1815 1977 // back outside base 1816 1978 this.rearBase = this.group.rearBase = this.frontBase.copy({ 1817 1979 translate: { z: -baseZ }, 1818 1980 rotate: { y: 0 }, 1819 - backface: baseColor, 1981 + backface: utils.cloneColor(baseColor), 1820 1982 }); 1821 1983 }; 1822 1984 ··· 1872 2034 length: 1, 1873 2035 fill: true, 1874 2036 }); 2037 + Cone.type = 'Cone'; 1875 2038 1876 2039 var TAU = utils.TAU; 1877 2040 ··· 1984 2147 1985 2148 } ) ); 1986 2149 /** 2150 + * Horn composite shape 2151 + */ 2152 + 2153 + ( function( root, factory ) { 2154 + // module definition 2155 + if ( typeof module == 'object' && module.exports ) { 2156 + // CommonJS 2157 + module.exports = factory( require('./boilerplate'), 2158 + require('./path-command'), require('./shape'), require('./group'), 2159 + require('./vector') ); 2160 + } else { 2161 + // browser global 2162 + var Zdog = root.Zdog; 2163 + Zdog.Horn = factory( Zdog, Zdog.PathCommand, Zdog.Shape, 2164 + Zdog.Group, Zdog.Vector ); 2165 + } 2166 + }( this, function factory( utils, PathCommand, Shape, Group, Vector ) { 2167 + 2168 + function noop() {} 2169 + 2170 + // ----- HornGroup ----- // 2171 + 2172 + var HornGroup = Group.subclass({ 2173 + color: '#333', 2174 + fill: true, 2175 + stroke: true, 2176 + updateSort: true, 2177 + }); 2178 + 2179 + HornGroup.type = 'HornGroup'; 2180 + 2181 + HornGroup.prototype.create = function() { 2182 + Group.prototype.create.apply( this, arguments ); 2183 + 2184 + // vectors used for calculation 2185 + this.renderApex = new Vector(); 2186 + 2187 + this.pathCommands = [ 2188 + new PathCommand( 'move', [ {} ] ), 2189 + new PathCommand( 'line', [ {} ] ), 2190 + new PathCommand( 'line', [ {} ] ), 2191 + new PathCommand( 'line', [ {} ] ), 2192 + ]; 2193 + }; 2194 + 2195 + HornGroup.prototype.render = function( ctx, renderer ) { 2196 + this.renderHornSurface( ctx, renderer ); 2197 + Group.prototype.render.apply( this, arguments ); 2198 + }; 2199 + 2200 + HornGroup.prototype.renderHornSurface = function( ctx, renderer ) { 2201 + if ( !this.visible ) { 2202 + return; 2203 + } 2204 + // render horn surface 2205 + var elem = this.getRenderElement( ctx, renderer ); 2206 + var scale = this.addTo.renderNormal.magnitude(); 2207 + var frontRadius = this.addTo.frontDiameter/2 * scale; 2208 + var rearRadius = this.addTo.rearDiameter/2 * scale; 2209 + this.renderApex.set( this.rearBase.renderOrigin ) 2210 + .subtract( this.frontBase.renderOrigin ); 2211 + 2212 + var apexDistance = this.renderApex.magnitude2d(); 2213 + if ( apexDistance <= Math.abs( rearRadius - frontRadius ) ) { 2214 + return; 2215 + } 2216 + 2217 + var angle = Math.atan2( this.renderApex.y, this.renderApex.x ); 2218 + var angle2 = Math.acos((frontRadius - rearRadius) / apexDistance); 2219 + 2220 + var frontTangentA = this.pathCommands[0].renderPoints[0]; 2221 + var rearTangentA = this.pathCommands[1].renderPoints[0]; 2222 + var rearTangentB = this.pathCommands[2].renderPoints[0]; 2223 + var frontTangentB = this.pathCommands[3].renderPoints[0]; 2224 + 2225 + frontTangentA.x = Math.cos( angle + angle2 ) * frontRadius; 2226 + frontTangentA.y = Math.sin( angle + angle2 ) * frontRadius; 2227 + rearTangentA.x = Math.cos( angle + angle2 ) * rearRadius; 2228 + rearTangentA.y = Math.sin( angle + angle2 ) * rearRadius; 2229 + 2230 + frontTangentB.x = Math.cos( angle - angle2 ) * frontRadius; 2231 + frontTangentB.y = Math.sin( angle - angle2 ) * frontRadius; 2232 + rearTangentB.x = Math.cos( angle - angle2 ) * rearRadius; 2233 + rearTangentB.y = Math.sin( angle - angle2 ) * rearRadius; 2234 + 2235 + frontTangentA.add( this.frontBase.renderOrigin ); 2236 + frontTangentB.add( this.frontBase.renderOrigin ); 2237 + rearTangentA.add( this.rearBase.renderOrigin ); 2238 + rearTangentB.add( this.rearBase.renderOrigin ); 2239 + 2240 + if ( renderer.isCanvas ) { 2241 + ctx.lineCap = 'butt'; // nice 2242 + } 2243 + renderer.renderPath( ctx, elem, this.pathCommands ); 2244 + renderer.stroke( ctx, elem, this.stroke, this.color, Shape.prototype.getLineWidth.apply(this) ); 2245 + renderer.fill( ctx, elem, this.fill, this.color ); 2246 + renderer.end( ctx, elem ); 2247 + 2248 + if ( renderer.isCanvas ) { 2249 + ctx.lineCap = 'round'; // reset 2250 + } 2251 + }; 2252 + 2253 + var svgURI = 'http://www.w3.org/2000/svg'; 2254 + 2255 + HornGroup.prototype.getRenderElement = function( ctx, renderer ) { 2256 + if ( !renderer.isSvg ) { 2257 + return; 2258 + } 2259 + if ( !this.svgElement ) { 2260 + // create svgElement 2261 + this.svgElement = document.createElementNS( svgURI, 'path'); 2262 + this.svgElement.setAttribute( 'stroke-linecap', 'round' ); 2263 + this.svgElement.setAttribute( 'stroke-linejoin', 'round' ); 2264 + } 2265 + return this.svgElement; 2266 + }; 2267 + 2268 + // prevent double-creation in parent.copyGraph() 2269 + // only create in Horn.create() 2270 + HornGroup.prototype.copyGraph = noop; 2271 + 2272 + // ----- HornCap ----- // 2273 + 2274 + var HornCap = Shape.subclass(); 2275 + 2276 + HornCap.type = 'HornCap'; 2277 + 2278 + HornCap.prototype.copyGraph = noop; 2279 + 2280 + // ----- Horn ----- // 2281 + 2282 + var Horn = Shape.subclass({ 2283 + frontDiameter: 1, 2284 + rearDiameter: 1, 2285 + length: 1, 2286 + frontFace: undefined, 2287 + fill: true, 2288 + }); 2289 + 2290 + Horn.type = 'Horn'; 2291 + 2292 + var TAU = utils.TAU; 2293 + 2294 + Horn.prototype.create = function(/* options */) { 2295 + // call super 2296 + Shape.prototype.create.apply( this, arguments ); 2297 + // composite shape, create child shapes 2298 + // HornGroup to render horn surface then bases 2299 + this.group = new HornGroup({ 2300 + addTo: this, 2301 + color: this.color, 2302 + fill: this.fill, 2303 + stroke: this.stroke, 2304 + visible: this.visible, 2305 + }); 2306 + var baseZ = this.length/2; 2307 + var baseColor = this.backface || true; 2308 + // front outside base 2309 + this.frontBase = this.group.frontBase = new HornCap({ 2310 + addTo: this.group, 2311 + translate: { z: (baseZ - this.frontDiameter/2) }, 2312 + rotate: { y: TAU/2 }, 2313 + color: this.color, 2314 + stroke: this.frontDiameter + this.stroke, 2315 + fill: this.fill, 2316 + backface: this.frontFace || baseColor, 2317 + visible: this.visible, 2318 + }); 2319 + // back outside base 2320 + this.rearBase = this.group.rearBase = new HornCap({ 2321 + addTo: this.group, 2322 + translate: { z: (-baseZ + this.rearDiameter/2) }, 2323 + rotate: { y: 0 }, 2324 + color: this.color, 2325 + stroke: this.rearDiameter + this.stroke, 2326 + fill: this.fill, 2327 + backface: baseColor, 2328 + visible: this.visible, 2329 + }); 2330 + 2331 + }; 2332 + 2333 + Horn.prototype.updateFrontCapDiameter = function(size) { 2334 + this.frontBase.stroke = size + this.stroke; 2335 + var baseZ = this.length/2; 2336 + this.frontBase.translate.z = (baseZ - size/2); 2337 + } 2338 + 2339 + Horn.prototype.updateRearCapDiameter = function(size) { 2340 + this.rearBase.stroke = size + this.stroke; 2341 + var baseZ = this.length/2; 2342 + this.rearBase.translate.z = (-baseZ + size/2); 2343 + } 2344 + 2345 + // Horn shape does not render anything 2346 + Horn.prototype.render = function() {}; 2347 + 2348 + // ----- set child properties ----- // 2349 + 2350 + var childProperties = [ 'stroke', 'fill', 'color', 'visible', 2351 + 'frontDiameter', 'rearDiameter' ]; 2352 + childProperties.forEach( function( property ) { 2353 + // use proxy property for custom getter & setter 2354 + var _prop = '_' + property; 2355 + Object.defineProperty( Horn.prototype, property, { 2356 + get: function() { 2357 + return this[ _prop ]; 2358 + }, 2359 + set: function( value ) { 2360 + this[ _prop ] = value; 2361 + // set property on children 2362 + if ( this.frontBase ) { 2363 + if (property === 'frontDiameter') { 2364 + this.updateFrontCapDiameter(value); 2365 + } 2366 + if (property === 'rearDiameter') { 2367 + this.updateRearCapDiameter(value); 2368 + } 2369 + this.frontBase[ property ] = value; 2370 + this.rearBase[ property ] = value; 2371 + this.group[ property ] = value; 2372 + } 2373 + }, 2374 + }); 2375 + }); 2376 + 2377 + // TODO child property setter for backface, frontBaseColor, & rearBaseColor 2378 + 2379 + return Horn; 2380 + 2381 + })); 2382 + /** 2383 + * Funnel composite shape 2384 + */ 2385 + 2386 + ( function( root, factory ) { 2387 + // module definition 2388 + if ( typeof module == 'object' && module.exports ) { 2389 + // CommonJS 2390 + module.exports = factory( require('./boilerplate'), 2391 + require('./path-command'), require('./shape'), require('./group'), 2392 + require('./vector') ); 2393 + } else { 2394 + // browser global 2395 + var Zdog = root.Zdog; 2396 + Zdog.Funnel = factory( Zdog, Zdog.PathCommand, Zdog.Shape, Zdog.Ellipse, 2397 + Zdog.Group, Zdog.Vector ); 2398 + } 2399 + }( this, function factory( utils, PathCommand, Shape, Ellipse, Group, Vector ) { 2400 + 2401 + function noop() {} 2402 + 2403 + // ----- FunnelGroup ----- // 2404 + 2405 + var FunnelGroup = Group.subclass({ 2406 + color: '#333', 2407 + fill: true, 2408 + stroke: true, 2409 + updateSort: true, 2410 + }); 2411 + 2412 + FunnelGroup.type = 'FunnelGroup'; 2413 + 2414 + FunnelGroup.prototype.create = function() { 2415 + Group.prototype.create.apply( this, arguments ); 2416 + 2417 + // vectors used for calculation 2418 + this.renderApex = new Vector(); 2419 + this.tangentFrontA = new Vector(); 2420 + this.tangentFrontB = new Vector(); 2421 + this.tangentRearA = new Vector(); 2422 + this.tangentRearB = new Vector(); 2423 + 2424 + this.pathCommands = [ 2425 + new PathCommand( 'move', [ {} ] ), 2426 + new PathCommand( 'line', [ {} ] ), 2427 + new PathCommand( 'line', [ {} ] ), 2428 + new PathCommand( 'line', [ {} ] ), 2429 + ]; 2430 + }; 2431 + 2432 + FunnelGroup.prototype.render = function( ctx, renderer ) { 2433 + this.renderFunnelSurface( ctx, renderer ); 2434 + Group.prototype.render.apply( this, arguments ); 2435 + }; 2436 + 2437 + FunnelGroup.prototype.renderFunnelSurface = function( ctx, renderer ) { 2438 + if ( !this.visible ) { 2439 + return; 2440 + } 2441 + // render funnel surface 2442 + var elem = this.getRenderElement( ctx, renderer ); 2443 + var frontBase = this.frontBase; 2444 + var frontDiameter = frontBase.diameter; 2445 + var rearBase = this.rearBase; 2446 + var rearDiameter = rearBase.diameter; 2447 + var scale = frontBase.renderNormal.magnitude(); 2448 + var frontRadius = frontDiameter/2 * scale; 2449 + var rearRadius = rearDiameter/2 * scale; 2450 + 2451 + this.renderApex.set( rearBase.renderOrigin ) 2452 + .subtract( frontBase.renderOrigin ); 2453 + 2454 + // calculate tangents. 2455 + var scale = frontBase.renderNormal.magnitude(); 2456 + var apexDistance = this.renderApex.magnitude2d(); 2457 + var normalDistance = frontBase.renderNormal.magnitude2d(); 2458 + // eccentricity 2459 + var eccenAngle = Math.acos( normalDistance / scale ); 2460 + var biggerRadius = (frontRadius > rearRadius) ? frontRadius : rearRadius; 2461 + var eccenPercent; 2462 + if (frontRadius == 0 || rearRadius == 0) { 2463 + eccenPercent = 1.0; 2464 + } else { 2465 + eccenPercent = (Math.abs(frontRadius - rearRadius) / biggerRadius); 2466 + } 2467 + var eccen = Math.sin( eccenAngle ) * Math.sqrt(eccenPercent); 2468 + // does apex extend beyond eclipse of face 2469 + apexDistance = apexDistance + frontRadius/4 + rearRadius/4; 2470 + var isApexVisible = frontRadius * eccen < apexDistance && 2471 + rearRadius * eccen < apexDistance; 2472 + if ( !isApexVisible ) { 2473 + return; 2474 + } 2475 + // update tangents 2476 + // TODO: try something more like horn_old.js updateSortValue() 2477 + var apexAngle = Math.atan2( frontBase.renderNormal.y, frontBase.renderNormal.x ) + 2478 + TAU/2; 2479 + var projectFrontLength = (apexDistance + frontRadius) / eccen; 2480 + var projectRearLength = (apexDistance + rearRadius) / eccen; 2481 + var projectFrontAngle = Math.acos( frontRadius / projectFrontLength ); 2482 + var projectRearAngle = Math.acos( rearRadius / -projectRearLength ); 2483 + // set tangent points 2484 + var tangentFrontA = this.tangentFrontA; 2485 + var tangentFrontB = this.tangentFrontB; 2486 + var tangentRearA = this.tangentRearA; 2487 + var tangentRearB = this.tangentRearB; 2488 + 2489 + tangentFrontA.x = Math.cos( projectFrontAngle ) * frontRadius * eccen; 2490 + tangentFrontA.y = Math.sin( projectFrontAngle ) * frontRadius; 2491 + tangentRearA.x = Math.cos( projectRearAngle ) * rearRadius * eccen; 2492 + tangentRearA.y = Math.sin( projectRearAngle ) * rearRadius; 2493 + 2494 + tangentFrontB.set( this.tangentFrontA ); 2495 + tangentFrontB.y *= -1; 2496 + tangentRearB.set( this.tangentRearA ); 2497 + tangentRearB.y *= -1; 2498 + 2499 + tangentFrontA.rotateZ( apexAngle); 2500 + tangentFrontB.rotateZ( apexAngle); 2501 + tangentFrontA.add( frontBase.renderOrigin ); 2502 + tangentFrontB.add( frontBase.renderOrigin ); 2503 + tangentRearA.rotateZ( apexAngle + TAU/2); 2504 + tangentRearB.rotateZ( apexAngle + TAU/2); 2505 + tangentRearA.add( rearBase.renderOrigin ); 2506 + tangentRearB.add( rearBase.renderOrigin ); 2507 + 2508 + 2509 + // set path command render points 2510 + this.pathCommands[0].renderPoints[0].set( tangentFrontA ); 2511 + this.pathCommands[1].renderPoints[0].set( tangentRearB ); 2512 + this.pathCommands[2].renderPoints[0].set( tangentRearA ); 2513 + this.pathCommands[3].renderPoints[0].set( tangentFrontB ); 2514 + 2515 + if ( renderer.isCanvas ) { 2516 + ctx.lineCap = 'butt'; // nice 2517 + } 2518 + renderer.stroke(ctx, elem, this.stroke, this.color, Shape.prototype.getLineWidth.apply(this)); 2519 + renderer.renderPath( ctx, elem, this.pathCommands ); 2520 + //renderer.stroke( ctx, elem, true, '#333', 0.1 ); // remove once testing is done. 2521 + renderer.fill( ctx, elem, this.fill, this.color ); 2522 + renderer.end( ctx, elem ); 2523 + 2524 + if ( renderer.isCanvas ) { 2525 + ctx.lineCap = 'round'; // reset 2526 + } 2527 + }; 2528 + 2529 + var svgURI = 'http://www.w3.org/2000/svg'; 2530 + 2531 + FunnelGroup.prototype.getRenderElement = function( ctx, renderer ) { 2532 + if ( !renderer.isSvg ) { 2533 + return; 2534 + } 2535 + if ( !this.svgElement ) { 2536 + // create svgElement 2537 + this.svgElement = document.createElementNS( svgURI, 'path'); 2538 + this.svgElement.setAttribute( 'stroke-linecap', 'round' ); 2539 + this.svgElement.setAttribute( 'stroke-linejoin', 'round' ); 2540 + } 2541 + return this.svgElement; 2542 + }; 2543 + 2544 + // prevent double-creation in parent.copyGraph() 2545 + // only create in Funnel.create() 2546 + FunnelGroup.prototype.copyGraph = noop; 2547 + 2548 + // ----- FunnelCap ----- // 2549 + 2550 + var FunnelCap = Ellipse.subclass(); 2551 + 2552 + FunnelCap.type = 'FunnelCap'; 2553 + 2554 + FunnelCap.prototype.copyGraph = noop; 2555 + 2556 + // ----- Funnel ----- // 2557 + 2558 + var Funnel = Shape.subclass({ 2559 + frontDiameter: 1, 2560 + rearDiameter: 1, 2561 + length: 1, 2562 + frontFace: undefined, 2563 + fill: true, 2564 + }); 2565 + 2566 + Funnel.type = 'Funnel'; 2567 + 2568 + var TAU = utils.TAU; 2569 + 2570 + Funnel.prototype.create = function(/* options */) { 2571 + // call super 2572 + Shape.prototype.create.apply( this, arguments ); 2573 + // composite shape, create child shapes 2574 + // FunnelGroup to render funnel surface then bases 2575 + this.group = new FunnelGroup({ 2576 + addTo: this, 2577 + color: this.color, 2578 + fill: this.fill, 2579 + stroke: this.stroke, 2580 + visible: this.visible, 2581 + }); 2582 + var baseZ = this.length/2; 2583 + var baseColor = this.backface || true; 2584 + // front outside base 2585 + this.frontBase = this.group.frontBase = new FunnelCap({ 2586 + addTo: this.group, 2587 + translate: { z: (baseZ - this.frontDiameter/2) }, 2588 + rotate: { y: TAU/2 }, 2589 + color: this.color, 2590 + diameter: this.frontDiameter, 2591 + fill: this.fill, 2592 + stroke: this.stroke, 2593 + backface: this.frontFace || baseColor, 2594 + visible: this.visible, 2595 + }); 2596 + // back outside base 2597 + this.rearBase = this.group.rearBase = new FunnelCap({ 2598 + addTo: this.group, 2599 + translate: { z: (-baseZ + this.rearDiameter/2) }, 2600 + rotate: { y: 0 }, 2601 + color: this.color, 2602 + diameter: this.rearDiameter, 2603 + fill: this.fill, 2604 + stroke: this.stroke, 2605 + backface: baseColor, 2606 + visible: this.visible, 2607 + }); 2608 + 2609 + }; 2610 + 2611 + Funnel.prototype.updateFrontCapDiameter = function(size) { 2612 + this.frontBase.diameter = size; 2613 + var baseZ = this.length/2; 2614 + this.frontBase.translate.z = (baseZ - size/2); 2615 + } 2616 + 2617 + Funnel.prototype.updateRearCapDiameter = function(size) { 2618 + this.rearBase.diameter = size; 2619 + var baseZ = this.length/2; 2620 + this.rearBase.translate.z = (-baseZ + size/2); 2621 + } 2622 + 2623 + // Funnel shape does not render anything 2624 + Funnel.prototype.render = function() {}; 2625 + 2626 + // ----- set child properties ----- // 2627 + 2628 + var childProperties = [ 'stroke', 'fill', 'color', 'visible', 2629 + 'frontDiameter', 'rearDiameter' ]; 2630 + childProperties.forEach( function( property ) { 2631 + // use proxy property for custom getter & setter 2632 + var _prop = '_' + property; 2633 + Object.defineProperty( Funnel.prototype, property, { 2634 + get: function() { 2635 + return this[ _prop ]; 2636 + }, 2637 + set: function( value ) { 2638 + this[ _prop ] = value; 2639 + // set property on children 2640 + if ( this.frontBase ) { 2641 + if (property === 'frontDiameter') { 2642 + this.updateFrontCapDiameter(value); 2643 + } 2644 + if (property === 'rearDiameter') { 2645 + this.updateRearCapDiameter(value); 2646 + } 2647 + this.frontBase[ property ] = value; 2648 + this.rearBase[ property ] = value; 2649 + this.group[ property ] = value; 2650 + } 2651 + }, 2652 + }); 2653 + }); 2654 + 2655 + // TODO child property setter for backface, frontBaseColor, & rearBaseColor 2656 + 2657 + return Funnel; 2658 + 2659 + })); 2660 + /** 1987 2661 * Box composite shape 1988 2662 */ 1989 2663 ··· 2003 2677 // ----- BoxRect ----- // 2004 2678 2005 2679 var BoxRect = Rect.subclass(); 2680 + 2006 2681 // prevent double-creation in parent.copyGraph() 2007 2682 // only create in Box.create() 2008 - BoxRect.prototype.copyGraph = function() {}; 2683 + BoxRect.prototype.copyGraph = function() { }; 2009 2684 2010 2685 // ----- Box ----- // 2011 2686 ··· 2028 2703 width: 1, 2029 2704 height: 1, 2030 2705 depth: 1, 2031 - fill: true, 2032 - } ); 2706 + fill: true 2707 + }, Shape.defaults ); 2708 + delete boxDefaults.path; 2033 2709 2034 2710 var Box = Anchor.subclass( boxDefaults ); 2711 + Box.ignoreKeysJSON = [ 'children' ]; 2712 + Box.type = 'Box'; 2035 2713 2036 2714 /* eslint-disable no-self-assign */ 2037 2715 Box.prototype.create = function( options ) { ··· 2072 2750 } 2073 2751 // update & add face 2074 2752 var options = this.getFaceOptions( faceName ); 2075 - options.color = typeof value == 'string' ? value : this.color; 2753 + if (utils.isColor(value)) { 2754 + options.color = value; 2755 + } else { 2756 + options.color = utils.cloneColor(this.color); 2757 + } 2076 2758 2077 2759 if ( rect ) { 2078 2760 // update previous ··· 2154 2836 2155 2837 } ) ); 2156 2838 /** 2839 + * Texture 2840 + */ 2841 + ( function( root, factory ) { 2842 + // module definition 2843 + if ( typeof module == 'object' && module.exports ) { 2844 + // CommonJS 2845 + module.exports = factory(require('./vector')); 2846 + } else { 2847 + // browser global 2848 + var Zdog = root.Zdog; 2849 + Zdog.Texture = factory(Zdog.Vector); 2850 + } 2851 + }( this, function factory(Vector) { 2852 + 2853 + /** 2854 + * Calculates the inverse of the matrix: 2855 + * | x1 x2 x3 | 2856 + * | y1 y2 y3 | 2857 + * | 1 1 1 | 2858 + */ 2859 + function inverse(x1, y1, x2, y2, x3, y3) { 2860 + let tp = [ 2861 + y2 - y3, x3 - x2, x2*y3 - x3*y2, 2862 + y3 - y1, x1 - x3, x3*y1 - x1*y3, 2863 + y1 - y2, x2 - x1, x1*y2 - x2*y1]; 2864 + let det = tp[2] + tp[5] + tp[8]; 2865 + return tp.map(function(x) { return x / det;}); 2866 + } 2867 + 2868 + function parsePointMap(size, map) { 2869 + if (!Array.isArray(map) || !map.length) { 2870 + map = [0, 0, size[0], size[1]]; 2871 + } 2872 + if (typeof(map[0]) == "number") { 2873 + if (map.length < 4) { 2874 + let tmp = map; 2875 + map = [0, 0, size[0], size[1]]; 2876 + for (let i = 0; i < tmp.length; i++) { 2877 + map[i] = tmp[i]; 2878 + } 2879 + } 2880 + return [ 2881 + new Vector({x:map[0], y:map[1], z:1}), 2882 + new Vector({x:map[0] + map[2], y:map[1], z:1}), 2883 + new Vector({x:map[0], y:map[1] + map[3], z:1}) 2884 + ]; 2885 + } else { 2886 + return [new Vector(map[0]), new Vector(map[1]), new Vector(map[2])]; 2887 + } 2888 + } 2889 + 2890 + var idCounter = 0; 2891 + 2892 + const optionKeys = [ 2893 + 'img', 2894 + 'linearGrad', 2895 + 'radialGrad', 2896 + 'colorStops', 2897 + 'src', 2898 + 'dst' 2899 + ] 2900 + 2901 + /** 2902 + * Creates a tecture map. Possible options: 2903 + * img: Image object to be used as texture 2904 + * linearGrad: [x1, y1, x2, y2] Array defining the linear gradient 2905 + * radialGrad: [x0, y0, r0, x1, y1, r1] Array defining the radial gradient 2906 + * colorStops: [offset1, color1, offset2, color2...] Array defining the color 2907 + * stops for the gradient, offset must be in range [0, 1] 2908 + * 2909 + * src: <surface definition> Represents the surface for the texture. Above 2910 + * gradient definition should be represented in this coordinate space 2911 + * dst: <surface definition> Represents the surface of the object. This allows 2912 + * keeping the texture definition independent of the surface definition 2913 + * 2914 + * <surface definition> Can be represented in one of the following ways: 2915 + * [x, y, width, height] => We use 3 points top-left, top-right, bottom-left 2916 + * [x, y] => image/gradient size is used for width and height with the above rule 2917 + * [vector, vector, vector] => provided points are used 2918 + */ 2919 + function Texture(options) { 2920 + this.id = idCounter++; 2921 + this.isTexture = true; 2922 + 2923 + options = options || { } 2924 + for (var key in options ) { 2925 + if (optionKeys.indexOf( key ) != -1 ) { 2926 + this[key] = options[key]; 2927 + } 2928 + } 2929 + 2930 + var size; 2931 + if (options.img) { 2932 + size = [options.img.width, options.img.height]; 2933 + } else if (options.linearGrad) { 2934 + size = [Math.abs(options.linearGrad[2] - options.linearGrad[0]), Math.abs(options.linearGrad[3] - options.linearGrad[1])]; 2935 + } else if (options.radialGrad) { 2936 + size = [Math.abs(options.radialGrad[3] - options.radialGrad[0]), Math.abs(options.radialGrad[4] - options.radialGrad[1])]; 2937 + } else { 2938 + throw "One of [img, linearGrad, radialGrad] is required"; 2939 + } 2940 + if (size[0] == 0) size[0] = size[1]; 2941 + if (size[1] == 0) size[1] = size[0]; 2942 + 2943 + this.src = parsePointMap(size, options.src); 2944 + this.dst = parsePointMap(size, options.dst); 2945 + 2946 + this.srcInverse = inverse( 2947 + this.src[0].x, this.src[0].y, 2948 + this.src[1].x, this.src[1].y, 2949 + this.src[2].x, this.src[2].y); 2950 + this.p1 = new Vector(); 2951 + this.p2 = new Vector(); 2952 + this.p3 = new Vector(); 2953 + this.matrix = [0, 0, 0, 0, 0, 0]; 2954 + }; 2955 + 2956 + Texture.prototype.getMatrix = function() { 2957 + let m = this.matrix; 2958 + let inverse = this.srcInverse; 2959 + m[0] = this.p1.x * inverse[0] + this.p2.x * inverse[3] + this.p3.x * inverse[6]; 2960 + m[1] = this.p1.y * inverse[0] + this.p2.y * inverse[3] + this.p3.y * inverse[6]; 2961 + m[2] = this.p1.x * inverse[1] + this.p2.x * inverse[4] + this.p3.x * inverse[7]; 2962 + m[3] = this.p1.y * inverse[1] + this.p2.y * inverse[4] + this.p3.y * inverse[7]; 2963 + m[4] = this.p1.x * inverse[2] + this.p2.x * inverse[5] + this.p3.x * inverse[8]; 2964 + m[5] = this.p1.y * inverse[2] + this.p2.y * inverse[5] + this.p3.y * inverse[8]; 2965 + return m; 2966 + } 2967 + 2968 + Texture.prototype.getCanvasFill = function(ctx) { 2969 + if (!this.pattern) { 2970 + if (this.img) { 2971 + this.pattern = ctx.createPattern(this.img, "repeat"); 2972 + } else { 2973 + this.pattern = this.linearGrad 2974 + ? ctx.createLinearGradient.apply(ctx, this.linearGrad) 2975 + : ctx.createRadialGradient.apply(ctx, this.radialGrad); 2976 + if (this.colorStops) { 2977 + for (var i = 0; i < this.colorStops.length; i+=2) { 2978 + this.pattern.addColorStop(this.colorStops[i], this.colorStops[i+1]); 2979 + } 2980 + } 2981 + } 2982 + } 2983 + // pattern.setTransform is not supported in IE, 2984 + // so transform the context instead 2985 + ctx.transform.apply(ctx, this.getMatrix()); 2986 + return this.pattern; 2987 + }; 2988 + 2989 + const svgURI = 'http://www.w3.org/2000/svg'; 2990 + Texture.prototype.getSvgFill = function(svg) { 2991 + if (!this.svgPattern) { 2992 + if (this.img) { 2993 + this.svgPattern = document.createElementNS( svgURI, 'pattern'); 2994 + this.svgPattern.setAttribute("width", this.img.width); 2995 + this.svgPattern.setAttribute("height", this.img.height); 2996 + this.svgPattern.setAttribute("patternUnits", "userSpaceOnUse"); 2997 + this.attrTransform = "patternTransform"; 2998 + 2999 + let img = document.createElementNS( svgURI, 'image'); 3000 + img.setAttribute("href", this.img.src); 3001 + this.svgPattern.appendChild(img); 3002 + } else { 3003 + var type, vals, keys; 3004 + if (this.linearGrad) { 3005 + type = "linearGradient"; 3006 + vals = this.linearGrad; 3007 + keys = ["x1", "y1", "x2", "y2"] 3008 + } else { 3009 + type = "radialGradient"; 3010 + vals = this.radialGrad; 3011 + keys = ["fx", "fy", "fr", "cx", "cy", "r"] 3012 + } 3013 + this.svgPattern = document.createElementNS( svgURI, type); 3014 + for (var i = 0; i < keys.length; i++) { 3015 + this.svgPattern.setAttribute(keys[i], vals[i]); 3016 + } 3017 + 3018 + if (this.colorStops) { 3019 + for (var i = 0; i < this.colorStops.length; i+=2) { 3020 + let colorStop = document.createElementNS(svgURI, 'stop' ); 3021 + colorStop.setAttribute("offset", this.colorStops[i]); 3022 + colorStop.setAttribute("style", "stop-color:" + this.colorStops[i+1]); 3023 + this.svgPattern.appendChild(colorStop); 3024 + } 3025 + } 3026 + this.svgPattern.setAttribute("gradientUnits", "userSpaceOnUse"); 3027 + this.attrTransform = "gradientTransform"; 3028 + } 3029 + this.svgPattern.setAttribute("id", "texture_" + this.id); 3030 + this._svgUrl = 'url(#texture_' + this.id + ')'; 3031 + 3032 + this.defs = document.createElementNS(svgURI, 'defs' ); 3033 + this.defs.appendChild(this.svgPattern); 3034 + } 3035 + 3036 + this.svgPattern.setAttribute(this.attrTransform, 'matrix(' + this.getMatrix().join(' ') + ')'); 3037 + svg.appendChild( this.defs ); 3038 + return this._svgUrl; 3039 + } 3040 + 3041 + // ----- update ----- // 3042 + Texture.prototype.reset = function() { 3043 + this.p1.set(this.dst[0]); 3044 + this.p2.set(this.dst[1]); 3045 + this.p3.set(this.dst[2]); 3046 + }; 3047 + 3048 + Texture.prototype.transform = function( translation, rotation, scale ) { 3049 + this.p1.transform(translation, rotation, scale); 3050 + this.p2.transform(translation, rotation, scale); 3051 + this.p3.transform(translation, rotation, scale); 3052 + }; 3053 + 3054 + Texture.prototype.clone = function() { 3055 + return new Texture(this); 3056 + }; 3057 + 3058 + return Texture; 3059 + } ) ); 3060 + /** 2157 3061 * Index 2158 3062 */ 2159 3063 ··· 2179 3083 require('./hemisphere'), 2180 3084 require('./cylinder'), 2181 3085 require('./cone'), 2182 - require('./box') 3086 + require('./horn'), 3087 + require('./funnel'), 3088 + require('./box'), 3089 + require('./texture') 2183 3090 ); 2184 3091 } else if ( typeof define == 'function' && define.amd ) { 2185 3092 /* globals define */ // AMD ··· 2188 3095 /* eslint-disable max-params */ 2189 3096 } )( this, function factory( Zdog, CanvasRenderer, SvgRenderer, Vector, Anchor, 2190 3097 Dragger, Illustration, PathCommand, Shape, Group, Rect, RoundedRect, 2191 - Ellipse, Polygon, Hemisphere, Cylinder, Cone, Box ) { 3098 + Ellipse, Polygon, Hemisphere, Cylinder, Cone, Horn, Funnel, Box, Texture ) { 2192 3099 /* eslint-enable max-params */ 2193 3100 2194 3101 Zdog.CanvasRenderer = CanvasRenderer; ··· 2207 3114 Zdog.Hemisphere = Hemisphere; 2208 3115 Zdog.Cylinder = Cylinder; 2209 3116 Zdog.Cone = Cone; 3117 + Zdog.Horn = Horn; 3118 + Zdog.Funnel = Funnel; 2210 3119 Zdog.Box = Box; 3120 + Zdog.Texture = Texture; 2211 3121 2212 3122 return Zdog; 2213 3123 } );
+1 -1
dist/zdog.dist.min.js
··· 5 5 * https://zzz.dog 6 6 * Copyright 2020 Metafizzy 7 7 */ 8 - (function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog=e()}})(this,function t(){var e={};e.TAU=Math.PI*2;e.extend=function(t,e){for(var r in e){t[r]=e[r]}return t};e.lerp=function(t,e,r){return(e-t)*r+t};e.modulo=function(t,e){return(t%e+e)%e};var s={2:function(t){return t*t},3:function(t){return t*t*t},4:function(t){return t*t*t*t},5:function(t){return t*t*t*t*t}};e.easeInOut=function(t,e){if(e==1){return t}t=Math.max(0,Math.min(1,t));var r=t<.5;var i=r?t:1-t;i/=.5;var n=s[e]||s[2];var o=n(i);o/=2;return r?o:1-o};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.CanvasRenderer=e()}})(this,function t(){var n={isCanvas:true};n.begin=function(t){t.beginPath()};n.move=function(t,e,r){t.moveTo(r.x,r.y)};n.line=function(t,e,r){t.lineTo(r.x,r.y)};n.bezier=function(t,e,r,i,n){t.bezierCurveTo(r.x,r.y,i.x,i.y,n.x,n.y)};n.closePath=function(t){t.closePath()};n.setPath=function(){};n.renderPath=function(e,r,t,i){this.begin(e,r);t.forEach(function(t){t.render(e,r,n)});if(i){this.closePath(e,r)}};n.stroke=function(t,e,r,i,n){if(!r){return}t.strokeStyle=i;t.lineWidth=n;t.stroke()};n.fill=function(t,e,r,i){if(!r){return}t.fillStyle=i;t.fill()};n.end=function(){};return n});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.SvgRenderer=e()}})(this,function t(){var o={isSvg:true};var e=o.round=function(t){return Math.round(t*1e3)/1e3};function s(t){return e(t.x)+","+e(t.y)+" "}o.begin=function(){};o.move=function(t,e,r){return"M"+s(r)};o.line=function(t,e,r){return"L"+s(r)};o.bezier=function(t,e,r,i,n){return"C"+s(r)+s(i)+s(n)};o.closePath=function(){return"Z"};o.setPath=function(t,e,r){e.setAttribute("d",r)};o.renderPath=function(e,r,t,i){var n="";t.forEach(function(t){n+=t.render(e,r,o)});if(i){n+=this.closePath(e,r)}this.setPath(e,r,n)};o.stroke=function(t,e,r,i,n){if(!r){return}e.setAttribute("stroke",i);e.setAttribute("stroke-width",n)};o.fill=function(t,e,r,i){var n=r?i:"none";e.setAttribute("fill",n)};o.end=function(t,e){t.appendChild(e)};return o});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"))}else{var r=t.Zdog;r.Vector=e(r)}})(this,function t(r){function e(t){this.set(t)}var h=r.TAU;e.prototype.set=function(t){this.x=t&&t.x||0;this.y=t&&t.y||0;this.z=t&&t.z||0;return this};e.prototype.write=function(t){if(!t){return this}this.x=t.x!=undefined?t.x:this.x;this.y=t.y!=undefined?t.y:this.y;this.z=t.z!=undefined?t.z:this.z;return this};e.prototype.rotate=function(t){if(!t){return}this.rotateZ(t.z);this.rotateY(t.y);this.rotateX(t.x);return this};e.prototype.rotateZ=function(t){i(this,t,"x","y")};e.prototype.rotateX=function(t){i(this,t,"y","z")};e.prototype.rotateY=function(t){i(this,t,"x","z")};function i(t,e,r,i){if(!e||e%h===0){return}var n=Math.cos(e);var o=Math.sin(e);var s=t[r];var a=t[i];t[r]=s*n-a*o;t[i]=a*n+s*o}e.prototype.isSame=function(t){if(!t){return false}return this.x===t.x&&this.y===t.y&&this.z===t.z};e.prototype.add=function(t){if(!t){return this}this.x+=t.x||0;this.y+=t.y||0;this.z+=t.z||0;return this};e.prototype.subtract=function(t){if(!t){return this}this.x-=t.x||0;this.y-=t.y||0;this.z-=t.z||0;return this};e.prototype.multiply=function(t){if(t==undefined){return this}if(typeof t=="number"){this.x*=t;this.y*=t;this.z*=t}else{this.x*=t.x!=undefined?t.x:1;this.y*=t.y!=undefined?t.y:1;this.z*=t.z!=undefined?t.z:1}return this};e.prototype.transform=function(t,e,r){this.multiply(r);this.rotate(e);this.add(t);return this};e.prototype.lerp=function(t,e){this.x=r.lerp(this.x,t.x||0,e);this.y=r.lerp(this.y,t.y||0,e);this.z=r.lerp(this.z,t.z||0,e);return this};e.prototype.magnitude=function(){var t=this.x*this.x+this.y*this.y+this.z*this.z;return n(t)};function n(t){if(Math.abs(t-1)<1e-8){return 1}return Math.sqrt(t)}e.prototype.magnitude2d=function(){var t=this.x*this.x+this.y*this.y;return n(t)};e.prototype.copy=function(){return new e(this)};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./vector"),require("./canvas-renderer"),require("./svg-renderer"))}else{var r=t.Zdog;r.Anchor=e(r,r.Vector,r.CanvasRenderer,r.SvgRenderer)}})(this,function t(n,e,r,i){var o=n.TAU;var s={x:1,y:1,z:1};function a(t){this.create(t||{})}a.prototype.create=function(t){this.children=[];n.extend(this,this.constructor.defaults);this.setOptions(t);this.translate=new e(t.translate);this.rotate=new e(t.rotate);this.scale=new e(s).multiply(this.scale);this.origin=new e;this.renderOrigin=new e;if(this.addTo){this.addTo.addChild(this)}};a.defaults={};a.optionKeys=Object.keys(a.defaults).concat(["rotate","translate","scale","addTo"]);a.prototype.setOptions=function(t){var e=this.constructor.optionKeys;for(var r in t){if(e.indexOf(r)!=-1){this[r]=t[r]}}};a.prototype.addChild=function(t){if(this.children.indexOf(t)!=-1){return}t.remove();t.addTo=this;this.children.push(t)};a.prototype.removeChild=function(t){var e=this.children.indexOf(t);if(e!=-1){this.children.splice(e,1)}};a.prototype.remove=function(){if(this.addTo){this.addTo.removeChild(this)}};a.prototype.update=function(){this.reset();this.children.forEach(function(t){t.update()});this.transform(this.translate,this.rotate,this.scale)};a.prototype.reset=function(){this.renderOrigin.set(this.origin)};a.prototype.transform=function(e,r,i){this.renderOrigin.transform(e,r,i);this.children.forEach(function(t){t.transform(e,r,i)})};a.prototype.updateGraph=function(){this.update();this.updateFlatGraph();this.flatGraph.forEach(function(t){t.updateSortValue()});this.flatGraph.sort(a.shapeSorter)};a.shapeSorter=function(t,e){return t.sortValue-e.sortValue};Object.defineProperty(a.prototype,"flatGraph",{get:function(){if(!this._flatGraph){this.updateFlatGraph()}return this._flatGraph},set:function(t){this._flatGraph=t}});a.prototype.updateFlatGraph=function(){this.flatGraph=this.getFlatGraph()};a.prototype.getFlatGraph=function(){var t=[this];return this.addChildFlatGraph(t)};a.prototype.addChildFlatGraph=function(r){this.children.forEach(function(t){var e=t.getFlatGraph();Array.prototype.push.apply(r,e)});return r};a.prototype.updateSortValue=function(){this.sortValue=this.renderOrigin.z};a.prototype.render=function(){};a.prototype.renderGraphCanvas=function(e){if(!e){throw new Error("ctx is "+e+". "+"Canvas context required for render. Check .renderGraphCanvas( ctx ).")}this.flatGraph.forEach(function(t){t.render(e,r)})};a.prototype.renderGraphSvg=function(e){if(!e){throw new Error("svg is "+e+". "+"SVG required for render. Check .renderGraphSvg( svg ).")}this.flatGraph.forEach(function(t){t.render(e,i)})};a.prototype.copy=function(t){var e={};var r=this.constructor.optionKeys;r.forEach(function(t){e[t]=this[t]},this);n.extend(e,t);var i=this.constructor;return new i(e)};a.prototype.copyGraph=function(t){var e=this.copy(t);this.children.forEach(function(t){t.copyGraph({addTo:e})});return e};a.prototype.normalizeRotate=function(){this.rotate.x=n.modulo(this.rotate.x,o);this.rotate.y=n.modulo(this.rotate.y,o);this.rotate.z=n.modulo(this.rotate.z,o)};function h(r){return function(t){function e(t){this.create(t||{})}e.prototype=Object.create(r.prototype);e.prototype.constructor=e;e.defaults=n.extend({},r.defaults);n.extend(e.defaults,t);e.optionKeys=r.optionKeys.slice(0);Object.keys(e.defaults).forEach(function(t){if(!e.optionKeys.indexOf(t)!=1){e.optionKeys.push(t)}});e.subclass=h(e);return e}}a.subclass=h(a);return a});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.Dragger=e()}})(this,function t(){var r=typeof window!="undefined";var e="mousedown";var i="mousemove";var n="mouseup";if(r){if(window.PointerEvent){e="pointerdown";i="pointermove";n="pointerup"}else if("ontouchstart"in window){e="touchstart";i="touchmove";n="touchend"}}function o(){}function s(t){this.create(t||{})}s.prototype.create=function(t){this.onDragStart=t.onDragStart||o;this.onDragMove=t.onDragMove||o;this.onDragEnd=t.onDragEnd||o;this.bindDrag(t.startElement)};s.prototype.bindDrag=function(t){t=this.getQueryElement(t);if(!t){return}t.style.touchAction="none";t.addEventListener(e,this)};s.prototype.getQueryElement=function(t){if(typeof t=="string"){t=document.querySelector(t)}return t};s.prototype.handleEvent=function(t){var e=this["on"+t.type];if(e){e.call(this,t)}};s.prototype.onmousedown=s.prototype.onpointerdown=function(t){this.dragStart(t,t)};s.prototype.ontouchstart=function(t){this.dragStart(t,t.changedTouches[0])};s.prototype.dragStart=function(t,e){t.preventDefault();this.dragStartX=e.pageX;this.dragStartY=e.pageY;if(r){window.addEventListener(i,this);window.addEventListener(n,this)}this.onDragStart(e)};s.prototype.ontouchmove=function(t){this.dragMove(t,t.changedTouches[0])};s.prototype.onmousemove=s.prototype.onpointermove=function(t){this.dragMove(t,t)};s.prototype.dragMove=function(t,e){t.preventDefault();var r=e.pageX-this.dragStartX;var i=e.pageY-this.dragStartY;this.onDragMove(e,r,i)};s.prototype.onmouseup=s.prototype.onpointerup=s.prototype.ontouchend=s.prototype.dragEnd=function(){window.removeEventListener(i,this);window.removeEventListener(n,this);this.onDragEnd()};return s});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./anchor"),require("./dragger"))}else{var r=t.Zdog;r.Illustration=e(r,r.Anchor,r.Dragger)}})(this,function t(e,r,a){function i(){}var h=e.TAU;var n=r.subclass({element:undefined,centered:true,zoom:1,dragRotate:false,resize:false,onPrerender:i,onDragStart:i,onDragMove:i,onDragEnd:i,onResize:i});e.extend(n.prototype,a.prototype);n.prototype.create=function(t){r.prototype.create.call(this,t);a.prototype.create.call(this,t);this.setElement(this.element);this.setDragRotate(this.dragRotate);this.setResize(this.resize)};n.prototype.setElement=function(t){t=this.getQueryElement(t);if(!t){throw new Error("Zdog.Illustration element required. Set to "+t)}var e=t.nodeName.toLowerCase();if(e=="canvas"){this.setCanvas(t)}else if(e=="svg"){this.setSvg(t)}};n.prototype.setSize=function(t,e){t=Math.round(t);e=Math.round(e);if(this.isCanvas){this.setSizeCanvas(t,e)}else if(this.isSvg){this.setSizeSvg(t,e)}};n.prototype.setResize=function(t){this.resize=t;if(!this.resizeListener){this.resizeListener=this.onWindowResize.bind(this)}if(t){window.addEventListener("resize",this.resizeListener);this.onWindowResize()}else{window.removeEventListener("resize",this.resizeListener)}};n.prototype.onWindowResize=function(){this.setMeasuredSize();this.onResize(this.width,this.height)};n.prototype.setMeasuredSize=function(){var t,e;var r=this.resize=="fullscreen";if(r){t=window.innerWidth;e=window.innerHeight}else{var i=this.element.getBoundingClientRect();t=i.width;e=i.height}this.setSize(t,e)};n.prototype.renderGraph=function(t){if(this.isCanvas){this.renderGraphCanvas(t)}else if(this.isSvg){this.renderGraphSvg(t)}};n.prototype.updateRenderGraph=function(t){this.updateGraph();this.renderGraph(t)};n.prototype.setCanvas=function(t){this.element=t;this.isCanvas=true;this.ctx=this.element.getContext("2d");this.setSizeCanvas(t.width,t.height)};n.prototype.setSizeCanvas=function(t,e){this.width=t;this.height=e;var r=this.pixelRatio=window.devicePixelRatio||1;this.element.width=this.canvasWidth=t*r;this.element.height=this.canvasHeight=e*r;var i=r>1&&!this.resize;if(i){this.element.style.width=t+"px";this.element.style.height=e+"px"}};n.prototype.renderGraphCanvas=function(t){t=t||this;this.prerenderCanvas();r.prototype.renderGraphCanvas.call(t,this.ctx);this.postrenderCanvas()};n.prototype.prerenderCanvas=function(){var t=this.ctx;t.lineCap="round";t.lineJoin="round";t.clearRect(0,0,this.canvasWidth,this.canvasHeight);t.save();if(this.centered){var e=this.width/2*this.pixelRatio;var r=this.height/2*this.pixelRatio;t.translate(e,r)}var i=this.pixelRatio*this.zoom;t.scale(i,i);this.onPrerender(t)};n.prototype.postrenderCanvas=function(){this.ctx.restore()};n.prototype.setSvg=function(t){this.element=t;this.isSvg=true;this.pixelRatio=1;var e=t.getAttribute("width");var r=t.getAttribute("height");this.setSizeSvg(e,r)};n.prototype.setSizeSvg=function(t,e){this.width=t;this.height=e;var r=t/this.zoom;var i=e/this.zoom;var n=this.centered?-r/2:0;var o=this.centered?-i/2:0;this.element.setAttribute("viewBox",n+" "+o+" "+r+" "+i);if(this.resize){this.element.removeAttribute("width");this.element.removeAttribute("height")}else{this.element.setAttribute("width",t);this.element.setAttribute("height",e)}};n.prototype.renderGraphSvg=function(t){t=t||this;o(this.element);this.onPrerender(this.element);r.prototype.renderGraphSvg.call(t,this.element)};function o(t){while(t.firstChild){t.removeChild(t.firstChild)}}n.prototype.setDragRotate=function(t){if(!t){return}else if(t===true){t=this}this.dragRotate=t;this.bindDrag(this.element)};n.prototype.dragStart=function(){this.dragStartRX=this.dragRotate.rotate.x;this.dragStartRY=this.dragRotate.rotate.y;a.prototype.dragStart.apply(this,arguments)};n.prototype.dragMove=function(t,e){var r=e.pageX-this.dragStartX;var i=e.pageY-this.dragStartY;var n=Math.min(this.width,this.height);var o=r/n*h;var s=i/n*h;this.dragRotate.rotate.x=this.dragStartRX-s;this.dragRotate.rotate.y=this.dragStartRY-o;a.prototype.dragMove.apply(this,arguments)};return n});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./vector"))}else{var r=t.Zdog;r.PathCommand=e(r.Vector)}})(this,function t(i){function e(t,e,r){this.method=t;this.points=e.map(n);this.renderPoints=e.map(o);this.previousPoint=r;this.endRenderPoint=this.renderPoints[this.renderPoints.length-1];if(t=="arc"){this.controlPoints=[new i,new i]}}function n(t){if(t instanceof i){return t}else{return new i(t)}}function o(t){return new i(t)}e.prototype.reset=function(){var i=this.points;this.renderPoints.forEach(function(t,e){var r=i[e];t.set(r)})};e.prototype.transform=function(e,r,i){this.renderPoints.forEach(function(t){t.transform(e,r,i)})};e.prototype.render=function(t,e,r){return this[this.method](t,e,r)};e.prototype.move=function(t,e,r){return r.move(t,e,this.renderPoints[0])};e.prototype.line=function(t,e,r){return r.line(t,e,this.renderPoints[0])};e.prototype.bezier=function(t,e,r){var i=this.renderPoints[0];var n=this.renderPoints[1];var o=this.renderPoints[2];return r.bezier(t,e,i,n,o)};var h=9/16;e.prototype.arc=function(t,e,r){var i=this.previousPoint;var n=this.renderPoints[0];var o=this.renderPoints[1];var s=this.controlPoints[0];var a=this.controlPoints[1];s.set(i).lerp(n,h);a.set(o).lerp(n,h);return r.bezier(t,e,s,a,o)};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./vector"),require("./path-command"),require("./anchor"))}else{var r=t.Zdog;r.Shape=e(r,r.Vector,r.PathCommand,r.Anchor)}})(this,function t(e,r,p,i){var n=i.subclass({stroke:1,fill:false,color:"#333",closed:true,visible:true,path:[{}],front:{z:1},backface:true});n.prototype.create=function(t){i.prototype.create.call(this,t);this.updatePath();this.front=new r(t.front||this.front);this.renderFront=new r(this.front);this.renderNormal=new r};var d=["move","line","bezier","arc"];n.prototype.updatePath=function(){this.setPath();this.updatePathCommands()};n.prototype.setPath=function(){};n.prototype.updatePathCommands=function(){var u;this.pathCommands=this.path.map(function(t,e){var r=Object.keys(t);var i=r[0];var n=t[i];var o=r.length==1&&d.indexOf(i)!=-1;if(!o){i="line";n=t}var s=i=="line"||i=="move";var a=Array.isArray(n);if(s&&!a){n=[n]}i=e===0?"move":i;var h=new p(i,n,u);u=h.endRenderPoint;return h})};n.prototype.reset=function(){this.renderOrigin.set(this.origin);this.renderFront.set(this.front);this.pathCommands.forEach(function(t){t.reset()})};n.prototype.transform=function(e,r,i){this.renderOrigin.transform(e,r,i);this.renderFront.transform(e,r,i);this.renderNormal.set(this.renderOrigin).subtract(this.renderFront);this.pathCommands.forEach(function(t){t.transform(e,r,i)});this.children.forEach(function(t){t.transform(e,r,i)})};n.prototype.updateSortValue=function(){var t=this.pathCommands.length;var e=this.pathCommands[0].endRenderPoint;var r=this.pathCommands[t-1].endRenderPoint;var i=t>2&&e.isSame(r);if(i){t-=1}var n=0;for(var o=0;o<t;o++){n+=this.pathCommands[o].endRenderPoint.z}this.sortValue=n/t};n.prototype.render=function(t,e){var r=this.pathCommands.length;if(!this.visible||!r){return}this.isFacingBack=this.renderNormal.z>0;if(!this.backface&&this.isFacingBack){return}if(!e){throw new Error("Zdog renderer required. Set to "+e)}var i=r==1;if(e.isCanvas&&i){this.renderCanvasDot(t,e)}else{this.renderPath(t,e)}};var o=e.TAU;n.prototype.renderCanvasDot=function(t){var e=this.getLineWidth();if(!e){return}t.fillStyle=this.getRenderColor();var r=this.pathCommands[0].endRenderPoint;t.beginPath();var i=e/2;t.arc(r.x,r.y,i,0,o);t.fill()};n.prototype.getLineWidth=function(){if(!this.stroke){return 0}if(this.stroke==true){return 1}return this.stroke};n.prototype.getRenderColor=function(){var t=typeof this.backface=="string"&&this.isFacingBack;var e=t?this.backface:this.color;return e};n.prototype.renderPath=function(t,e){var r=this.getRenderElement(t,e);var i=this.pathCommands.length==2&&this.pathCommands[1].method=="line";var n=!i&&this.closed;var o=this.getRenderColor();e.renderPath(t,r,this.pathCommands,n);e.stroke(t,r,this.stroke,o,this.getLineWidth());e.fill(t,r,this.fill,o);e.end(t,r)};var s="http://www.w3.org/2000/svg";n.prototype.getRenderElement=function(t,e){if(!e.isSvg){return}if(!this.svgElement){this.svgElement=document.createElementNS(s,"path");this.svgElement.setAttribute("stroke-linecap","round");this.svgElement.setAttribute("stroke-linejoin","round")}return this.svgElement};return n});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./anchor"))}else{var r=t.Zdog;r.Group=e(r.Anchor)}})(this,function t(r){var e=r.subclass({updateSort:false,visible:true});e.prototype.updateSortValue=function(){var e=0;this.flatGraph.forEach(function(t){t.updateSortValue();e+=t.sortValue});this.sortValue=e/this.flatGraph.length;if(this.updateSort){this.flatGraph.sort(r.shapeSorter)}};e.prototype.render=function(e,r){if(!this.visible){return}this.flatGraph.forEach(function(t){t.render(e,r)})};e.prototype.updateFlatGraph=function(){var t=[];this.flatGraph=this.addChildFlatGraph(t)};e.prototype.getFlatGraph=function(){return[this]};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./shape"))}else{var r=t.Zdog;r.Rect=e(r.Shape)}})(this,function t(e){var r=e.subclass({width:1,height:1});r.prototype.setPath=function(){var t=this.width/2;var e=this.height/2;this.path=[{x:-t,y:-e},{x:t,y:-e},{x:t,y:e},{x:-t,y:e}]};return r});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./shape"))}else{var r=t.Zdog;r.RoundedRect=e(r.Shape)}})(this,function t(e){var r=e.subclass({width:1,height:1,cornerRadius:.25,closed:false});r.prototype.setPath=function(){var t=this.width/2;var e=this.height/2;var r=Math.min(t,e);var i=Math.min(this.cornerRadius,r);var n=t-i;var o=e-i;var s=[{x:n,y:-e},{arc:[{x:t,y:-e},{x:t,y:-o}]}];if(o){s.push({x:t,y:o})}s.push({arc:[{x:t,y:e},{x:n,y:e}]});if(n){s.push({x:-n,y:e})}s.push({arc:[{x:-t,y:e},{x:-t,y:o}]});if(o){s.push({x:-t,y:-o})}s.push({arc:[{x:-t,y:-e},{x:-n,y:-e}]});if(n){s.push({x:n,y:-e})}this.path=s};return r});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./shape"))}else{var r=t.Zdog;r.Ellipse=e(r.Shape)}})(this,function t(e){var r=e.subclass({diameter:1,width:undefined,height:undefined,quarters:4,closed:false});r.prototype.setPath=function(){var t=this.width!=undefined?this.width:this.diameter;var e=this.height!=undefined?this.height:this.diameter;var r=t/2;var i=e/2;this.path=[{x:0,y:-i},{arc:[{x:r,y:-i},{x:r,y:0}]}];if(this.quarters>1){this.path.push({arc:[{x:r,y:i},{x:0,y:i}]})}if(this.quarters>2){this.path.push({arc:[{x:-r,y:i},{x:-r,y:0}]})}if(this.quarters>3){this.path.push({arc:[{x:-r,y:-i},{x:0,y:-i}]})}};return r});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./shape"))}else{var r=t.Zdog;r.Polygon=e(r,r.Shape)}})(this,function t(e,r){var i=r.subclass({sides:3,radius:.5});var n=e.TAU;i.prototype.setPath=function(){this.path=[];for(var t=0;t<this.sides;t++){var e=t/this.sides*n-n/4;var r=Math.cos(e)*this.radius;var i=Math.sin(e)*this.radius;this.path.push({x:r,y:i})}};return i});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./vector"),require("./anchor"),require("./ellipse"))}else{var r=t.Zdog;r.Hemisphere=e(r,r.Vector,r.Anchor,r.Ellipse)}})(this,function t(e,r,i,n){var o=n.subclass({fill:true});var u=e.TAU;o.prototype.create=function(){n.prototype.create.apply(this,arguments);this.apex=new i({addTo:this,translate:{z:this.diameter/2}});this.renderCentroid=new r};o.prototype.updateSortValue=function(){this.renderCentroid.set(this.renderOrigin).lerp(this.apex.renderOrigin,3/8);this.sortValue=this.renderCentroid.z};o.prototype.render=function(t,e){this.renderDome(t,e);n.prototype.render.apply(this,arguments)};o.prototype.renderDome=function(t,e){if(!this.visible){return}var r=this.getDomeRenderElement(t,e);var i=Math.atan2(this.renderNormal.y,this.renderNormal.x);var n=this.diameter/2*this.renderNormal.magnitude();var o=this.renderOrigin.x;var s=this.renderOrigin.y;if(e.isCanvas){var a=i+u/4;var h=i-u/4;t.beginPath();t.arc(o,s,n,a,h)}else if(e.isSvg){i=(i-u/4)/u*360;this.domeSvgElement.setAttribute("d","M "+-n+",0 A "+n+","+n+" 0 0 1 "+n+",0");this.domeSvgElement.setAttribute("transform","translate("+o+","+s+" ) rotate("+i+")")}e.stroke(t,r,this.stroke,this.color,this.getLineWidth());e.fill(t,r,this.fill,this.color);e.end(t,r)};var s="http://www.w3.org/2000/svg";o.prototype.getDomeRenderElement=function(t,e){if(!e.isSvg){return}if(!this.domeSvgElement){this.domeSvgElement=document.createElementNS(s,"path");this.domeSvgElement.setAttribute("stroke-linecap","round");this.domeSvgElement.setAttribute("stroke-linejoin","round")}return this.domeSvgElement};return o});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./path-command"),require("./shape"),require("./group"),require("./ellipse"))}else{var r=t.Zdog;r.Cylinder=e(r,r.PathCommand,r.Shape,r.Group,r.Ellipse)}})(this,function t(e,r,i,n,o){function s(){}var a=n.subclass({color:"#333",updateSort:true});a.prototype.create=function(){n.prototype.create.apply(this,arguments);this.pathCommands=[new r("move",[{}]),new r("line",[{}])]};a.prototype.render=function(t,e){this.renderCylinderSurface(t,e);n.prototype.render.apply(this,arguments)};a.prototype.renderCylinderSurface=function(t,e){if(!this.visible){return}var r=this.getRenderElement(t,e);var i=this.frontBase;var n=this.rearBase;var o=i.renderNormal.magnitude();var s=i.diameter*o+i.getLineWidth();this.pathCommands[0].renderPoints[0].set(i.renderOrigin);this.pathCommands[1].renderPoints[0].set(n.renderOrigin);if(e.isCanvas){t.lineCap="butt"}e.renderPath(t,r,this.pathCommands);e.stroke(t,r,true,this.color,s);e.end(t,r);if(e.isCanvas){t.lineCap="round"}};var h="http://www.w3.org/2000/svg";a.prototype.getRenderElement=function(t,e){if(!e.isSvg){return}if(!this.svgElement){this.svgElement=document.createElementNS(h,"path")}return this.svgElement};a.prototype.copyGraph=s;var u=o.subclass();u.prototype.copyGraph=s;var p=i.subclass({diameter:1,length:1,frontFace:undefined,fill:true});var d=e.TAU;p.prototype.create=function(){i.prototype.create.apply(this,arguments);this.group=new a({addTo:this,color:this.color,visible:this.visible});var t=this.length/2;var e=this.backface||true;this.frontBase=this.group.frontBase=new o({addTo:this.group,diameter:this.diameter,translate:{z:t},rotate:{y:d/2},color:this.color,stroke:this.stroke,fill:this.fill,backface:this.frontFace||e,visible:this.visible});this.rearBase=this.group.rearBase=this.frontBase.copy({translate:{z:-t},rotate:{y:0},backface:e})};p.prototype.render=function(){};var c=["stroke","fill","color","visible"];c.forEach(function(e){var r="_"+e;Object.defineProperty(p.prototype,e,{get:function(){return this[r]},set:function(t){this[r]=t;if(this.frontBase){this.frontBase[e]=t;this.rearBase[e]=t;this.group[e]=t}}})});return p});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./vector"),require("./path-command"),require("./anchor"),require("./ellipse"))}else{var r=t.Zdog;r.Cone=e(r,r.Vector,r.PathCommand,r.Anchor,r.Ellipse)}})(this,function t(e,r,i,n,o){var s=o.subclass({length:1,fill:true});var v=e.TAU;s.prototype.create=function(){o.prototype.create.apply(this,arguments);this.apex=new n({addTo:this,translate:{z:this.length}});this.renderApex=new r;this.renderCentroid=new r;this.tangentA=new r;this.tangentB=new r;this.surfacePathCommands=[new i("move",[{}]),new i("line",[{}]),new i("line",[{}])]};s.prototype.updateSortValue=function(){this.renderCentroid.set(this.renderOrigin).lerp(this.apex.renderOrigin,1/3);this.sortValue=this.renderCentroid.z};s.prototype.render=function(t,e){this.renderConeSurface(t,e);o.prototype.render.apply(this,arguments)};s.prototype.renderConeSurface=function(t,e){if(!this.visible){return}this.renderApex.set(this.apex.renderOrigin).subtract(this.renderOrigin);var r=this.renderNormal.magnitude();var i=this.renderApex.magnitude2d();var n=this.renderNormal.magnitude2d();var o=Math.acos(n/r);var s=Math.sin(o);var a=this.diameter/2*r;var h=a*s<i;if(!h){return}var u=Math.atan2(this.renderNormal.y,this.renderNormal.x)+v/2;var p=i/s;var d=Math.acos(a/p);var c=this.tangentA;var f=this.tangentB;c.x=Math.cos(d)*a*s;c.y=Math.sin(d)*a;f.set(this.tangentA);f.y*=-1;c.rotateZ(u);f.rotateZ(u);c.add(this.renderOrigin);f.add(this.renderOrigin);this.setSurfaceRenderPoint(0,c);this.setSurfaceRenderPoint(1,this.apex.renderOrigin);this.setSurfaceRenderPoint(2,f);var l=this.getSurfaceRenderElement(t,e);e.renderPath(t,l,this.surfacePathCommands);e.stroke(t,l,this.stroke,this.color,this.getLineWidth());e.fill(t,l,this.fill,this.color);e.end(t,l)};var a="http://www.w3.org/2000/svg";s.prototype.getSurfaceRenderElement=function(t,e){if(!e.isSvg){return}if(!this.surfaceSvgElement){this.surfaceSvgElement=document.createElementNS(a,"path");this.surfaceSvgElement.setAttribute("stroke-linecap","round");this.surfaceSvgElement.setAttribute("stroke-linejoin","round")}return this.surfaceSvgElement};s.prototype.setSurfaceRenderPoint=function(t,e){var r=this.surfacePathCommands[t].renderPoints[0];r.set(e)};return s});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./anchor"),require("./shape"),require("./rect"))}else{var r=t.Zdog;r.Box=e(r,r.Anchor,r.Shape,r.Rect)}})(this,function t(e,r,i,n){var o=n.subclass();o.prototype.copyGraph=function(){};var s=e.TAU;var a=["frontFace","rearFace","leftFace","rightFace","topFace","bottomFace"];var h=e.extend({},i.defaults);delete h.path;a.forEach(function(t){h[t]=true});e.extend(h,{width:1,height:1,depth:1,fill:true});var u=r.subclass(h);u.prototype.create=function(t){r.prototype.create.call(this,t);this.updatePath();this.fill=this.fill};u.prototype.updatePath=function(){a.forEach(function(t){this[t]=this[t]},this)};a.forEach(function(e){var r="_"+e;Object.defineProperty(u.prototype,e,{get:function(){return this[r]},set:function(t){this[r]=t;this.setFace(e,t)}})});u.prototype.setFace=function(t,e){var r=t+"Rect";var i=this[r];if(!e){this.removeChild(i);return}var n=this.getFaceOptions(t);n.color=typeof e=="string"?e:this.color;if(i){i.setOptions(n)}else{i=this[r]=new o(n)}i.updatePath();this.addChild(i)};u.prototype.getFaceOptions=function(t){return{frontFace:{width:this.width,height:this.height,translate:{z:this.depth/2}},rearFace:{width:this.width,height:this.height,translate:{z:-this.depth/2},rotate:{y:s/2}},leftFace:{width:this.depth,height:this.height,translate:{x:-this.width/2},rotate:{y:-s/4}},rightFace:{width:this.depth,height:this.height,translate:{x:this.width/2},rotate:{y:s/4}},topFace:{width:this.width,height:this.depth,translate:{y:-this.height/2},rotate:{x:-s/4}},bottomFace:{width:this.width,height:this.depth,translate:{y:this.height/2},rotate:{x:s/4}}}[t]};var p=["color","stroke","fill","backface","front","visible"];p.forEach(function(o){var t="_"+o;Object.defineProperty(u.prototype,o,{get:function(){return this[t]},set:function(n){this[t]=n;a.forEach(function(t){var e=this[t+"Rect"];var r=typeof this[t]=="string";var i=o=="color"&&r;if(e&&!i){e[o]=n}},this)}})});return u});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./canvas-renderer"),require("./svg-renderer"),require("./vector"),require("./anchor"),require("./dragger"),require("./illustration"),require("./path-command"),require("./shape"),require("./group"),require("./rect"),require("./rounded-rect"),require("./ellipse"),require("./polygon"),require("./hemisphere"),require("./cylinder"),require("./cone"),require("./box"))}else if(typeof define=="function"&&define.amd){define("zdog",[],t.Zdog)}})(this,function t(e,r,i,n,o,s,a,h,u,p,d,c,f,l,v,y,m,g){e.CanvasRenderer=r;e.SvgRenderer=i;e.Vector=n;e.Anchor=o;e.Dragger=s;e.Illustration=a;e.PathCommand=h;e.Shape=u;e.Group=p;e.Rect=d;e.RoundedRect=c;e.Ellipse=f;e.Polygon=l;e.Hemisphere=v;e.Cylinder=y;e.Cone=m;e.Box=g;return e}); 8 + (function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog=e()}})(this,function t(){var e={};e.TAU=Math.PI*2;e.extend=function(t,e){for(var r in e){t[r]=e[r]}return t};e.lerp=function(t,e,r){return(e-t)*r+t};e.modulo=function(t,e){return(t%e+e)%e};var s={2:function(t){return t*t},3:function(t){return t*t*t},4:function(t){return t*t*t*t},5:function(t){return t*t*t*t*t}};e.easeInOut=function(t,e){if(e==1){return t}t=Math.max(0,Math.min(1,t));var r=t<.5;var i=r?t:1-t;i/=.5;var n=s[e]||s[2];var o=n(i);o/=2;return r?o:1-o};e.isColor=function(t){return typeof t=="string"||t&&t.isTexture};e.cloneColor=function(t){return t&&t.clone?t.clone():t};e.exportGraph=function(t){return JSON.parse(JSON.stringify(t))};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.CanvasRenderer=e()}})(this,function t(){var n={isCanvas:true};n.begin=function(t){t.beginPath()};n.move=function(t,e,r){t.moveTo(r.x,r.y)};n.line=function(t,e,r){t.lineTo(r.x,r.y)};n.bezier=function(t,e,r,i,n){t.bezierCurveTo(r.x,r.y,i.x,i.y,n.x,n.y)};n.closePath=function(t){t.closePath()};n.setPath=function(){};n.renderPath=function(e,r,t,i){this.begin(e,r);t.forEach(function(t){t.render(e,r,n)});if(i){this.closePath(e,r)}};n.stroke=function(t,e,r,i,n){if(!r){return}t.lineWidth=n;if(i&&i.getCanvasFill){t.save();t.strokeStyle=i.getCanvasFill(t);t.stroke();t.restore()}else{t.strokeStyle=i;t.stroke()}};n.fill=function(t,e,r,i){if(!r){return}if(i&&i.getCanvasFill){t.save();t.fillStyle=i.getCanvasFill(t);t.fill();t.restore()}else{t.fillStyle=i;t.fill()}};n.end=function(){};return n});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.SvgRenderer=e()}})(this,function t(){var o={isSvg:true};var e=o.round=function(t){return Math.round(t*1e3)/1e3};function s(t){return e(t.x)+","+e(t.y)+" "}o.begin=function(){};o.move=function(t,e,r){return"M"+s(r)};o.line=function(t,e,r){return"L"+s(r)};o.bezier=function(t,e,r,i,n){return"C"+s(r)+s(i)+s(n)};o.closePath=function(){return"Z"};o.setPath=function(t,e,r){e.setAttribute("d",r)};o.renderPath=function(e,r,t,i){var n="";t.forEach(function(t){n+=t.render(e,r,o)});if(i){n+=this.closePath(e,r)}this.setPath(e,r,n)};o.stroke=function(t,e,r,i,n){if(!r){return}if(i&&i.getSvgFill){i=i.getSvgFill(t)}e.setAttribute("stroke",i);e.setAttribute("stroke-width",n)};o.fill=function(t,e,r,i){var n=r?i:"none";if(n&&n.getSvgFill){n=n.getSvgFill(t)}e.setAttribute("fill",n)};o.end=function(t,e){t.appendChild(e)};return o});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"))}else{var r=t.Zdog;r.Vector=e(r)}})(this,function t(r){function e(t){this.set(t)}var h=r.TAU;e.prototype.set=function(t){this.x=t&&t.x||0;this.y=t&&t.y||0;this.z=t&&t.z||0;return this};e.prototype.write=function(t){if(!t){return this}this.x=t.x!=undefined?t.x:this.x;this.y=t.y!=undefined?t.y:this.y;this.z=t.z!=undefined?t.z:this.z;return this};e.prototype.rotate=function(t){if(!t){return}this.rotateZ(t.z);this.rotateY(t.y);this.rotateX(t.x);return this};e.prototype.rotateZ=function(t){i(this,t,"x","y")};e.prototype.rotateX=function(t){i(this,t,"y","z")};e.prototype.rotateY=function(t){i(this,t,"x","z")};function i(t,e,r,i){if(!e||e%h===0){return}var n=Math.cos(e);var o=Math.sin(e);var s=t[r];var a=t[i];t[r]=s*n-a*o;t[i]=a*n+s*o}e.prototype.isSame=function(t){if(!t){return false}return this.x===t.x&&this.y===t.y&&this.z===t.z};e.prototype.add=function(t){if(!t){return this}this.x+=t.x||0;this.y+=t.y||0;this.z+=t.z||0;return this};e.prototype.subtract=function(t){if(!t){return this}this.x-=t.x||0;this.y-=t.y||0;this.z-=t.z||0;return this};e.prototype.multiply=function(t){if(t==undefined){return this}if(typeof t=="number"){this.x*=t;this.y*=t;this.z*=t}else{this.x*=t.x!=undefined?t.x:1;this.y*=t.y!=undefined?t.y:1;this.z*=t.z!=undefined?t.z:1}return this};e.prototype.transform=function(t,e,r){this.multiply(r);this.rotate(e);this.add(t);return this};e.prototype.lerp=function(t,e){this.x=r.lerp(this.x,t.x||0,e);this.y=r.lerp(this.y,t.y||0,e);this.z=r.lerp(this.z,t.z||0,e);return this};e.prototype.magnitude=function(){var t=this.x*this.x+this.y*this.y+this.z*this.z;return n(t)};function n(t){if(Math.abs(t-1)<1e-8){return 1}return Math.sqrt(t)}e.prototype.magnitude2d=function(){var t=this.x*this.x+this.y*this.y;return n(t)};e.prototype.copy=function(){return new e(this)};function o(t){return Math.round(t*1e3)/1e3}e.prototype.toJSON=function(){var t=this.x;var e=this.y;var r=this.z;if(t===e&&e===r){return t!==0?o(t):undefined}var i={x:t,y:e,z:r};var n={};Object.keys(i).forEach(function(t){var e=i[t];if(e!==0){n[t]=o(e)}});return Object.keys(n).length?n:undefined};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./vector"),require("./canvas-renderer"),require("./svg-renderer"))}else{var r=t.Zdog;r.Anchor=e(r,r.Vector,r.CanvasRenderer,r.SvgRenderer)}})(this,function t(s,e,r,i){var n=s.TAU;var o={x:1,y:1,z:1};function a(t){this.create(t||{})}a.type="Anchor";a.prototype.create=function(t){this.children=[];s.extend(this,this.constructor.defaults);this.setOptions(t);this.translate=new e(t.translate);this.rotate=new e(t.rotate);this.scale=new e(o).multiply(this.scale);this.origin=new e;this.renderOrigin=new e;if(this.addTo){this.addTo.addChild(this)}if(t.importGraph){this.importGraph(t.importGraph)}};a.defaults={};a.optionKeys=Object.keys(a.defaults).concat(["rotate","translate","scale","addTo"]);a.ignoreKeysJSON=["addTo"];a.prototype.setOptions=function(t){var e=this.constructor.optionKeys;for(var r in t){if(e.indexOf(r)!=-1){this[r]=t[r]}}};a.prototype.addChild=function(t){if(this.children.indexOf(t)!=-1){return}t.remove();t.addTo=this;this.children.push(t)};a.prototype.removeChild=function(t){var e=this.children.indexOf(t);if(e!=-1){this.children.splice(e,1)}};a.prototype.remove=function(){if(this.addTo){this.addTo.removeChild(this)}};a.prototype.update=function(){this.reset();this.children.forEach(function(t){t.update()});this.transform(this.translate,this.rotate,this.scale)};a.prototype.reset=function(){this.renderOrigin.set(this.origin)};a.prototype.transform=function(e,r,i){this.renderOrigin.transform(e,r,i);this.children.forEach(function(t){t.transform(e,r,i)})};a.prototype.updateGraph=function(){this.update();this.updateFlatGraph();this.flatGraph.forEach(function(t){t.updateSortValue()});this.flatGraph.sort(a.shapeSorter)};a.shapeSorter=function(t,e){return t.sortValue-e.sortValue};Object.defineProperty(a.prototype,"flatGraph",{get:function(){if(!this._flatGraph){this.updateFlatGraph()}return this._flatGraph},set:function(t){this._flatGraph=t}});a.prototype.updateFlatGraph=function(){this.flatGraph=this.getFlatGraph()};a.prototype.getFlatGraph=function(){var t=[this];return this.addChildFlatGraph(t)};a.prototype.addChildFlatGraph=function(r){this.children.forEach(function(t){var e=t.getFlatGraph();Array.prototype.push.apply(r,e)});return r};a.prototype.updateSortValue=function(){this.sortValue=this.renderOrigin.z};a.prototype.render=function(){};a.prototype.renderGraphCanvas=function(e){if(!e){throw new Error("ctx is "+e+". "+"Canvas context required for render. Check .renderGraphCanvas( ctx ).")}this.flatGraph.forEach(function(t){t.render(e,r)})};a.prototype.renderGraphSvg=function(e){if(!e){throw new Error("svg is "+e+". "+"SVG required for render. Check .renderGraphSvg( svg ).")}this.flatGraph.forEach(function(t){t.render(e,i)})};a.prototype.copy=function(t){var e={};var r=this.constructor.optionKeys;r.forEach(function(t){e[t]=this[t]},this);s.extend(e,t);var i=this.constructor;return new i(e)};a.prototype.copyGraph=function(t){var e=this.copy(t);this.children.forEach(function(t){t.copyGraph({addTo:e})});return e};a.prototype.importGraph=function(t){this.addChild(o(t));function o(t){t=s.extend({},t);var e=t.type==="Illustration"?"Anchor":t.type;var r=(t.children||[]).slice(0);delete t.children;var i=s[e];var n;if(i){n=new i(t);r.forEach(function(t){o(s.extend(t,{addTo:n}))})}return n}};a.prototype.normalizeRotate=function(){this.rotate.x=s.modulo(this.rotate.x,n);this.rotate.y=s.modulo(this.rotate.y,n);this.rotate.z=s.modulo(this.rotate.z,n)};a.prototype.toJSON=function(){var t=this.constructor.type;var i={type:t};var n=this.constructor.defaults;var e=this.constructor.optionKeys.slice(0).concat("children");var o=a.ignoreKeysJSON.slice(0).concat(this.constructor.ignoreKeysJSON||[]);e.forEach(function(t){if(o.indexOf(t)>-1){return}var e=this[t];if(!["undefined","function"].indexOf(typeof e)>-1&&e!==n[t]){if(Array.isArray(e)&&e.length===0){return}if(e.toJSON){var r=e.toJSON();if(typeof r!=="undefined"){if(t==="scale"&&r===1){return}i[t]=r}}else{i[t]=e}}},this);return i};function h(r){return function(t){function e(t){this.create(t||{})}e.prototype=Object.create(r.prototype);e.prototype.constructor=e;e.defaults=s.extend({},r.defaults);s.extend(e.defaults,t);e.optionKeys=r.optionKeys.slice(0);Object.keys(e.defaults).forEach(function(t){if(!e.optionKeys.indexOf(t)!=1){e.optionKeys.push(t)}});e.ignoreKeysJSON=r.ignoreKeysJSON.slice(0);e.subclass=h(e);return e}}a.subclass=h(a);return a});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.Dragger=e()}})(this,function t(){var r=typeof window!="undefined";var e="mousedown";var i="mousemove";var n="mouseup";if(r){if(window.PointerEvent){e="pointerdown";i="pointermove";n="pointerup"}else if("ontouchstart"in window){e="touchstart";i="touchmove";n="touchend"}}function o(){}function s(t){this.create(t||{})}s.prototype.create=function(t){this.onDragStart=t.onDragStart||o;this.onDragMove=t.onDragMove||o;this.onDragEnd=t.onDragEnd||o;this.bindDrag(t.startElement)};s.prototype.bindDrag=function(t){t=this.getQueryElement(t);if(!t){return}t.style.touchAction="none";t.addEventListener(e,this)};s.prototype.getQueryElement=function(t){if(typeof t=="string"){t=document.querySelector(t)}return t};s.prototype.handleEvent=function(t){var e=this["on"+t.type];if(e){e.call(this,t)}};s.prototype.onmousedown=s.prototype.onpointerdown=function(t){this.dragStart(t,t)};s.prototype.ontouchstart=function(t){this.dragStart(t,t.changedTouches[0])};s.prototype.dragStart=function(t,e){t.preventDefault();this.dragStartX=e.pageX;this.dragStartY=e.pageY;if(r){window.addEventListener(i,this);window.addEventListener(n,this)}this.onDragStart(e)};s.prototype.ontouchmove=function(t){this.dragMove(t,t.changedTouches[0])};s.prototype.onmousemove=s.prototype.onpointermove=function(t){this.dragMove(t,t)};s.prototype.dragMove=function(t,e){t.preventDefault();var r=e.pageX-this.dragStartX;var i=e.pageY-this.dragStartY;this.onDragMove(e,r,i)};s.prototype.onmouseup=s.prototype.onpointerup=s.prototype.ontouchend=s.prototype.dragEnd=function(){window.removeEventListener(i,this);window.removeEventListener(n,this);this.onDragEnd()};return s});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./anchor"),require("./dragger"))}else{var r=t.Zdog;r.Illustration=e(r,r.Anchor,r.Dragger)}})(this,function t(e,r,a){function i(){}var h=e.TAU;var n=r.subclass({element:undefined,centered:true,zoom:1,dragRotate:false,resize:false,onPrerender:i,onDragStart:i,onDragMove:i,onDragEnd:i,onResize:i});n.ignoreKeysJSON=["dragRotate","element","resize"];n.type="Illustration";e.extend(n.prototype,a.prototype);n.prototype.create=function(t){r.prototype.create.call(this,t);a.prototype.create.call(this,t);this.setElement(this.element);this.setDragRotate(this.dragRotate);this.setResize(this.resize)};n.prototype.setElement=function(t){t=this.getQueryElement(t);if(!t){throw new Error("Zdog.Illustration element required. Set to "+t)}var e=t.nodeName.toLowerCase();if(e=="canvas"){this.setCanvas(t)}else if(e=="svg"){this.setSvg(t)}};n.prototype.setSize=function(t,e){t=Math.round(t);e=Math.round(e);if(this.isCanvas){this.setSizeCanvas(t,e)}else if(this.isSvg){this.setSizeSvg(t,e)}};n.prototype.setResize=function(t){this.resize=t;if(!this.resizeListener){this.resizeListener=this.onWindowResize.bind(this)}if(t){window.addEventListener("resize",this.resizeListener);this.onWindowResize()}else{window.removeEventListener("resize",this.resizeListener)}};n.prototype.onWindowResize=function(){this.setMeasuredSize();this.onResize(this.width,this.height)};n.prototype.setMeasuredSize=function(){var t,e;var r=this.resize=="fullscreen";if(r){t=window.innerWidth;e=window.innerHeight}else{var i=this.element.getBoundingClientRect();t=i.width;e=i.height}this.setSize(t,e)};n.prototype.renderGraph=function(t){if(this.isCanvas){this.renderGraphCanvas(t)}else if(this.isSvg){this.renderGraphSvg(t)}};n.prototype.updateRenderGraph=function(t){this.updateGraph();this.renderGraph(t)};n.prototype.setCanvas=function(t){this.element=t;this.isCanvas=true;this.ctx=this.element.getContext("2d");this.setSizeCanvas(t.width,t.height)};n.prototype.setSizeCanvas=function(t,e){this.width=t;this.height=e;var r=this.pixelRatio=window.devicePixelRatio||1;this.element.width=this.canvasWidth=t*r;this.element.height=this.canvasHeight=e*r;var i=r>1&&!this.resize;if(i){this.element.style.width=t+"px";this.element.style.height=e+"px"}};n.prototype.renderGraphCanvas=function(t){t=t||this;this.prerenderCanvas();r.prototype.renderGraphCanvas.call(t,this.ctx);this.postrenderCanvas()};n.prototype.prerenderCanvas=function(){var t=this.ctx;t.lineCap="round";t.lineJoin="round";t.clearRect(0,0,this.canvasWidth,this.canvasHeight);t.save();if(this.centered){var e=this.width/2*this.pixelRatio;var r=this.height/2*this.pixelRatio;t.translate(e,r)}var i=this.pixelRatio*this.zoom;t.scale(i,i);this.onPrerender(t)};n.prototype.postrenderCanvas=function(){this.ctx.restore()};n.prototype.setSvg=function(t){this.element=t;this.isSvg=true;this.pixelRatio=1;var e=t.getAttribute("width");var r=t.getAttribute("height");this.setSizeSvg(e,r)};n.prototype.setSizeSvg=function(t,e){this.width=t;this.height=e;var r=t/this.zoom;var i=e/this.zoom;var n=this.centered?-r/2:0;var o=this.centered?-i/2:0;this.element.setAttribute("viewBox",n+" "+o+" "+r+" "+i);if(this.resize){this.element.removeAttribute("width");this.element.removeAttribute("height")}else{this.element.setAttribute("width",t);this.element.setAttribute("height",e)}};n.prototype.renderGraphSvg=function(t){t=t||this;o(this.element);this.onPrerender(this.element);r.prototype.renderGraphSvg.call(t,this.element)};function o(t){while(t.firstChild){t.removeChild(t.firstChild)}}n.prototype.setDragRotate=function(t){if(!t){return}else if(t===true){t=this}this.dragRotate=t;this.bindDrag(this.element)};n.prototype.dragStart=function(){this.dragStartRX=this.dragRotate.rotate.x;this.dragStartRY=this.dragRotate.rotate.y;a.prototype.dragStart.apply(this,arguments)};n.prototype.dragMove=function(t,e){var r=e.pageX-this.dragStartX;var i=e.pageY-this.dragStartY;var n=Math.min(this.width,this.height);var o=r/n*h;var s=i/n*h;this.dragRotate.rotate.x=this.dragStartRX-s;this.dragRotate.rotate.y=this.dragStartRY-o;a.prototype.dragMove.apply(this,arguments)};return n});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./vector"))}else{var r=t.Zdog;r.PathCommand=e(r.Vector)}})(this,function t(i){function e(t,e,r){this.method=t;this.points=e.map(n);this.renderPoints=e.map(o);this.previousPoint=r;this.endRenderPoint=this.renderPoints[this.renderPoints.length-1];if(t=="arc"){this.controlPoints=[new i,new i]}}function n(t){if(t instanceof i){return t}else{return new i(t)}}function o(t){return new i(t)}e.prototype.reset=function(){var i=this.points;this.renderPoints.forEach(function(t,e){var r=i[e];t.set(r)})};e.prototype.transform=function(e,r,i){this.renderPoints.forEach(function(t){t.transform(e,r,i)})};e.prototype.render=function(t,e,r){return this[this.method](t,e,r)};e.prototype.move=function(t,e,r){return r.move(t,e,this.renderPoints[0])};e.prototype.line=function(t,e,r){return r.line(t,e,this.renderPoints[0])};e.prototype.bezier=function(t,e,r){var i=this.renderPoints[0];var n=this.renderPoints[1];var o=this.renderPoints[2];return r.bezier(t,e,i,n,o)};var h=9/16;e.prototype.arc=function(t,e,r){var i=this.previousPoint;var n=this.renderPoints[0];var o=this.renderPoints[1];var s=this.controlPoints[0];var a=this.controlPoints[1];s.set(i).lerp(n,h);a.set(o).lerp(n,h);return r.bezier(t,e,s,a,o)};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./vector"),require("./path-command"),require("./anchor"))}else{var r=t.Zdog;r.Shape=e(r,r.Vector,r.PathCommand,r.Anchor)}})(this,function t(r,e,u,i){var n=i.subclass({stroke:1,fill:false,color:"#333",closed:true,visible:true,path:[{}],front:{z:1},backface:true});n.type="Shape";n.prototype.create=function(t){i.prototype.create.call(this,t);this.updatePath();this.front=new e(t.front||this.front);this.renderFront=new e(this.front);this.renderNormal=new e};var d=["move","line","bezier","arc"];n.prototype.updatePath=function(){this.setPath();this.updatePathCommands()};n.prototype.setPath=function(){};n.prototype.updatePathCommands=function(){var p;this.pathCommands=this.path.map(function(t,e){var r=Object.keys(t);var i=r[0];var n=t[i];var o=r.length==1&&d.indexOf(i)!=-1;if(!o){i="line";n=t}var s=i=="line"||i=="move";var a=Array.isArray(n);if(s&&!a){n=[n]}i=e===0?"move":i;var h=new u(i,n,p);p=h.endRenderPoint;return h})};n.prototype.reset=function(){this.renderOrigin.set(this.origin);this.renderFront.set(this.front);this.pathCommands.forEach(function(t){t.reset()});if(this.backface&&this.backface.reset){this.backface.reset()}if(this.color&&this.color.reset){this.color.reset()}};n.prototype.transform=function(e,r,i){this.renderOrigin.transform(e,r,i);this.renderFront.transform(e,r,i);this.renderNormal.set(this.renderOrigin).subtract(this.renderFront);this.pathCommands.forEach(function(t){t.transform(e,r,i)});this.children.forEach(function(t){t.transform(e,r,i)});if(this.backface&&this.backface.transform){this.backface.transform(e,r,i)}if(this.color&&this.color.transform){this.color.transform(e,r,i)}};n.prototype.updateSortValue=function(){var t=this.pathCommands.length;var e=this.pathCommands[0].endRenderPoint;var r=this.pathCommands[t-1].endRenderPoint;var i=t>2&&e.isSame(r);if(i){t-=1}var n=0;for(var o=0;o<t;o++){n+=this.pathCommands[o].endRenderPoint.z}this.sortValue=n/t};n.prototype.render=function(t,e){var r=this.pathCommands.length;if(!this.visible||!r){return}this.isFacingBack=this.renderNormal.z>0;if(!this.backface&&this.isFacingBack){return}if(!e){throw new Error("Zdog renderer required. Set to "+e)}var i=r==1;if(e.isCanvas&&i){this.renderCanvasDot(t,e)}else{this.renderPath(t,e)}};var o=r.TAU;n.prototype.renderCanvasDot=function(t,e){var r=this.getLineWidth();if(!r){return}var i=this.pathCommands[0].endRenderPoint;t.beginPath();var n=r/2;t.arc(i.x,i.y,n,0,o);e.fill(t,null,true,this.getRenderColor())};n.prototype.getLineWidth=function(){if(!this.stroke){return 0}if(this.stroke==true){return 1}return this.stroke};n.prototype.getRenderColor=function(){var t=r.isColor(this.backface)&&this.isFacingBack;var e=t?this.backface:this.color;return e};n.prototype.renderPath=function(t,e){var r=this.getRenderElement(t,e);var i=this.pathCommands.length==2&&this.pathCommands[1].method=="line";var n=!i&&this.closed;var o=this.getRenderColor();e.renderPath(t,r,this.pathCommands,n);e.stroke(t,r,this.stroke,o,this.getLineWidth());e.fill(t,r,this.fill,o);e.end(t,r)};var s="http://www.w3.org/2000/svg";n.prototype.getRenderElement=function(t,e){if(!e.isSvg){return}if(!this.svgElement){this.svgElement=document.createElementNS(s,"path");this.svgElement.setAttribute("stroke-linecap","round");this.svgElement.setAttribute("stroke-linejoin","round")}return this.svgElement};return n});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./anchor"))}else{var r=t.Zdog;r.Group=e(r.Anchor)}})(this,function t(r){var e=r.subclass({updateSort:false,visible:true});e.type="Group";e.prototype.updateSortValue=function(){var e=0;this.flatGraph.forEach(function(t){t.updateSortValue();e+=t.sortValue});this.sortValue=e/this.flatGraph.length;if(this.updateSort){this.flatGraph.sort(r.shapeSorter)}};e.prototype.render=function(e,r){if(!this.visible){return}this.flatGraph.forEach(function(t){t.render(e,r)})};e.prototype.updateFlatGraph=function(){var t=[];this.flatGraph=this.addChildFlatGraph(t)};e.prototype.getFlatGraph=function(){return[this]};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./shape"))}else{var r=t.Zdog;r.Rect=e(r.Shape)}})(this,function t(e){var r=e.subclass({width:1,height:1});r.type="Rect";r.prototype.setPath=function(){var t=this.width/2;var e=this.height/2;this.path=[{x:-t,y:-e},{x:t,y:-e},{x:t,y:e},{x:-t,y:e}]};return r});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./shape"))}else{var r=t.Zdog;r.RoundedRect=e(r.Shape)}})(this,function t(e){var r=e.subclass({width:1,height:1,cornerRadius:.25,closed:false});r.type="RoundedRect";r.prototype.setPath=function(){var t=this.width/2;var e=this.height/2;var r=Math.min(t,e);var i=Math.min(this.cornerRadius,r);var n=t-i;var o=e-i;var s=[{x:n,y:-e},{arc:[{x:t,y:-e},{x:t,y:-o}]}];if(o){s.push({x:t,y:o})}s.push({arc:[{x:t,y:e},{x:n,y:e}]});if(n){s.push({x:-n,y:e})}s.push({arc:[{x:-t,y:e},{x:-t,y:o}]});if(o){s.push({x:-t,y:-o})}s.push({arc:[{x:-t,y:-e},{x:-n,y:-e}]});if(n){s.push({x:n,y:-e})}this.path=s};return r});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./shape"))}else{var r=t.Zdog;r.Ellipse=e(r.Shape)}})(this,function t(e){var r=e.subclass({diameter:1,width:undefined,height:undefined,quarters:4,closed:false});r.type="Ellipse";r.prototype.setPath=function(){var t=this.width!=undefined?this.width:this.diameter;var e=this.height!=undefined?this.height:this.diameter;var r=t/2;var i=e/2;this.path=[{x:0,y:-i},{arc:[{x:r,y:-i},{x:r,y:0}]}];if(this.quarters>1){this.path.push({arc:[{x:r,y:i},{x:0,y:i}]})}if(this.quarters>2){this.path.push({arc:[{x:-r,y:i},{x:-r,y:0}]})}if(this.quarters>3){this.path.push({arc:[{x:-r,y:-i},{x:0,y:-i}]})}};return r});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./shape"))}else{var r=t.Zdog;r.Polygon=e(r,r.Shape)}})(this,function t(e,r){var i=r.subclass({sides:3,radius:.5});i.type="Polygon";var n=e.TAU;i.prototype.setPath=function(){this.path=[];for(var t=0;t<this.sides;t++){var e=t/this.sides*n-n/4;var r=Math.cos(e)*this.radius;var i=Math.sin(e)*this.radius;this.path.push({x:r,y:i})}};return i});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./vector"),require("./anchor"),require("./ellipse"))}else{var r=t.Zdog;r.Hemisphere=e(r,r.Vector,r.Anchor,r.Ellipse)}})(this,function t(e,r,i,n){var o=n.subclass({fill:true});o.type="Hemisphere";var p=e.TAU;o.prototype.create=function(){n.prototype.create.apply(this,arguments);this.apex=new i({addTo:this,translate:{z:this.diameter/2}});this.renderCentroid=new r};o.prototype.updateSortValue=function(){this.renderCentroid.set(this.renderOrigin).lerp(this.apex.renderOrigin,3/8);this.sortValue=this.renderCentroid.z};o.prototype.render=function(t,e){this.renderDome(t,e);n.prototype.render.apply(this,arguments)};o.prototype.renderDome=function(t,e){if(!this.visible){return}var r=this.getDomeRenderElement(t,e);var i=Math.atan2(this.renderNormal.y,this.renderNormal.x);var n=this.diameter/2*this.renderNormal.magnitude();var o=this.renderOrigin.x;var s=this.renderOrigin.y;if(e.isCanvas){var a=i+p/4;var h=i-p/4;t.beginPath();t.arc(o,s,n,a,h)}else if(e.isSvg){i=(i-p/4)/p*360;this.domeSvgElement.setAttribute("d","M "+-n+",0 A "+n+","+n+" 0 0 1 "+n+",0");this.domeSvgElement.setAttribute("transform","translate("+o+","+s+" ) rotate("+i+")")}e.stroke(t,r,this.stroke,this.color,this.getLineWidth());e.fill(t,r,this.fill,this.color);e.end(t,r)};var s="http://www.w3.org/2000/svg";o.prototype.getDomeRenderElement=function(t,e){if(!e.isSvg){return}if(!this.domeSvgElement){this.domeSvgElement=document.createElementNS(s,"path");this.domeSvgElement.setAttribute("stroke-linecap","round");this.domeSvgElement.setAttribute("stroke-linejoin","round")}return this.domeSvgElement};return o});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./path-command"),require("./shape"),require("./group"),require("./ellipse"))}else{var r=t.Zdog;r.Cylinder=e(r,r.PathCommand,r.Shape,r.Group,r.Ellipse)}})(this,function t(r,e,i,n,o){function s(){}var a=n.subclass({color:"#333",updateSort:true});a.type="CylinderGroup";a.prototype.create=function(){n.prototype.create.apply(this,arguments);this.pathCommands=[new e("move",[{}]),new e("line",[{}])]};a.prototype.render=function(t,e){this.renderCylinderSurface(t,e);n.prototype.render.apply(this,arguments)};a.prototype.renderCylinderSurface=function(t,e){if(!this.visible){return}var r=this.getRenderElement(t,e);var i=this.frontBase;var n=this.rearBase;var o=i.renderNormal.magnitude();var s=i.diameter*o+i.getLineWidth();this.pathCommands[0].renderPoints[0].set(i.renderOrigin);this.pathCommands[1].renderPoints[0].set(n.renderOrigin);if(e.isCanvas){t.lineCap="butt"}e.renderPath(t,r,this.pathCommands);e.stroke(t,r,true,this.color,s);e.end(t,r);if(e.isCanvas){t.lineCap="round"}};var h="http://www.w3.org/2000/svg";a.prototype.getRenderElement=function(t,e){if(!e.isSvg){return}if(!this.svgElement){this.svgElement=document.createElementNS(h,"path")}return this.svgElement};a.prototype.copyGraph=s;var p=o.subclass();p.prototype.copyGraph=s;var u=i.subclass({diameter:1,length:1,frontFace:undefined,fill:true});u.type="Cylinder";var d=r.TAU;u.prototype.create=function(){i.prototype.create.apply(this,arguments);this.group=new a({addTo:this,color:this.color,visible:this.visible});var t=this.length/2;var e=this.backface||true;this.frontBase=this.group.frontBase=new o({addTo:this.group,diameter:this.diameter,translate:{z:t},rotate:{y:d/2},color:this.color,stroke:this.stroke,fill:this.fill,backface:r.cloneColor(this.frontFace||e),visible:this.visible});this.rearBase=this.group.rearBase=this.frontBase.copy({translate:{z:-t},rotate:{y:0},backface:r.cloneColor(e)})};u.prototype.render=function(){};var l=["stroke","fill","color","visible"];l.forEach(function(e){var r="_"+e;Object.defineProperty(u.prototype,e,{get:function(){return this[r]},set:function(t){this[r]=t;if(this.frontBase){this.frontBase[e]=t;this.rearBase[e]=t;this.group[e]=t}}})});return u});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./vector"),require("./path-command"),require("./anchor"),require("./ellipse"))}else{var r=t.Zdog;r.Cone=e(r,r.Vector,r.PathCommand,r.Anchor,r.Ellipse)}})(this,function t(e,r,i,n,o){var s=o.subclass({length:1,fill:true});s.type="Cone";var v=e.TAU;s.prototype.create=function(){o.prototype.create.apply(this,arguments);this.apex=new n({addTo:this,translate:{z:this.length}});this.renderApex=new r;this.renderCentroid=new r;this.tangentA=new r;this.tangentB=new r;this.surfacePathCommands=[new i("move",[{}]),new i("line",[{}]),new i("line",[{}])]};s.prototype.updateSortValue=function(){this.renderCentroid.set(this.renderOrigin).lerp(this.apex.renderOrigin,1/3);this.sortValue=this.renderCentroid.z};s.prototype.render=function(t,e){this.renderConeSurface(t,e);o.prototype.render.apply(this,arguments)};s.prototype.renderConeSurface=function(t,e){if(!this.visible){return}this.renderApex.set(this.apex.renderOrigin).subtract(this.renderOrigin);var r=this.renderNormal.magnitude();var i=this.renderApex.magnitude2d();var n=this.renderNormal.magnitude2d();var o=Math.acos(n/r);var s=Math.sin(o);var a=this.diameter/2*r;var h=a*s<i;if(!h){return}var p=Math.atan2(this.renderNormal.y,this.renderNormal.x)+v/2;var u=i/s;var d=Math.acos(a/u);var l=this.tangentA;var c=this.tangentB;l.x=Math.cos(d)*a*s;l.y=Math.sin(d)*a;c.set(this.tangentA);c.y*=-1;l.rotateZ(p);c.rotateZ(p);l.add(this.renderOrigin);c.add(this.renderOrigin);this.setSurfaceRenderPoint(0,l);this.setSurfaceRenderPoint(1,this.apex.renderOrigin);this.setSurfaceRenderPoint(2,c);var f=this.getSurfaceRenderElement(t,e);e.renderPath(t,f,this.surfacePathCommands);e.stroke(t,f,this.stroke,this.color,this.getLineWidth());e.fill(t,f,this.fill,this.color);e.end(t,f)};var a="http://www.w3.org/2000/svg";s.prototype.getSurfaceRenderElement=function(t,e){if(!e.isSvg){return}if(!this.surfaceSvgElement){this.surfaceSvgElement=document.createElementNS(a,"path");this.surfaceSvgElement.setAttribute("stroke-linecap","round");this.surfaceSvgElement.setAttribute("stroke-linejoin","round")}return this.surfaceSvgElement};s.prototype.setSurfaceRenderPoint=function(t,e){var r=this.surfacePathCommands[t].renderPoints[0];r.set(e)};return s});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./path-command"),require("./shape"),require("./group"),require("./vector"))}else{var r=t.Zdog;r.Horn=e(r,r.PathCommand,r.Shape,r.Group,r.Vector)}})(this,function t(e,r,c,i,n){function o(){}var s=i.subclass({color:"#333",fill:true,stroke:true,updateSort:true});s.type="HornGroup";s.prototype.create=function(){i.prototype.create.apply(this,arguments);this.renderApex=new n;this.pathCommands=[new r("move",[{}]),new r("line",[{}]),new r("line",[{}]),new r("line",[{}])]};s.prototype.render=function(t,e){this.renderHornSurface(t,e);i.prototype.render.apply(this,arguments)};s.prototype.renderHornSurface=function(t,e){if(!this.visible){return}var r=this.getRenderElement(t,e);var i=this.addTo.renderNormal.magnitude();var n=this.addTo.frontDiameter/2*i;var o=this.addTo.rearDiameter/2*i;this.renderApex.set(this.rearBase.renderOrigin).subtract(this.frontBase.renderOrigin);var s=this.renderApex.magnitude2d();if(s<=Math.abs(o-n)){return}var a=Math.atan2(this.renderApex.y,this.renderApex.x);var h=Math.acos((n-o)/s);var p=this.pathCommands[0].renderPoints[0];var u=this.pathCommands[1].renderPoints[0];var d=this.pathCommands[2].renderPoints[0];var l=this.pathCommands[3].renderPoints[0];p.x=Math.cos(a+h)*n;p.y=Math.sin(a+h)*n;u.x=Math.cos(a+h)*o;u.y=Math.sin(a+h)*o;l.x=Math.cos(a-h)*n;l.y=Math.sin(a-h)*n;d.x=Math.cos(a-h)*o;d.y=Math.sin(a-h)*o;p.add(this.frontBase.renderOrigin);l.add(this.frontBase.renderOrigin);u.add(this.rearBase.renderOrigin);d.add(this.rearBase.renderOrigin);if(e.isCanvas){t.lineCap="butt"}e.renderPath(t,r,this.pathCommands);e.stroke(t,r,this.stroke,this.color,c.prototype.getLineWidth.apply(this));e.fill(t,r,this.fill,this.color);e.end(t,r);if(e.isCanvas){t.lineCap="round"}};var a="http://www.w3.org/2000/svg";s.prototype.getRenderElement=function(t,e){if(!e.isSvg){return}if(!this.svgElement){this.svgElement=document.createElementNS(a,"path");this.svgElement.setAttribute("stroke-linecap","round");this.svgElement.setAttribute("stroke-linejoin","round")}return this.svgElement};s.prototype.copyGraph=o;var h=c.subclass();h.type="HornCap";h.prototype.copyGraph=o;var p=c.subclass({frontDiameter:1,rearDiameter:1,length:1,frontFace:undefined,fill:true});p.type="Horn";var u=e.TAU;p.prototype.create=function(){c.prototype.create.apply(this,arguments);this.group=new s({addTo:this,color:this.color,fill:this.fill,stroke:this.stroke,visible:this.visible});var t=this.length/2;var e=this.backface||true;this.frontBase=this.group.frontBase=new h({addTo:this.group,translate:{z:t-this.frontDiameter/2},rotate:{y:u/2},color:this.color,stroke:this.frontDiameter+this.stroke,fill:this.fill,backface:this.frontFace||e,visible:this.visible});this.rearBase=this.group.rearBase=new h({addTo:this.group,translate:{z:-t+this.rearDiameter/2},rotate:{y:0},color:this.color,stroke:this.rearDiameter+this.stroke,fill:this.fill,backface:e,visible:this.visible})};p.prototype.updateFrontCapDiameter=function(t){this.frontBase.stroke=t+this.stroke;var e=this.length/2;this.frontBase.translate.z=e-t/2};p.prototype.updateRearCapDiameter=function(t){this.rearBase.stroke=t+this.stroke;var e=this.length/2;this.rearBase.translate.z=-e+t/2};p.prototype.render=function(){};var d=["stroke","fill","color","visible","frontDiameter","rearDiameter"];d.forEach(function(e){var r="_"+e;Object.defineProperty(p.prototype,e,{get:function(){return this[r]},set:function(t){this[r]=t;if(this.frontBase){if(e==="frontDiameter"){this.updateFrontCapDiameter(t)}if(e==="rearDiameter"){this.updateRearCapDiameter(t)}this.frontBase[e]=t;this.rearBase[e]=t;this.group[e]=t}}})});return p});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./path-command"),require("./shape"),require("./group"),require("./vector"))}else{var r=t.Zdog;r.Funnel=e(r,r.PathCommand,r.Shape,r.Ellipse,r.Group,r.Vector)}})(this,function t(e,r,z,i,n,o){function s(){}var a=n.subclass({color:"#333",fill:true,stroke:true,updateSort:true});a.type="FunnelGroup";a.prototype.create=function(){n.prototype.create.apply(this,arguments);this.renderApex=new o;this.tangentFrontA=new o;this.tangentFrontB=new o;this.tangentRearA=new o;this.tangentRearB=new o;this.pathCommands=[new r("move",[{}]),new r("line",[{}]),new r("line",[{}]),new r("line",[{}])]};a.prototype.render=function(t,e){this.renderFunnelSurface(t,e);n.prototype.render.apply(this,arguments)};a.prototype.renderFunnelSurface=function(t,e){if(!this.visible){return}var r=this.getRenderElement(t,e);var i=this.frontBase;var n=i.diameter;var o=this.rearBase;var s=o.diameter;var a=i.renderNormal.magnitude();var h=n/2*a;var p=s/2*a;this.renderApex.set(o.renderOrigin).subtract(i.renderOrigin);var a=i.renderNormal.magnitude();var u=this.renderApex.magnitude2d();var d=i.renderNormal.magnitude2d();var l=Math.acos(d/a);var c=h>p?h:p;var f;if(h==0||p==0){f=1}else{f=Math.abs(h-p)/c}var v=Math.sin(l)*Math.sqrt(f);u=u+h/4+p/4;var m=h*v<u&&p*v<u;if(!m){return}var y=Math.atan2(i.renderNormal.y,i.renderNormal.x)+G/2;var g=(u+h)/v;var x=(u+p)/v;var b=Math.acos(h/g);var w=Math.acos(p/-x);var S=this.tangentFrontA;var C=this.tangentFrontB;var E=this.tangentRearA;var P=this.tangentRearB;S.x=Math.cos(b)*h*v;S.y=Math.sin(b)*h;E.x=Math.cos(w)*p*v;E.y=Math.sin(w)*p;C.set(this.tangentFrontA);C.y*=-1;P.set(this.tangentRearA);P.y*=-1;S.rotateZ(y);C.rotateZ(y);S.add(i.renderOrigin);C.add(i.renderOrigin);E.rotateZ(y+G/2);P.rotateZ(y+G/2);E.add(o.renderOrigin);P.add(o.renderOrigin);this.pathCommands[0].renderPoints[0].set(S);this.pathCommands[1].renderPoints[0].set(P);this.pathCommands[2].renderPoints[0].set(E);this.pathCommands[3].renderPoints[0].set(C);if(e.isCanvas){t.lineCap="butt"}e.stroke(t,r,this.stroke,this.color,z.prototype.getLineWidth.apply(this));e.renderPath(t,r,this.pathCommands);e.fill(t,r,this.fill,this.color);e.end(t,r);if(e.isCanvas){t.lineCap="round"}};var h="http://www.w3.org/2000/svg";a.prototype.getRenderElement=function(t,e){if(!e.isSvg){return}if(!this.svgElement){this.svgElement=document.createElementNS(h,"path");this.svgElement.setAttribute("stroke-linecap","round");this.svgElement.setAttribute("stroke-linejoin","round")}return this.svgElement};a.prototype.copyGraph=s;var p=i.subclass();p.type="FunnelCap";p.prototype.copyGraph=s;var u=z.subclass({frontDiameter:1,rearDiameter:1,length:1,frontFace:undefined,fill:true});u.type="Funnel";var G=e.TAU;u.prototype.create=function(){z.prototype.create.apply(this,arguments);this.group=new a({addTo:this,color:this.color,fill:this.fill,stroke:this.stroke,visible:this.visible});var t=this.length/2;var e=this.backface||true;this.frontBase=this.group.frontBase=new p({addTo:this.group,translate:{z:t-this.frontDiameter/2},rotate:{y:G/2},color:this.color,diameter:this.frontDiameter,fill:this.fill,stroke:this.stroke,backface:this.frontFace||e,visible:this.visible});this.rearBase=this.group.rearBase=new p({addTo:this.group,translate:{z:-t+this.rearDiameter/2},rotate:{y:0},color:this.color,diameter:this.rearDiameter,fill:this.fill,stroke:this.stroke,backface:e,visible:this.visible})};u.prototype.updateFrontCapDiameter=function(t){this.frontBase.diameter=t;var e=this.length/2;this.frontBase.translate.z=e-t/2};u.prototype.updateRearCapDiameter=function(t){this.rearBase.diameter=t;var e=this.length/2;this.rearBase.translate.z=-e+t/2};u.prototype.render=function(){};var d=["stroke","fill","color","visible","frontDiameter","rearDiameter"];d.forEach(function(e){var r="_"+e;Object.defineProperty(u.prototype,e,{get:function(){return this[r]},set:function(t){this[r]=t;if(this.frontBase){if(e==="frontDiameter"){this.updateFrontCapDiameter(t)}if(e==="rearDiameter"){this.updateRearCapDiameter(t)}this.frontBase[e]=t;this.rearBase[e]=t;this.group[e]=t}}})});return u});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./anchor"),require("./shape"),require("./rect"))}else{var r=t.Zdog;r.Box=e(r,r.Anchor,r.Shape,r.Rect)}})(this,function t(o,e,r,i){var s=i.subclass();s.prototype.copyGraph=function(){};var n=o.TAU;var a=["frontFace","rearFace","leftFace","rightFace","topFace","bottomFace"];var h=o.extend({},r.defaults);delete h.path;a.forEach(function(t){h[t]=true});o.extend(h,{width:1,height:1,depth:1,fill:true},r.defaults);delete h.path;var p=e.subclass(h);p.ignoreKeysJSON=["children"];p.type="Box";p.prototype.create=function(t){e.prototype.create.call(this,t);this.updatePath();this.fill=this.fill};p.prototype.updatePath=function(){a.forEach(function(t){this[t]=this[t]},this)};a.forEach(function(e){var r="_"+e;Object.defineProperty(p.prototype,e,{get:function(){return this[r]},set:function(t){this[r]=t;this.setFace(e,t)}})});p.prototype.setFace=function(t,e){var r=t+"Rect";var i=this[r];if(!e){this.removeChild(i);return}var n=this.getFaceOptions(t);if(o.isColor(e)){n.color=e}else{n.color=o.cloneColor(this.color)}if(i){i.setOptions(n)}else{i=this[r]=new s(n)}i.updatePath();this.addChild(i)};p.prototype.getFaceOptions=function(t){return{frontFace:{width:this.width,height:this.height,translate:{z:this.depth/2}},rearFace:{width:this.width,height:this.height,translate:{z:-this.depth/2},rotate:{y:n/2}},leftFace:{width:this.depth,height:this.height,translate:{x:-this.width/2},rotate:{y:-n/4}},rightFace:{width:this.depth,height:this.height,translate:{x:this.width/2},rotate:{y:n/4}},topFace:{width:this.width,height:this.depth,translate:{y:-this.height/2},rotate:{x:-n/4}},bottomFace:{width:this.width,height:this.depth,translate:{y:this.height/2},rotate:{x:n/4}}}[t]};var u=["color","stroke","fill","backface","front","visible"];u.forEach(function(o){var t="_"+o;Object.defineProperty(p.prototype,o,{get:function(){return this[t]},set:function(n){this[t]=n;a.forEach(function(t){var e=this[t+"Rect"];var r=typeof this[t]=="string";var i=o=="color"&&r;if(e&&!i){e[o]=n}},this)}})});return p});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./vector"))}else{var r=t.Zdog;r.Texture=e(r.Vector)}})(this,function t(i){function n(t,e,r,i,n,o){let s=[i-o,n-r,r*o-n*i,o-e,t-n,n*e-t*o,e-i,r-t,t*i-r*e];let a=s[2]+s[5]+s[8];return s.map(function(t){return t/a})}function o(t,r){if(!Array.isArray(r)||!r.length){r=[0,0,t[0],t[1]]}if(typeof r[0]=="number"){if(r.length<4){let e=r;r=[0,0,t[0],t[1]];for(let t=0;t<e.length;t++){r[t]=e[t]}}return[new i({x:r[0],y:r[1],z:1}),new i({x:r[0]+r[2],y:r[1],z:1}),new i({x:r[0],y:r[1]+r[3],z:1})]}else{return[new i(r[0]),new i(r[1]),new i(r[2])]}}var s=0;const a=["img","linearGrad","radialGrad","colorStops","src","dst"];function e(t){this.id=s++;this.isTexture=true;t=t||{};for(var e in t){if(a.indexOf(e)!=-1){this[e]=t[e]}}var r;if(t.img){r=[t.img.width,t.img.height]}else if(t.linearGrad){r=[Math.abs(t.linearGrad[2]-t.linearGrad[0]),Math.abs(t.linearGrad[3]-t.linearGrad[1])]}else if(t.radialGrad){r=[Math.abs(t.radialGrad[3]-t.radialGrad[0]),Math.abs(t.radialGrad[4]-t.radialGrad[1])]}else{throw"One of [img, linearGrad, radialGrad] is required"}if(r[0]==0)r[0]=r[1];if(r[1]==0)r[1]=r[0];this.src=o(r,t.src);this.dst=o(r,t.dst);this.srcInverse=n(this.src[0].x,this.src[0].y,this.src[1].x,this.src[1].y,this.src[2].x,this.src[2].y);this.p1=new i;this.p2=new i;this.p3=new i;this.matrix=[0,0,0,0,0,0]}e.prototype.getMatrix=function(){let t=this.matrix;let e=this.srcInverse;t[0]=this.p1.x*e[0]+this.p2.x*e[3]+this.p3.x*e[6];t[1]=this.p1.y*e[0]+this.p2.y*e[3]+this.p3.y*e[6];t[2]=this.p1.x*e[1]+this.p2.x*e[4]+this.p3.x*e[7];t[3]=this.p1.y*e[1]+this.p2.y*e[4]+this.p3.y*e[7];t[4]=this.p1.x*e[2]+this.p2.x*e[5]+this.p3.x*e[8];t[5]=this.p1.y*e[2]+this.p2.y*e[5]+this.p3.y*e[8];return t};e.prototype.getCanvasFill=function(t){if(!this.pattern){if(this.img){this.pattern=t.createPattern(this.img,"repeat")}else{this.pattern=this.linearGrad?t.createLinearGradient.apply(t,this.linearGrad):t.createRadialGradient.apply(t,this.radialGrad);if(this.colorStops){for(var e=0;e<this.colorStops.length;e+=2){this.pattern.addColorStop(this.colorStops[e],this.colorStops[e+1])}}}}t.transform.apply(t,this.getMatrix());return this.pattern};const h="http://www.w3.org/2000/svg";e.prototype.getSvgFill=function(t){if(!this.svgPattern){if(this.img){this.svgPattern=document.createElementNS(h,"pattern");this.svgPattern.setAttribute("width",this.img.width);this.svgPattern.setAttribute("height",this.img.height);this.svgPattern.setAttribute("patternUnits","userSpaceOnUse");this.attrTransform="patternTransform";let t=document.createElementNS(h,"image");t.setAttribute("href",this.img.src);this.svgPattern.appendChild(t)}else{var e,r,i;if(this.linearGrad){e="linearGradient";r=this.linearGrad;i=["x1","y1","x2","y2"]}else{e="radialGradient";r=this.radialGrad;i=["fx","fy","fr","cx","cy","r"]}this.svgPattern=document.createElementNS(h,e);for(var n=0;n<i.length;n++){this.svgPattern.setAttribute(i[n],r[n])}if(this.colorStops){for(var n=0;n<this.colorStops.length;n+=2){let t=document.createElementNS(h,"stop");t.setAttribute("offset",this.colorStops[n]);t.setAttribute("style","stop-color:"+this.colorStops[n+1]);this.svgPattern.appendChild(t)}}this.svgPattern.setAttribute("gradientUnits","userSpaceOnUse");this.attrTransform="gradientTransform"}this.svgPattern.setAttribute("id","texture_"+this.id);this._svgUrl="url(#texture_"+this.id+")";this.defs=document.createElementNS(h,"defs");this.defs.appendChild(this.svgPattern)}this.svgPattern.setAttribute(this.attrTransform,"matrix("+this.getMatrix().join(" ")+")");t.appendChild(this.defs);return this._svgUrl};e.prototype.reset=function(){this.p1.set(this.dst[0]);this.p2.set(this.dst[1]);this.p3.set(this.dst[2])};e.prototype.transform=function(t,e,r){this.p1.transform(t,e,r);this.p2.transform(t,e,r);this.p3.transform(t,e,r)};e.prototype.clone=function(){return new e(this)};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./canvas-renderer"),require("./svg-renderer"),require("./vector"),require("./anchor"),require("./dragger"),require("./illustration"),require("./path-command"),require("./shape"),require("./group"),require("./rect"),require("./rounded-rect"),require("./ellipse"),require("./polygon"),require("./hemisphere"),require("./cylinder"),require("./cone"),require("./horn"),require("./funnel"),require("./box"),require("./texture"))}else if(typeof define=="function"&&define.amd){define("zdog",[],t.Zdog)}})(this,function t(e,r,i,n,o,s,a,h,p,u,d,l,c,f,v,m,y,g,x,b,w){e.CanvasRenderer=r;e.SvgRenderer=i;e.Vector=n;e.Anchor=o;e.Dragger=s;e.Illustration=a;e.PathCommand=h;e.Shape=p;e.Group=u;e.Rect=d;e.RoundedRect=l;e.Ellipse=c;e.Polygon=f;e.Hemisphere=v;e.Cylinder=m;e.Cone=y;e.Horn=g;e.Funnel=x;e.Box=b;e.Texture=w;return e});
+278
js/funnel.js
··· 1 + /** 2 + * Funnel 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('./vector') ); 12 + } else { 13 + // browser global 14 + var Zdog = root.Zdog; 15 + Zdog.Funnel = factory( Zdog, Zdog.PathCommand, Zdog.Shape, Zdog.Ellipse, 16 + Zdog.Group, Zdog.Vector ); 17 + } 18 + }( this, function factory( utils, PathCommand, Shape, Ellipse, Group, Vector ) { 19 + 20 + function noop() {} 21 + 22 + // ----- FunnelGroup ----- // 23 + 24 + var FunnelGroup = Group.subclass({ 25 + color: '#333', 26 + fill: true, 27 + stroke: true, 28 + updateSort: true, 29 + }); 30 + 31 + FunnelGroup.type = 'FunnelGroup'; 32 + 33 + FunnelGroup.prototype.create = function() { 34 + Group.prototype.create.apply( this, arguments ); 35 + 36 + // vectors used for calculation 37 + this.renderApex = new Vector(); 38 + this.tangentFrontA = new Vector(); 39 + this.tangentFrontB = new Vector(); 40 + this.tangentRearA = new Vector(); 41 + this.tangentRearB = new Vector(); 42 + 43 + this.pathCommands = [ 44 + new PathCommand( 'move', [ {} ] ), 45 + new PathCommand( 'line', [ {} ] ), 46 + new PathCommand( 'line', [ {} ] ), 47 + new PathCommand( 'line', [ {} ] ), 48 + ]; 49 + }; 50 + 51 + FunnelGroup.prototype.render = function( ctx, renderer ) { 52 + this.renderFunnelSurface( ctx, renderer ); 53 + Group.prototype.render.apply( this, arguments ); 54 + }; 55 + 56 + FunnelGroup.prototype.renderFunnelSurface = function( ctx, renderer ) { 57 + if ( !this.visible ) { 58 + return; 59 + } 60 + // render funnel surface 61 + var elem = this.getRenderElement( ctx, renderer ); 62 + var frontBase = this.frontBase; 63 + var frontDiameter = frontBase.diameter; 64 + var rearBase = this.rearBase; 65 + var rearDiameter = rearBase.diameter; 66 + var scale = frontBase.renderNormal.magnitude(); 67 + var frontRadius = frontDiameter/2 * scale; 68 + var rearRadius = rearDiameter/2 * scale; 69 + 70 + this.renderApex.set( rearBase.renderOrigin ) 71 + .subtract( frontBase.renderOrigin ); 72 + 73 + // calculate tangents. 74 + var scale = frontBase.renderNormal.magnitude(); 75 + var apexDistance = this.renderApex.magnitude2d(); 76 + var normalDistance = frontBase.renderNormal.magnitude2d(); 77 + // eccentricity 78 + var eccenAngle = Math.acos( normalDistance / scale ); 79 + var biggerRadius = (frontRadius > rearRadius) ? frontRadius : rearRadius; 80 + var eccenPercent; 81 + if (frontRadius == 0 || rearRadius == 0) { 82 + eccenPercent = 1.0; 83 + } else { 84 + eccenPercent = (Math.abs(frontRadius - rearRadius) / biggerRadius); 85 + } 86 + var eccen = Math.sin( eccenAngle ) * Math.sqrt(eccenPercent); 87 + // does apex extend beyond eclipse of face 88 + apexDistance = apexDistance + frontRadius/4 + rearRadius/4; 89 + var isApexVisible = frontRadius * eccen < apexDistance && 90 + rearRadius * eccen < apexDistance; 91 + if ( !isApexVisible ) { 92 + return; 93 + } 94 + // update tangents 95 + // TODO: try something more like horn_old.js updateSortValue() 96 + var apexAngle = Math.atan2( frontBase.renderNormal.y, frontBase.renderNormal.x ) + 97 + TAU/2; 98 + var projectFrontLength = (apexDistance + frontRadius) / eccen; 99 + var projectRearLength = (apexDistance + rearRadius) / eccen; 100 + var projectFrontAngle = Math.acos( frontRadius / projectFrontLength ); 101 + var projectRearAngle = Math.acos( rearRadius / -projectRearLength ); 102 + // set tangent points 103 + var tangentFrontA = this.tangentFrontA; 104 + var tangentFrontB = this.tangentFrontB; 105 + var tangentRearA = this.tangentRearA; 106 + var tangentRearB = this.tangentRearB; 107 + 108 + tangentFrontA.x = Math.cos( projectFrontAngle ) * frontRadius * eccen; 109 + tangentFrontA.y = Math.sin( projectFrontAngle ) * frontRadius; 110 + tangentRearA.x = Math.cos( projectRearAngle ) * rearRadius * eccen; 111 + tangentRearA.y = Math.sin( projectRearAngle ) * rearRadius; 112 + 113 + tangentFrontB.set( this.tangentFrontA ); 114 + tangentFrontB.y *= -1; 115 + tangentRearB.set( this.tangentRearA ); 116 + tangentRearB.y *= -1; 117 + 118 + tangentFrontA.rotateZ( apexAngle); 119 + tangentFrontB.rotateZ( apexAngle); 120 + tangentFrontA.add( frontBase.renderOrigin ); 121 + tangentFrontB.add( frontBase.renderOrigin ); 122 + tangentRearA.rotateZ( apexAngle + TAU/2); 123 + tangentRearB.rotateZ( apexAngle + TAU/2); 124 + tangentRearA.add( rearBase.renderOrigin ); 125 + tangentRearB.add( rearBase.renderOrigin ); 126 + 127 + 128 + // set path command render points 129 + this.pathCommands[0].renderPoints[0].set( tangentFrontA ); 130 + this.pathCommands[1].renderPoints[0].set( tangentRearB ); 131 + this.pathCommands[2].renderPoints[0].set( tangentRearA ); 132 + this.pathCommands[3].renderPoints[0].set( tangentFrontB ); 133 + 134 + if ( renderer.isCanvas ) { 135 + ctx.lineCap = 'butt'; // nice 136 + } 137 + renderer.stroke(ctx, elem, this.stroke, this.color, Shape.prototype.getLineWidth.apply(this)); 138 + renderer.renderPath( ctx, elem, this.pathCommands ); 139 + //renderer.stroke( ctx, elem, true, '#333', 0.1 ); // remove once testing is done. 140 + renderer.fill( ctx, elem, this.fill, this.color ); 141 + renderer.end( ctx, elem ); 142 + 143 + if ( renderer.isCanvas ) { 144 + ctx.lineCap = 'round'; // reset 145 + } 146 + }; 147 + 148 + var svgURI = 'http://www.w3.org/2000/svg'; 149 + 150 + FunnelGroup.prototype.getRenderElement = function( ctx, renderer ) { 151 + if ( !renderer.isSvg ) { 152 + return; 153 + } 154 + if ( !this.svgElement ) { 155 + // create svgElement 156 + this.svgElement = document.createElementNS( svgURI, 'path'); 157 + this.svgElement.setAttribute( 'stroke-linecap', 'round' ); 158 + this.svgElement.setAttribute( 'stroke-linejoin', 'round' ); 159 + } 160 + return this.svgElement; 161 + }; 162 + 163 + // prevent double-creation in parent.copyGraph() 164 + // only create in Funnel.create() 165 + FunnelGroup.prototype.copyGraph = noop; 166 + 167 + // ----- FunnelCap ----- // 168 + 169 + var FunnelCap = Ellipse.subclass(); 170 + 171 + FunnelCap.type = 'FunnelCap'; 172 + 173 + FunnelCap.prototype.copyGraph = noop; 174 + 175 + // ----- Funnel ----- // 176 + 177 + var Funnel = Shape.subclass({ 178 + frontDiameter: 1, 179 + rearDiameter: 1, 180 + length: 1, 181 + frontFace: undefined, 182 + fill: true, 183 + }); 184 + 185 + Funnel.type = 'Funnel'; 186 + 187 + var TAU = utils.TAU; 188 + 189 + Funnel.prototype.create = function(/* options */) { 190 + // call super 191 + Shape.prototype.create.apply( this, arguments ); 192 + // composite shape, create child shapes 193 + // FunnelGroup to render funnel surface then bases 194 + this.group = new FunnelGroup({ 195 + addTo: this, 196 + color: this.color, 197 + fill: this.fill, 198 + stroke: this.stroke, 199 + visible: this.visible, 200 + }); 201 + var baseZ = this.length/2; 202 + var baseColor = this.backface || true; 203 + // front outside base 204 + this.frontBase = this.group.frontBase = new FunnelCap({ 205 + addTo: this.group, 206 + translate: { z: (baseZ - this.frontDiameter/2) }, 207 + rotate: { y: TAU/2 }, 208 + color: this.color, 209 + diameter: this.frontDiameter, 210 + fill: this.fill, 211 + stroke: this.stroke, 212 + backface: this.frontFace || baseColor, 213 + visible: this.visible, 214 + }); 215 + // back outside base 216 + this.rearBase = this.group.rearBase = new FunnelCap({ 217 + addTo: this.group, 218 + translate: { z: (-baseZ + this.rearDiameter/2) }, 219 + rotate: { y: 0 }, 220 + color: this.color, 221 + diameter: this.rearDiameter, 222 + fill: this.fill, 223 + stroke: this.stroke, 224 + backface: baseColor, 225 + visible: this.visible, 226 + }); 227 + 228 + }; 229 + 230 + Funnel.prototype.updateFrontCapDiameter = function(size) { 231 + this.frontBase.diameter = size; 232 + var baseZ = this.length/2; 233 + this.frontBase.translate.z = (baseZ - size/2); 234 + } 235 + 236 + Funnel.prototype.updateRearCapDiameter = function(size) { 237 + this.rearBase.diameter = size; 238 + var baseZ = this.length/2; 239 + this.rearBase.translate.z = (-baseZ + size/2); 240 + } 241 + 242 + // Funnel shape does not render anything 243 + Funnel.prototype.render = function() {}; 244 + 245 + // ----- set child properties ----- // 246 + 247 + var childProperties = [ 'stroke', 'fill', 'color', 'visible', 248 + 'frontDiameter', 'rearDiameter' ]; 249 + childProperties.forEach( function( property ) { 250 + // use proxy property for custom getter & setter 251 + var _prop = '_' + property; 252 + Object.defineProperty( Funnel.prototype, property, { 253 + get: function() { 254 + return this[ _prop ]; 255 + }, 256 + set: function( value ) { 257 + this[ _prop ] = value; 258 + // set property on children 259 + if ( this.frontBase ) { 260 + if (property === 'frontDiameter') { 261 + this.updateFrontCapDiameter(value); 262 + } 263 + if (property === 'rearDiameter') { 264 + this.updateRearCapDiameter(value); 265 + } 266 + this.frontBase[ property ] = value; 267 + this.rearBase[ property ] = value; 268 + this.group[ property ] = value; 269 + } 270 + }, 271 + }); 272 + }); 273 + 274 + // TODO child property setter for backface, frontBaseColor, & rearBaseColor 275 + 276 + return Funnel; 277 + 278 + }));
+44 -80
js/horn.js
··· 23 23 24 24 var HornGroup = Group.subclass({ 25 25 color: '#333', 26 + fill: true, 27 + stroke: true, 26 28 updateSort: true, 27 29 }); 28 30 ··· 30 32 31 33 HornGroup.prototype.create = function() { 32 34 Group.prototype.create.apply( this, arguments ); 33 - 35 + 34 36 // vectors used for calculation 35 37 this.renderApex = new Vector(); 36 - this.tangentFrontA = new Vector(); 37 - this.tangentFrontB = new Vector(); 38 - this.tangentRearA = new Vector(); 39 - this.tangentRearB = new Vector(); 40 - 38 + 41 39 this.pathCommands = [ 42 40 new PathCommand( 'move', [ {} ] ), 43 41 new PathCommand( 'line', [ {} ] ), 44 - new PathCommand( 'line', [ {} ] ), 45 - new PathCommand( 'line', [ {} ] ), 42 + new PathCommand( 'line', [ {} ] ), 43 + new PathCommand( 'line', [ {} ] ), 46 44 ]; 47 45 }; 48 46 ··· 57 55 } 58 56 // render horn surface 59 57 var elem = this.getRenderElement( ctx, renderer ); 60 - var frontBase = this.frontBase; 61 - var frontDiameter = frontBase.stroke; 62 - var rearBase = this.rearBase; 63 - var rearDiameter = rearBase.stroke; 64 - var scale = frontBase.renderNormal.magnitude(); 65 - var frontRadius = frontDiameter/2 * scale; 66 - var rearRadius = rearDiameter/2 * scale; 67 - 68 - this.renderApex.set( rearBase.renderOrigin ) 69 - .subtract( frontBase.renderOrigin ); 70 - 71 - // calculate tangents. 72 - var scale = frontBase.renderNormal.magnitude(); 58 + var scale = this.addTo.renderNormal.magnitude(); 59 + var frontRadius = this.addTo.frontDiameter/2 * scale; 60 + var rearRadius = this.addTo.rearDiameter/2 * scale; 61 + this.renderApex.set( this.rearBase.renderOrigin ) 62 + .subtract( this.frontBase.renderOrigin ); 63 + 73 64 var apexDistance = this.renderApex.magnitude2d(); 74 - var normalDistance = frontBase.renderNormal.magnitude2d(); 75 - // eccentricity 76 - var eccenAngle = Math.acos( normalDistance / scale ); 77 - var biggerRadius = (frontRadius > rearRadius) ? frontRadius : rearRadius; 78 - var eccenPercent; 79 - if (frontRadius == 0 || rearRadius == 0) { 80 - eccenPercent = 1.0; 81 - } else { 82 - eccenPercent = (Math.abs(frontRadius - rearRadius) / biggerRadius); 83 - } 84 - var eccen = Math.sin( eccenAngle ) * Math.sqrt(eccenPercent); 85 - // does apex extend beyond eclipse of face 86 - apexDistance = apexDistance + frontRadius/4 + rearRadius/4; 87 - var isApexVisible = frontRadius * eccen < apexDistance && 88 - rearRadius * eccen < apexDistance; 89 - if ( !isApexVisible ) { 65 + if ( apexDistance <= Math.abs( rearRadius - frontRadius ) ) { 90 66 return; 91 67 } 92 - // update tangents 93 - // TODO: try something more like horn_old.js updateSortValue() 94 - var apexAngle = Math.atan2( frontBase.renderNormal.y, frontBase.renderNormal.x ) + 95 - TAU/2; 96 - var projectFrontLength = (apexDistance + frontRadius) / eccen; 97 - var projectRearLength = (apexDistance + rearRadius) / eccen; 98 - var projectFrontAngle = Math.acos( frontRadius / projectFrontLength ); 99 - var projectRearAngle = Math.acos( rearRadius / -projectRearLength ); 100 - // set tangent points 101 - var tangentFrontA = this.tangentFrontA; 102 - var tangentFrontB = this.tangentFrontB; 103 - var tangentRearA = this.tangentRearA; 104 - var tangentRearB = this.tangentRearB; 105 68 106 - tangentFrontA.x = Math.cos( projectFrontAngle ) * frontRadius * eccen; 107 - tangentFrontA.y = Math.sin( projectFrontAngle ) * frontRadius; 108 - tangentRearA.x = Math.cos( projectRearAngle ) * rearRadius * eccen; 109 - tangentRearA.y = Math.sin( projectRearAngle ) * rearRadius; 69 + var angle = Math.atan2( this.renderApex.y, this.renderApex.x ); 70 + var angle2 = Math.acos((frontRadius - rearRadius) / apexDistance); 110 71 111 - tangentFrontB.set( this.tangentFrontA ); 112 - tangentFrontB.y *= -1; 113 - tangentRearB.set( this.tangentRearA ); 114 - tangentRearB.y *= -1; 72 + var frontTangentA = this.pathCommands[0].renderPoints[0]; 73 + var rearTangentA = this.pathCommands[1].renderPoints[0]; 74 + var rearTangentB = this.pathCommands[2].renderPoints[0]; 75 + var frontTangentB = this.pathCommands[3].renderPoints[0]; 115 76 116 - tangentFrontA.rotateZ( apexAngle); 117 - tangentFrontB.rotateZ( apexAngle); 118 - tangentFrontA.add( frontBase.renderOrigin ); 119 - tangentFrontB.add( frontBase.renderOrigin ); 120 - tangentRearA.rotateZ( apexAngle + TAU/2); 121 - tangentRearB.rotateZ( apexAngle + TAU/2); 122 - tangentRearA.add( rearBase.renderOrigin ); 123 - tangentRearB.add( rearBase.renderOrigin ); 124 - 125 - 126 - // set path command render points 127 - this.pathCommands[0].renderPoints[0].set( tangentFrontA ); 128 - this.pathCommands[1].renderPoints[0].set( tangentRearB ); 129 - this.pathCommands[2].renderPoints[0].set( tangentRearA ); 130 - this.pathCommands[3].renderPoints[0].set( tangentFrontB ); 77 + frontTangentA.x = Math.cos( angle + angle2 ) * frontRadius; 78 + frontTangentA.y = Math.sin( angle + angle2 ) * frontRadius; 79 + rearTangentA.x = Math.cos( angle + angle2 ) * rearRadius; 80 + rearTangentA.y = Math.sin( angle + angle2 ) * rearRadius; 81 + 82 + frontTangentB.x = Math.cos( angle - angle2 ) * frontRadius; 83 + frontTangentB.y = Math.sin( angle - angle2 ) * frontRadius; 84 + rearTangentB.x = Math.cos( angle - angle2 ) * rearRadius; 85 + rearTangentB.y = Math.sin( angle - angle2 ) * rearRadius; 86 + 87 + frontTangentA.add( this.frontBase.renderOrigin ); 88 + frontTangentB.add( this.frontBase.renderOrigin ); 89 + rearTangentA.add( this.rearBase.renderOrigin ); 90 + rearTangentB.add( this.rearBase.renderOrigin ); 131 91 132 92 if ( renderer.isCanvas ) { 133 93 ctx.lineCap = 'butt'; // nice 134 94 } 135 95 renderer.renderPath( ctx, elem, this.pathCommands ); 136 - //renderer.stroke( ctx, elem, true, '#333', 0.1 ); // remove once testing is done. 137 - renderer.fill( ctx, elem, true, this.color ); 96 + renderer.stroke( ctx, elem, this.stroke, this.color, Shape.prototype.getLineWidth.apply(this) ); 97 + renderer.fill( ctx, elem, this.fill, this.color ); 138 98 renderer.end( ctx, elem ); 139 99 140 100 if ( renderer.isCanvas ) { ··· 151 111 if ( !this.svgElement ) { 152 112 // create svgElement 153 113 this.svgElement = document.createElementNS( svgURI, 'path'); 114 + this.svgElement.setAttribute( 'stroke-linecap', 'round' ); 115 + this.svgElement.setAttribute( 'stroke-linejoin', 'round' ); 154 116 } 155 117 return this.svgElement; 156 118 }; ··· 189 151 this.group = new HornGroup({ 190 152 addTo: this, 191 153 color: this.color, 154 + fill: this.fill, 155 + stroke: this.stroke, 192 156 visible: this.visible, 193 157 }); 194 158 var baseZ = this.length/2; ··· 199 163 translate: { z: (baseZ - this.frontDiameter/2) }, 200 164 rotate: { y: TAU/2 }, 201 165 color: this.color, 202 - stroke: this.frontDiameter, 166 + stroke: this.frontDiameter + this.stroke, 203 167 fill: this.fill, 204 168 backface: this.frontFace || baseColor, 205 169 visible: this.visible, ··· 210 174 translate: { z: (-baseZ + this.rearDiameter/2) }, 211 175 rotate: { y: 0 }, 212 176 color: this.color, 213 - stroke: this.rearDiameter, 177 + stroke: this.rearDiameter + this.stroke, 214 178 fill: this.fill, 215 179 backface: baseColor, 216 180 visible: this.visible, 217 181 }); 218 - 182 + 219 183 }; 220 184 221 185 Horn.prototype.updateFrontCapDiameter = function(size) { 222 - this.frontBase.stroke = size; 186 + this.frontBase.stroke = size + this.stroke; 223 187 var baseZ = this.length/2; 224 188 this.frontBase.translate.z = (baseZ - size/2); 225 189 } 226 190 227 191 Horn.prototype.updateRearCapDiameter = function(size) { 228 - this.rearBase.stroke = size; 192 + this.rearBase.stroke = size + this.stroke; 229 193 var baseZ = this.length/2; 230 194 this.rearBase.translate.z = (-baseZ + size/2); 231 195 }
+5 -3
js/index.js
··· 24 24 require('./hemisphere'), 25 25 require('./cylinder'), 26 26 require('./cone'), 27 - require('./horn'), 27 + require('./horn'), 28 + require('./funnel'), 28 29 require('./box'), 29 30 require('./texture') 30 31 ); ··· 35 36 /* eslint-disable max-params */ 36 37 } )( this, function factory( Zdog, CanvasRenderer, SvgRenderer, Vector, Anchor, 37 38 Dragger, Illustration, PathCommand, Shape, Group, Rect, RoundedRect, 38 - Ellipse, Polygon, Hemisphere, Cylinder, Cone, Horn, Box, Texture ) { 39 + Ellipse, Polygon, Hemisphere, Cylinder, Cone, Horn, Funnel, Box, Texture ) { 39 40 /* eslint-enable max-params */ 40 41 41 42 Zdog.CanvasRenderer = CanvasRenderer; ··· 54 55 Zdog.Hemisphere = Hemisphere; 55 56 Zdog.Cylinder = Cylinder; 56 57 Zdog.Cone = Cone; 57 - Zdog.Horn = Horn; 58 + Zdog.Horn = Horn; 59 + Zdog.Funnel = Funnel; 58 60 Zdog.Box = Box; 59 61 Zdog.Texture = Texture; 60 62