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

Configure Feed

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

๐Ÿถ build v1.0.0!

+2121 -16
+2 -4
.github/contributing.md
··· 2 2 3 3 **Add ๐Ÿ‘ reaction** to issues for features you would like to see added to Zdog. Do not add +1 comments โ€” [they will be deleted](https://metafizzy.co/blog/use-github-reactions-delete-plus-1-comments/). 4 4 5 - ## Submitting issues 6 - 7 - ### Reduced test case required 5 + ## Reduced test cases required 8 6 9 - All bug reports and problem issues require a [**reduced test case**](https://css-tricks.com/reduced-test-cases/). 7 + All bug reports and problem issues require a [**reduced test case**](https://css-tricks.com/reduced-test-cases/). Create one by [forking this CodePen](https://codepen.io/desandro/pen/xNLWwG). 10 8 11 9 + A reduced test case clearly demonstrates the bug or issue. 12 10 + It contains the bare minimum HTML, CSS, and JavaScript required to demonstrate the bug.
+1 -1
.github/issue_template.md
··· 1 1 <!-- Thanks for submitting an issue! If you have a bug or problem issue, please include a **reduced test case**. Create one in CodePen or other demo site. See guidelines link above. --> 2 2 3 - **Test case:** https://codepen.io/desandro/pen/azqbop 3 + **Test case:** https://codepen.io/desandro/pen/xNLWwG
+5 -4
Makefile
··· 1 1 bundle: 2 2 cat js/boilerplate.js js/canvas-renderer.js js/svg-renderer.js js/vector.js \ 3 - js/anchor.js js/path-command.js js/shape.js js/group.js js/rect.js \ 4 - js/rounded-rect.js js/ellipse.js js/polygon.js js/hemisphere.js \ 5 - js/cylinder.js js/cone.js js/box.js > dist/zdog.dist.js 3 + js/anchor.js js/dragger.js js/illustration.js js/path-command.js \ 4 + js/shape.js js/group.js js/rect.js js/rounded-rect.js js/ellipse.js \ 5 + js/polygon.js js/hemisphere.js js/cylinder.js js/cone.js js/box.js \ 6 + js/index.js > dist/zdog.dist.js 6 7 7 8 uglify: 8 9 npx uglifyjs dist/zdog.dist.js -o dist/zdog.dist.min.js --mangle --comments /^!/ ··· 10 11 lint: 11 12 npx jshint js/*.js demos/**/*.js 12 13 13 - dist: hint bundle uglify 14 + dist: lint bundle uglify
+4 -4
README.md
··· 8 8 9 9 ### Download 10 10 11 - + [zdog.dist.min.js](https://unpkg.com/zdog@0/dist/zdog.dist.min.js) minified, or 12 - + [zdog.dist.js](https://unpkg.com/zdog@0/dist/zdog.dist.js) un-minified 11 + + [zdog.dist.min.js](https://unpkg.com/zdog@1/dist/zdog.dist.min.js) minified, or 12 + + [zdog.dist.js](https://unpkg.com/zdog@1/dist/zdog.dist.js) un-minified 13 13 14 14 ### CDN 15 15 16 16 Link directly to Zdog JS on [unpkg](https://unpkg.com). 17 17 18 18 ``` html 19 - <script src="https://unpkg.com/zdog@0/dist/zdog.dist.min.js"></script> 19 + <script src="https://unpkg.com/zdog@1/dist/zdog.dist.min.js"></script> 20 20 ``` 21 21 22 22 ### Package managers ··· 86 86 87 87 ### Other Zdog repos 88 88 89 - + [zdog-demos](https://github.com/metafizzy/zdog-demos) - Lots more bigger, wilder Zdog demos 89 + + [zdog-demos](https://github.com/metafizzy/zdog-demos) - More, bigger, wilder Zdog demos 90 90 + [zdog-docs](https://github.com/metafizzy/zdog-docs) - Documentation site source code for [zzz.dog](https://zzz.dog) 91 91 92 92 ---
+2096
dist/zdog.dist.js
··· 1 + /*! 2 + * Zdog v1.0.0 3 + * Round, flat, designer-friendly pseudo-3D engine 4 + * Licensed MIT 5 + * https://zzz.dog 6 + * Copyright 2019 Metafizzy 7 + */ 8 + 9 + /** 10 + * Boilerplate & utils 11 + */ 12 + 13 + ( function( root, factory ) { 14 + // module definition 15 + if ( typeof module == 'object' && module.exports ) { 16 + /* globals module */ // CommonJS 17 + module.exports = factory(); 18 + } else { 19 + // browser global 20 + root.Zdog = factory(); 21 + } 22 + }( this, function factory() { 23 + 24 + var Zdog = {}; 25 + 26 + Zdog.TAU = Math.PI * 2; 27 + 28 + Zdog.extend = function( a, b ) { 29 + for ( var prop in b ) { 30 + a[ prop ] = b[ prop ]; 31 + } 32 + return a; 33 + }; 34 + 35 + Zdog.lerp = function( a, b, t ) { 36 + return ( b - a ) * t + a; 37 + }; 38 + 39 + Zdog.modulo = function( num, div ) { 40 + return ( ( num % div ) + div ) % div; 41 + }; 42 + 43 + var powerMultipliers = { 44 + 2: function( a ) { 45 + return a * a; 46 + }, 47 + 3: function( a ) { 48 + return a * a * a; 49 + }, 50 + 4: function( a ) { 51 + return a * a * a * a; 52 + }, 53 + 5: function( a ) { 54 + return a * a * a * a * a; 55 + } 56 + }; 57 + 58 + Zdog.easeInOut = function( alpha, power ) { 59 + if ( power == 1 ) { 60 + return alpha; 61 + } 62 + alpha = Math.max( 0, Math.min( 1, alpha ) ); 63 + var isFirstHalf = alpha < 0.5; 64 + var slope = isFirstHalf ? alpha : 1 - alpha; 65 + slope = slope / 0.5; 66 + // make easing steeper with more multiples 67 + var powerMultiplier = powerMultipliers[ power ] || powerMultipliers[2]; 68 + var curve = powerMultiplier( slope ); 69 + curve = curve / 2; 70 + return isFirstHalf ? curve : 1 - curve; 71 + }; 72 + 73 + return Zdog; 74 + 75 + })); 76 + /** 77 + * CanvasRenderer 78 + */ 79 + 80 + ( function( root, factory ) { 81 + // module definition 82 + if ( typeof module == 'object' && module.exports ) { 83 + /* globals module */ // CommonJS 84 + module.exports = factory(); 85 + } else { 86 + // browser global 87 + root.Zdog.CanvasRenderer = factory(); 88 + } 89 + }( this, function factory() { 90 + 91 + var CanvasRenderer = { isCanvas: true }; 92 + 93 + CanvasRenderer.begin = function( ctx ) { 94 + ctx.beginPath(); 95 + }; 96 + 97 + CanvasRenderer.move = function( ctx, elem, point ) { 98 + ctx.moveTo( point.x, point.y ); 99 + }; 100 + 101 + CanvasRenderer.line = function( ctx, elem, point ) { 102 + ctx.lineTo( point.x, point.y ); 103 + }; 104 + 105 + CanvasRenderer.bezier = function( ctx, elem, cp0, cp1, end ) { 106 + ctx.bezierCurveTo( cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y ); 107 + }; 108 + 109 + CanvasRenderer.closePath = function( ctx ) { 110 + ctx.closePath(); 111 + }; 112 + 113 + CanvasRenderer.setPath = function() {}; 114 + 115 + CanvasRenderer.renderPath = function( ctx, elem, pathCommands, isClosed ) { 116 + this.begin( ctx, elem ); 117 + pathCommands.forEach( function( command ) { 118 + command.render( ctx, elem, CanvasRenderer ); 119 + }); 120 + if ( isClosed ) { 121 + this.closePath( ctx, elem ); 122 + } 123 + }; 124 + 125 + CanvasRenderer.stroke = function( ctx, elem, isStroke, color, lineWidth ) { 126 + if ( !isStroke ) { 127 + return; 128 + } 129 + ctx.strokeStyle = color; 130 + ctx.lineWidth = lineWidth; 131 + ctx.stroke(); 132 + }; 133 + 134 + CanvasRenderer.fill = function( ctx, elem, isFill, color ) { 135 + if ( !isFill ) { 136 + return; 137 + } 138 + ctx.fillStyle = color; 139 + ctx.fill(); 140 + }; 141 + 142 + CanvasRenderer.end = function() {}; 143 + 144 + return CanvasRenderer; 145 + 146 + })); 147 + /** 148 + * SvgRenderer 149 + */ 150 + 151 + ( function( root, factory ) { 152 + // module definition 153 + if ( typeof module == 'object' && module.exports ) { 154 + /* globals module */ // CommonJS 155 + module.exports = factory(); 156 + } else { 157 + // browser global 158 + root.Zdog.SvgRenderer = factory(); 159 + } 160 + }( this, function factory() { 161 + 162 + var SvgRenderer = { isSvg: true }; 163 + 164 + // round path coordinates to 3 decimals 165 + var round = SvgRenderer.round = function( num ) { 166 + return Math.round( num * 1000 ) / 1000; 167 + }; 168 + 169 + function getPointString( point ) { 170 + return round( point.x ) + ',' + round( point.y ) + ' '; 171 + } 172 + 173 + SvgRenderer.begin = function() {}; 174 + 175 + SvgRenderer.move = function( svg, elem, point ) { 176 + return 'M' + getPointString( point ); 177 + }; 178 + 179 + SvgRenderer.line = function( svg, elem, point ) { 180 + return 'L' + getPointString( point ); 181 + }; 182 + 183 + SvgRenderer.bezier = function( svg, elem, cp0, cp1, end ) { 184 + return 'C' + getPointString( cp0 ) + getPointString( cp1 ) + 185 + getPointString( end ); 186 + }; 187 + 188 + SvgRenderer.closePath = function(/* elem */) { 189 + return 'Z'; 190 + }; 191 + 192 + SvgRenderer.setPath = function( svg, elem, pathValue ) { 193 + elem.setAttribute( 'd', pathValue ); 194 + }; 195 + 196 + SvgRenderer.renderPath = function( svg, elem, pathCommands, isClosed ) { 197 + var pathValue = ''; 198 + pathCommands.forEach( function( command ) { 199 + pathValue += command.render( svg, elem, SvgRenderer ); 200 + }); 201 + if ( isClosed ) { 202 + pathValue += this.closePath( svg, elem ); 203 + } 204 + this.setPath( svg, elem, pathValue ); 205 + }; 206 + 207 + SvgRenderer.stroke = function( svg, elem, isStroke, color, lineWidth ) { 208 + if ( !isStroke ) { 209 + return; 210 + } 211 + elem.setAttribute( 'stroke', color ); 212 + elem.setAttribute( 'stroke-width', lineWidth ); 213 + }; 214 + 215 + SvgRenderer.fill = function( svg, elem, isFill, color ) { 216 + var fillColor = isFill ? color : 'none'; 217 + elem.setAttribute( 'fill', fillColor ); 218 + }; 219 + 220 + SvgRenderer.end = function( svg, elem ) { 221 + svg.appendChild( elem ); 222 + }; 223 + 224 + return SvgRenderer; 225 + 226 + })); 227 + /** 228 + * Vector 229 + */ 230 + 231 + ( function( root, factory ) { 232 + // module definition 233 + if ( typeof module == 'object' && module.exports ) { 234 + /* globals module, require */ // CommonJS 235 + module.exports = factory( require('./boilerplate') ); 236 + } else { 237 + // browser global 238 + var Zdog = root.Zdog; 239 + Zdog.Vector = factory( Zdog ); 240 + } 241 + 242 + }( this, function factory( utils ) { 243 + 244 + function Vector( position ) { 245 + this.set( position ); 246 + } 247 + 248 + var TAU = utils.TAU; 249 + 250 + // 'pos' = 'position' 251 + Vector.prototype.set = function( pos ) { 252 + this.x = pos && pos.x || 0; 253 + this.y = pos && pos.y || 0; 254 + this.z = pos && pos.z || 0; 255 + return this; 256 + }; 257 + 258 + // set coordinates without sanitizing 259 + // vec.write({ y: 2 }) only sets y coord 260 + Vector.prototype.write = function( pos ) { 261 + if ( !pos ) { 262 + return this; 263 + } 264 + this.x = pos.x != undefined ? pos.x : this.x; 265 + this.y = pos.y != undefined ? pos.y : this.y; 266 + this.z = pos.z != undefined ? pos.z : this.z; 267 + return this; 268 + }; 269 + 270 + Vector.prototype.rotate = function( rotation ) { 271 + if ( !rotation ) { 272 + return; 273 + } 274 + this.rotateZ( rotation.z ); 275 + this.rotateY( rotation.y ); 276 + this.rotateX( rotation.x ); 277 + return this; 278 + }; 279 + 280 + Vector.prototype.rotateZ = function( angle ) { 281 + rotateProperty( this, angle, 'x', 'y' ); 282 + }; 283 + 284 + Vector.prototype.rotateX = function( angle ) { 285 + rotateProperty( this, angle, 'y', 'z' ); 286 + }; 287 + 288 + Vector.prototype.rotateY = function( angle ) { 289 + rotateProperty( this, angle, 'x', 'z' ); 290 + }; 291 + 292 + function rotateProperty( vec, angle, propA, propB ) { 293 + if ( !angle || angle % TAU === 0 ) { 294 + return; 295 + } 296 + var cos = Math.cos( angle ); 297 + var sin = Math.sin( angle ); 298 + var a = vec[ propA ]; 299 + var b = vec[ propB ]; 300 + vec[ propA ] = a*cos - b*sin; 301 + vec[ propB ] = b*cos + a*sin; 302 + } 303 + 304 + Vector.prototype.add = function( pos ) { 305 + if ( !pos ) { 306 + return this; 307 + } 308 + this.x += pos.x || 0; 309 + this.y += pos.y || 0; 310 + this.z += pos.z || 0; 311 + return this; 312 + }; 313 + 314 + Vector.prototype.subtract = function( pos ) { 315 + if ( !pos ) { 316 + return this; 317 + } 318 + this.x -= pos.x || 0; 319 + this.y -= pos.y || 0; 320 + this.z -= pos.z || 0; 321 + return this; 322 + }; 323 + 324 + Vector.prototype.multiply = function( pos ) { 325 + if ( pos == undefined ) { 326 + return this; 327 + } 328 + // multiple all values by same number 329 + if ( typeof pos == 'number' ) { 330 + this.x *= pos; 331 + this.y *= pos; 332 + this.z *= pos; 333 + } else { 334 + // multiply object 335 + this.x *= pos.x != undefined ? pos.x : 1; 336 + this.y *= pos.y != undefined ? pos.y : 1; 337 + this.z *= pos.z != undefined ? pos.z : 1; 338 + } 339 + return this; 340 + }; 341 + 342 + Vector.prototype.transform = function( translation, rotation, scale ) { 343 + this.multiply( scale ); 344 + this.rotate( rotation ); 345 + this.add( translation ); 346 + return this; 347 + }; 348 + 349 + Vector.prototype.lerp = function( pos, t ) { 350 + this.x = utils.lerp( this.x, pos.x || 0, t ); 351 + this.y = utils.lerp( this.y, pos.y || 0, t ); 352 + this.z = utils.lerp( this.z, pos.z || 0, t ); 353 + return this; 354 + }; 355 + 356 + Vector.prototype.magnitude = function() { 357 + var sum = this.x*this.x + this.y*this.y + this.z*this.z; 358 + return getMagnitudeSqrt( sum ); 359 + }; 360 + 361 + function getMagnitudeSqrt( sum ) { 362 + // PERF: check if sum ~= 1 and skip sqrt 363 + if ( Math.abs( sum - 1 ) < 0.00000001 ) { 364 + return 1; 365 + } 366 + return Math.sqrt( sum ); 367 + } 368 + 369 + Vector.prototype.magnitude2d = function() { 370 + var sum = this.x*this.x + this.y*this.y; 371 + return getMagnitudeSqrt( sum ); 372 + }; 373 + 374 + Vector.prototype.copy = function() { 375 + return new Vector( this ); 376 + }; 377 + 378 + return Vector; 379 + 380 + })); 381 + /** 382 + * Anchor 383 + */ 384 + 385 + ( function( root, factory ) { 386 + // module definition 387 + if ( typeof module == 'object' && module.exports ) { 388 + /* globals module, require */ // CommonJS 389 + module.exports = factory( require('./boilerplate'), require('./vector') ); 390 + } else { 391 + // browser global 392 + var Zdog = root.Zdog; 393 + Zdog.Anchor = factory( Zdog, Zdog.Vector ); 394 + } 395 + }( this, function factory( utils, Vector ) { 396 + 397 + var TAU = utils.TAU; 398 + var onePoint = { x: 1, y: 1, z: 1 }; 399 + 400 + function Anchor( options ) { 401 + this.create( options || {} ); 402 + } 403 + 404 + Anchor.prototype.create = function( options ) { 405 + // set defaults & options 406 + utils.extend( this, this.constructor.defaults ); 407 + this.setOptions( options ); 408 + 409 + // transform 410 + this.translate = new Vector( options.translate ); 411 + this.rotate = new Vector( options.rotate ); 412 + this.scale = new Vector( onePoint ).multiply( this.scale ); 413 + // origin 414 + this.origin = new Vector(); 415 + this.renderOrigin = new Vector(); 416 + // children 417 + this.children = []; 418 + if ( this.addTo ) { 419 + this.addTo.addChild( this ); 420 + } 421 + }; 422 + 423 + Anchor.defaults = {}; 424 + 425 + Anchor.optionKeys = Object.keys( Anchor.defaults ).concat([ 426 + 'rotate', 427 + 'translate', 428 + 'scale', 429 + 'addTo', 430 + ]); 431 + 432 + Anchor.prototype.setOptions = function( options ) { 433 + var optionKeys = this.constructor.optionKeys; 434 + 435 + for ( var key in options ) { 436 + if ( optionKeys.includes( key ) ) { 437 + this[ key ] = options[ key ]; 438 + } 439 + } 440 + }; 441 + 442 + Anchor.prototype.addChild = function( shape ) { 443 + var index = this.children.indexOf( shape ); 444 + if ( index != -1 ) { 445 + return; 446 + } 447 + shape.remove(); // remove previous parent 448 + shape.addTo = this; // keep parent reference 449 + this.children.push( shape ); 450 + }; 451 + 452 + Anchor.prototype.removeChild = function( shape ) { 453 + var index = this.children.indexOf( shape ); 454 + if ( index != -1 ) { 455 + this.children.splice( index, 1 ); 456 + } 457 + }; 458 + 459 + Anchor.prototype.remove = function() { 460 + if ( this.addTo ) { 461 + this.addTo.removeChild( this ); 462 + } 463 + }; 464 + 465 + // ----- update ----- // 466 + 467 + Anchor.prototype.update = function() { 468 + // update self 469 + this.reset(); 470 + // update children 471 + this.children.forEach( function( child ) { 472 + child.update(); 473 + }); 474 + this.transform( this.translate, this.rotate, this.scale ); 475 + }; 476 + 477 + Anchor.prototype.reset = function() { 478 + this.renderOrigin.set( this.origin ); 479 + }; 480 + 481 + Anchor.prototype.transform = function( translation, rotation, scale ) { 482 + this.renderOrigin.transform( translation, rotation, scale ); 483 + // transform children 484 + this.children.forEach( function( child ) { 485 + child.transform( translation, rotation, scale ); 486 + }); 487 + }; 488 + 489 + Anchor.prototype.updateGraph = function() { 490 + this.update(); 491 + this.checkFlatGraph(); 492 + this.flatGraph.forEach( function( item ) { 493 + item.updateSortValue(); 494 + }); 495 + // z-sort 496 + this.flatGraph.sort( Anchor.shapeSorter ); 497 + }; 498 + 499 + Anchor.shapeSorter = function( a, b ) { 500 + return a.sortValue - b.sortValue; 501 + }; 502 + 503 + Anchor.prototype.checkFlatGraph = function() { 504 + if ( !this.flatGraph ) { 505 + this.updateFlatGraph(); 506 + } 507 + }; 508 + 509 + Anchor.prototype.updateFlatGraph = function() { 510 + this.flatGraph = this.getFlatGraph(); 511 + }; 512 + 513 + // return Array of self & all child graph items 514 + Anchor.prototype.getFlatGraph = function() { 515 + var flatGraph = [ this ]; 516 + this.children.forEach( function( child ) { 517 + var childFlatGraph = child.getFlatGraph(); 518 + flatGraph = flatGraph.concat( childFlatGraph ); 519 + }); 520 + return flatGraph; 521 + }; 522 + 523 + Anchor.prototype.updateSortValue = function() { 524 + this.sortValue = this.renderOrigin.z; 525 + }; 526 + 527 + // ----- render ----- // 528 + 529 + Anchor.prototype.render = function() {}; 530 + 531 + Anchor.prototype.renderGraphCanvas = function( ctx ) { 532 + if ( !ctx ) { 533 + throw new Error( 'ctx is ' + ctx + '. ' + 534 + 'Canvas context required for render. Check .renderGraphCanvas( ctx ).' ); 535 + } 536 + this.checkFlatGraph(); 537 + this.flatGraph.forEach( function( item ) { 538 + item.render( ctx, Zdog.CanvasRenderer ); 539 + }); 540 + }; 541 + 542 + Anchor.prototype.renderGraphSvg = function( svg ) { 543 + if ( !svg ) { 544 + throw new Error( 'svg is ' + svg + '. ' + 545 + 'SVG required for render. Check .renderGraphSvg( svg ).' ); 546 + } 547 + this.checkFlatGraph(); 548 + this.flatGraph.forEach( function( item ) { 549 + item.render( svg, Zdog.SvgRenderer ); 550 + }); 551 + }; 552 + 553 + // ----- misc ----- // 554 + 555 + Anchor.prototype.copy = function( options ) { 556 + // copy options 557 + var itemOptions = {}; 558 + var optionKeys = this.constructor.optionKeys; 559 + optionKeys.forEach( function( key ) { 560 + itemOptions[ key ] = this[ key ]; 561 + }, this ); 562 + // add set options 563 + utils.extend( itemOptions, options ); 564 + var ItemClass = this.constructor; 565 + return new ItemClass( itemOptions ); 566 + }; 567 + 568 + Anchor.prototype.copyGraph = function( options ) { 569 + var clone = this.copy( options ); 570 + this.children.forEach( function( child ) { 571 + child.copyGraph({ 572 + addTo: clone, 573 + }); 574 + }); 575 + return clone; 576 + }; 577 + 578 + Anchor.prototype.normalizeRotate = function() { 579 + this.rotate.x = utils.modulo( this.rotate.x, TAU ); 580 + this.rotate.y = utils.modulo( this.rotate.y, TAU ); 581 + this.rotate.z = utils.modulo( this.rotate.z, TAU ); 582 + }; 583 + 584 + // ----- subclass ----- // 585 + 586 + function getSubclass( Super ) { 587 + return function( defaults ) { 588 + // create constructor 589 + function Item( options ) { 590 + this.create( options || {} ); 591 + } 592 + 593 + Item.prototype = Object.create( Super.prototype ); 594 + Item.prototype.constructor = Item; 595 + 596 + Item.defaults = utils.extend( {}, Super.defaults ); 597 + utils.extend( Item.defaults, defaults ); 598 + // create optionKeys 599 + Item.optionKeys = Super.optionKeys.slice(0); 600 + // add defaults keys to optionKeys, dedupe 601 + Object.keys( Item.defaults ).forEach( function( key ) { 602 + if ( !Item.optionKeys.includes( key ) ) { 603 + Item.optionKeys.push( key ); 604 + } 605 + }); 606 + 607 + Item.subclass = getSubclass( Item ); 608 + 609 + return Item; 610 + }; 611 + } 612 + 613 + Anchor.subclass = getSubclass( Anchor ); 614 + 615 + return Anchor; 616 + 617 + })); 618 + /** 619 + * Dragger 620 + */ 621 + 622 + ( function( root, factory ) { 623 + // module definition 624 + if ( typeof module == 'object' && module.exports ) { 625 + /* globals module */ // CommonJS 626 + module.exports = factory(); 627 + } else { 628 + // browser global 629 + root.Zdog.Dragger = factory(); 630 + } 631 + }( this, function factory() { 632 + 633 + // quick & dirty drag event stuff 634 + // messes up if multiple pointers/touches 635 + 636 + // event support, default to mouse events 637 + var downEvent = 'mousedown'; 638 + var moveEvent = 'mousemove'; 639 + var upEvent = 'mouseup'; 640 + if ( window.PointerEvent ) { 641 + // PointerEvent, Chrome 642 + downEvent = 'pointerdown'; 643 + moveEvent = 'pointermove'; 644 + upEvent = 'pointerup'; 645 + } else if ( 'ontouchstart' in window ) { 646 + // Touch Events, iOS Safari 647 + downEvent = 'touchstart'; 648 + moveEvent = 'touchmove'; 649 + upEvent = 'touchend'; 650 + } 651 + 652 + function noop() {} 653 + 654 + function Dragger( options ) { 655 + this.create( options || {} ); 656 + } 657 + 658 + Dragger.prototype.create = function( options ) { 659 + this.onDragStart = options.onDragStart || noop; 660 + this.onDragMove = options.onDragMove || noop; 661 + this.onDragEnd = options.onDragEnd || noop; 662 + 663 + this.bindDrag( options.startElement ); 664 + }; 665 + 666 + Dragger.prototype.bindDrag = function( element ) { 667 + element = this.getQueryElement( element ); 668 + if ( element ) { 669 + element.addEventListener( downEvent , this ); 670 + } 671 + }; 672 + 673 + Dragger.prototype.getQueryElement = function( element ) { 674 + if ( typeof element == 'string' ) { 675 + // with string, query selector 676 + element = document.querySelector( element ); 677 + } 678 + return element; 679 + }; 680 + 681 + Dragger.prototype.handleEvent = function( event ) { 682 + var method = this[ 'on' + event.type ]; 683 + if ( method ) { 684 + method.call( this, event ); 685 + } 686 + }; 687 + 688 + Dragger.prototype.onmousedown = 689 + Dragger.prototype.onpointerdown = function( event ) { 690 + this.dragStart( event, event ); 691 + }; 692 + 693 + Dragger.prototype.ontouchstart = function( event ) { 694 + this.dragStart( event, event.changedTouches[0] ); 695 + }; 696 + 697 + Dragger.prototype.dragStart = function( event, pointer ) { 698 + event.preventDefault(); 699 + this.dragStartX = pointer.pageX; 700 + this.dragStartY = pointer.pageY; 701 + window.addEventListener( moveEvent, this ); 702 + window.addEventListener( upEvent, this ); 703 + this.onDragStart( pointer ); 704 + }; 705 + 706 + Dragger.prototype.ontouchmove = function( event ) { 707 + // HACK, moved touch may not be first 708 + this.dragMove( event, event.changedTouches[0] ); 709 + }; 710 + 711 + Dragger.prototype.onmousemove = 712 + Dragger.prototype.onpointermove = function( event ) { 713 + this.dragMove( event, event ); 714 + }; 715 + 716 + Dragger.prototype.dragMove = function( event, pointer ) { 717 + event.preventDefault(); 718 + var moveX = pointer.pageX - this.dragStartX; 719 + var moveY = pointer.pageY - this.dragStartY; 720 + this.onDragMove( pointer, moveX, moveY ); 721 + }; 722 + 723 + Dragger.prototype.onmouseup = 724 + Dragger.prototype.onpointerup = 725 + Dragger.prototype.ontouchend = 726 + Dragger.prototype.dragEnd = function(/* event */) { 727 + window.removeEventListener( moveEvent, this ); 728 + window.removeEventListener( upEvent, this ); 729 + this.onDragEnd(); 730 + }; 731 + 732 + return Dragger; 733 + 734 + })); 735 + /** 736 + * Illustration 737 + */ 738 + 739 + ( function( root, factory ) { 740 + // module definition 741 + if ( typeof module == 'object' && module.exports ) { 742 + /* globals module, require */ // CommonJS 743 + module.exports = factory( require('./boilerplate'), require('./anchor'), 744 + require('./dragger') ); 745 + } else { 746 + // browser global 747 + var Zdog = root.Zdog; 748 + Zdog.Illustration = factory( Zdog, Zdog.Anchor, Zdog.Dragger ); 749 + } 750 + }( this, function factory( utils, Anchor, Dragger ) { 751 + 752 + function noop() {} 753 + var TAU = utils.TAU; 754 + 755 + var Illustration = Anchor.subclass({ 756 + element: undefined, 757 + centered: true, 758 + zoom: 1, 759 + dragRotate: false, 760 + resize: false, 761 + onPrerender: noop, 762 + onDragStart: noop, 763 + onDragMove: noop, 764 + onDragEnd: noop, 765 + onResize: noop, 766 + }); 767 + 768 + utils.extend( Illustration.prototype, Dragger.prototype ); 769 + 770 + Illustration.prototype.create = function( options ) { 771 + Anchor.prototype.create.call( this, options ); 772 + Dragger.prototype.create.call( this, options ); 773 + this.setElement( this.element ); 774 + this.setDragRotate( this.dragRotate ); 775 + this.setResize( this.resize ); 776 + }; 777 + 778 + Illustration.prototype.setElement = function( element ) { 779 + element = this.getQueryElement( element ); 780 + if ( !element ) { 781 + throw new Error( 'Zdog.Illustration element required. Set to ' + element ); 782 + } 783 + 784 + var nodeName = element.nodeName.toLowerCase(); 785 + if ( nodeName == 'canvas' ) { 786 + this.setCanvas( element ); 787 + } else if ( nodeName == 'svg' ) { 788 + this.setSvg( element ); 789 + } 790 + }; 791 + 792 + Illustration.prototype.setSize = function( width, height ) { 793 + width = Math.round( width ); 794 + height = Math.round( height ); 795 + if ( this.isCanvas ) { 796 + this.setSizeCanvas( width, height ); 797 + } else if ( this.isSvg ) { 798 + this.setSizeSvg( width, height ); 799 + } 800 + }; 801 + 802 + Illustration.prototype.setResize = function( resize ) { 803 + this.resize = resize; 804 + // create resize event listener 805 + if ( !this.resizeListener ) { 806 + this.resizeListener = this.onWindowResize.bind( this ); 807 + } 808 + // add/remove event listener 809 + if ( resize ) { 810 + window.addEventListener( 'resize', this.resizeListener ); 811 + this.onWindowResize(); 812 + } else { 813 + window.removeEventListener( 'resize', this.resizeListener ); 814 + } 815 + }; 816 + 817 + // TODO debounce this? 818 + Illustration.prototype.onWindowResize = function() { 819 + this.setMeasuredSize(); 820 + this.onResize( this.width, this.height ); 821 + }; 822 + 823 + Illustration.prototype.setMeasuredSize = function() { 824 + var width, height; 825 + var isFullscreen = this.resize == 'fullscreen'; 826 + if ( isFullscreen ) { 827 + width = window.innerWidth; 828 + height = window.innerHeight; 829 + } else { 830 + var rect = this.element.getBoundingClientRect(); 831 + width = rect.width; 832 + height = rect.height; 833 + } 834 + this.setSize( width, height ); 835 + }; 836 + 837 + // ----- render ----- // 838 + 839 + Illustration.prototype.renderGraph = function( item ) { 840 + if ( this.isCanvas ) { 841 + this.renderGraphCanvas( item ); 842 + } else if ( this.isSvg ) { 843 + this.renderGraphSvg( item ); 844 + } 845 + }; 846 + 847 + // combo method 848 + Illustration.prototype.updateRenderGraph = function( item ) { 849 + this.updateGraph(); 850 + this.renderGraph( item ); 851 + }; 852 + 853 + // ----- canvas ----- // 854 + 855 + Illustration.prototype.setCanvas = function( element ) { 856 + this.element = element; 857 + this.isCanvas = true; 858 + // update related properties 859 + this.ctx = this.element.getContext('2d'); 860 + // set initial size 861 + this.setSizeCanvas( element.width, element.height ); 862 + }; 863 + 864 + Illustration.prototype.setSizeCanvas = function( width, height ) { 865 + this.width = width; 866 + this.height = height; 867 + // up-rez for hi-DPI devices 868 + var pixelRatio = this.pixelRatio = window.devicePixelRatio || 1; 869 + this.element.width = this.canvasWidth = width * pixelRatio; 870 + this.element.height = this.canvasHeight = height * pixelRatio; 871 + if ( pixelRatio > 1 ) { 872 + this.element.style.width = width + 'px'; 873 + this.element.style.height = height + 'px'; 874 + } 875 + }; 876 + 877 + Illustration.prototype.renderGraphCanvas = function( item ) { 878 + item = item || this; 879 + this.prerenderCanvas(); 880 + Anchor.prototype.renderGraphCanvas.call( item, this.ctx ); 881 + this.postrenderCanvas(); 882 + }; 883 + 884 + Illustration.prototype.prerenderCanvas = function() { 885 + var ctx = this.ctx; 886 + ctx.lineCap = 'round'; 887 + ctx.lineJoin = 'round'; 888 + ctx.clearRect( 0, 0, this.canvasWidth, this.canvasHeight ); 889 + ctx.save(); 890 + if ( this.centered ) { 891 + ctx.translate( this.width/2, this.height/2 ); 892 + } 893 + var scale = this.pixelRatio * this.zoom; 894 + ctx.scale( scale, scale ); 895 + this.onPrerender( ctx ); 896 + }; 897 + 898 + Illustration.prototype.postrenderCanvas = function () { 899 + this.ctx.restore(); 900 + }; 901 + 902 + // ----- svg ----- // 903 + 904 + Illustration.prototype.setSvg = function( element ) { 905 + this.element = element; 906 + this.isSvg = true; 907 + this.pixelRatio = 1; 908 + // set initial size from width & height attributes 909 + var width = element.getAttribute('width'); 910 + var height = element.getAttribute('height'); 911 + this.setSizeSvg( width, height ); 912 + }; 913 + 914 + Illustration.prototype.setSizeSvg = function( width, height ) { 915 + this.width = width; 916 + this.height = height; 917 + var viewWidth = width / this.zoom; 918 + var viewHeight = height / this.zoom; 919 + var viewX = this.centered ? -viewWidth/2 : 0; 920 + var viewY = this.centered ? -viewHeight/2 : 0; 921 + this.element.setAttribute( 'viewBox', viewX + ' ' + viewY + ' ' + 922 + viewWidth + ' ' + viewHeight ); 923 + if ( this.resize ) { 924 + // remove size attributes, let size be determined by viewbox 925 + this.element.removeAttribute('width'); 926 + this.element.removeAttribute('height'); 927 + } else { 928 + this.element.setAttribute( 'width', width ); 929 + this.element.setAttribute( 'height', height ); 930 + } 931 + }; 932 + 933 + Illustration.prototype.renderGraphSvg = function( item ) { 934 + item = item || this; 935 + empty( this.element ); 936 + this.onPrerender( this.element ); 937 + Anchor.prototype.renderGraphSvg.call( item, this.element ); 938 + }; 939 + 940 + function empty( element ) { 941 + while ( element.firstChild ) { 942 + element.removeChild( element.firstChild ); 943 + } 944 + } 945 + 946 + // ----- drag ----- // 947 + 948 + Illustration.prototype.setDragRotate = function( item ) { 949 + if ( !item ) { 950 + return; 951 + } else if ( item === true ) { 952 + item = this; 953 + } 954 + this.dragRotate = item; 955 + 956 + this.bindDrag( this.element ); 957 + }; 958 + 959 + Illustration.prototype.dragStart = function(/* event, pointer */) { 960 + this.dragStartRX = this.dragRotate.rotate.x; 961 + this.dragStartRY = this.dragRotate.rotate.y; 962 + Dragger.prototype.dragStart.apply( this, arguments ); 963 + }; 964 + 965 + Illustration.prototype.dragMove = function( event, pointer ) { 966 + var moveX = pointer.pageX - this.dragStartX; 967 + var moveY = pointer.pageY - this.dragStartY; 968 + var displaySize = Math.min( this.width, this.height ); 969 + var moveRY = moveX / displaySize * TAU; 970 + var moveRX = moveY / displaySize * TAU; 971 + this.dragRotate.rotate.x = this.dragStartRX - moveRX; 972 + this.dragRotate.rotate.y = this.dragStartRY - moveRY; 973 + Dragger.prototype.dragMove.apply( this, arguments ); 974 + }; 975 + 976 + return Illustration; 977 + 978 + })); 979 + /** 980 + * PathCommand 981 + */ 982 + 983 + ( function( root, factory ) { 984 + // module definition 985 + if ( typeof module == 'object' && module.exports ) { 986 + /* globals module, require */ // CommonJS 987 + module.exports = factory( require('./vector') ); 988 + } else { 989 + // browser global 990 + var Zdog = root.Zdog; 991 + Zdog.PathCommand = factory( Zdog.Vector ); 992 + } 993 + }( this, function factory( Vector ) { 994 + 995 + function PathCommand( method, points, previousPoint ) { 996 + this.method = method; 997 + this.points = points.map( mapVectorPoint ); 998 + this.renderPoints = points.map( mapNewVector ); 999 + this.previousPoint = previousPoint; 1000 + this.endRenderPoint = this.renderPoints[ this.renderPoints.length - 1 ]; 1001 + // arc actions come with previous point & corner point 1002 + // but require bezier control points 1003 + if ( method == 'arc' ) { 1004 + this.controlPoints = [ new Vector(), new Vector() ]; 1005 + } 1006 + } 1007 + 1008 + function mapVectorPoint( point ) { 1009 + if ( point instanceof Vector ) { 1010 + return point; 1011 + } else { 1012 + return new Vector( point ); 1013 + } 1014 + } 1015 + 1016 + function mapNewVector( point ) { 1017 + return new Vector( point ); 1018 + } 1019 + 1020 + PathCommand.prototype.reset = function() { 1021 + // reset renderPoints back to orignal points position 1022 + var points = this.points; 1023 + this.renderPoints.forEach( function( renderPoint, i ) { 1024 + var point = points[i]; 1025 + renderPoint.set( point ); 1026 + }); 1027 + }; 1028 + 1029 + PathCommand.prototype.transform = function( translation, rotation, scale ) { 1030 + this.renderPoints.forEach( function( renderPoint ) { 1031 + renderPoint.transform( translation, rotation, scale ); 1032 + }); 1033 + }; 1034 + 1035 + PathCommand.prototype.render = function( ctx, elem, renderer ) { 1036 + return this[ this.method ]( ctx, elem, renderer ); 1037 + }; 1038 + 1039 + PathCommand.prototype.move = function( ctx, elem, renderer ) { 1040 + return renderer.move( ctx, elem, this.renderPoints[0] ); 1041 + }; 1042 + 1043 + PathCommand.prototype.line = function( ctx, elem, renderer ) { 1044 + return renderer.line( ctx, elem, this.renderPoints[0] ); 1045 + }; 1046 + 1047 + PathCommand.prototype.bezier = function( ctx, elem, renderer ) { 1048 + var cp0 = this.renderPoints[0]; 1049 + var cp1 = this.renderPoints[1]; 1050 + var end = this.renderPoints[2]; 1051 + return renderer.bezier( ctx, elem, cp0, cp1, end ); 1052 + }; 1053 + 1054 + PathCommand.prototype.arc = function( ctx, elem, renderer ) { 1055 + var prev = this.previousPoint; 1056 + var corner = this.renderPoints[0]; 1057 + var end = this.renderPoints[1]; 1058 + var cp0 = this.controlPoints[0]; 1059 + var cp1 = this.controlPoints[1]; 1060 + cp0.set( prev ).lerp( corner, 9/16 ); 1061 + cp1.set( end ).lerp( corner, 9/16 ); 1062 + return renderer.bezier( ctx, elem, cp0, cp1, end ); 1063 + }; 1064 + 1065 + return PathCommand; 1066 + 1067 + })); 1068 + /** 1069 + * Shape 1070 + */ 1071 + 1072 + ( function( root, factory ) { 1073 + // module definition 1074 + if ( typeof module == 'object' && module.exports ) { 1075 + /* globals module, require */ // CommonJS 1076 + module.exports = factory( require('./boilerplate'), require('./vector'), 1077 + require('./path-command'), require('./anchor') ); 1078 + } else { 1079 + // browser global 1080 + var Zdog = root.Zdog; 1081 + Zdog.Shape = factory( Zdog, Zdog.Vector, Zdog.PathCommand, Zdog.Anchor ); 1082 + } 1083 + }( this, function factory( utils, Vector, PathCommand, Anchor ) { 1084 + 1085 + var Shape = Anchor.subclass({ 1086 + stroke: 1, 1087 + fill: false, 1088 + color: '#333', 1089 + closed: true, 1090 + visible: true, 1091 + path: [ {} ], 1092 + front: { z: 1 }, 1093 + backface: true, 1094 + }); 1095 + 1096 + Shape.prototype.create = function( options ) { 1097 + Anchor.prototype.create.call( this, options ); 1098 + this.updatePath(); 1099 + // front 1100 + this.front = new Vector( options.front || this.front ); 1101 + this.renderFront = new Vector( this.front ); 1102 + this.renderNormal = new Vector(); 1103 + }; 1104 + 1105 + var actionNames = [ 1106 + 'move', 1107 + 'line', 1108 + 'bezier', 1109 + 'arc', 1110 + ]; 1111 + 1112 + Shape.prototype.updatePath = function() { 1113 + this.setPath(); 1114 + this.updatePathCommands(); 1115 + }; 1116 + 1117 + // place holder for Ellipse, Rect, etc. 1118 + Shape.prototype.setPath = function() {}; 1119 + 1120 + // parse path into PathCommands 1121 + Shape.prototype.updatePathCommands = function() { 1122 + var previousPoint; 1123 + this.pathCommands = this.path.map( function( pathPart, i ) { 1124 + // pathPart can be just vector coordinates -> { x, y, z } 1125 + // or path instruction -> { arc: [ {x0,y0,z0}, {x1,y1,z1} ] } 1126 + var keys = Object.keys( pathPart ); 1127 + var method = keys[0]; 1128 + var points = pathPart[ method ]; 1129 + // default to line if no instruction 1130 + var isInstruction = keys.length == 1 && actionNames.includes( method ); 1131 + if ( !isInstruction ) { 1132 + method = 'line'; 1133 + points = pathPart; 1134 + } 1135 + // munge single-point methods like line & move without arrays 1136 + var isLineOrMove = method == 'line' || method == 'move'; 1137 + var isPointsArray = Array.isArray( points ); 1138 + if ( isLineOrMove && !isPointsArray ) { 1139 + points = [ points ]; 1140 + } 1141 + 1142 + // first action is always move 1143 + method = i === 0 ? 'move' : method; 1144 + // arcs require previous last point 1145 + var command = new PathCommand( method, points, previousPoint ); 1146 + // update previousLastPoint 1147 + previousPoint = command.endRenderPoint; 1148 + return command; 1149 + }); 1150 + }; 1151 + 1152 + // ----- update ----- // 1153 + 1154 + Shape.prototype.reset = function() { 1155 + this.renderOrigin.set( this.origin ); 1156 + this.renderFront.set( this.front ); 1157 + // reset command render points 1158 + this.pathCommands.forEach( function( command ) { 1159 + command.reset(); 1160 + }); 1161 + }; 1162 + 1163 + Shape.prototype.transform = function( translation, rotation, scale ) { 1164 + // calculate render points backface visibility & cone/hemisphere shapes 1165 + this.renderOrigin.transform( translation, rotation, scale ); 1166 + this.renderFront.transform( translation, rotation, scale ); 1167 + this.renderNormal.set( this.renderOrigin ).subtract( this.renderFront ); 1168 + // transform points 1169 + this.pathCommands.forEach( function( command ) { 1170 + command.transform( translation, rotation, scale ); 1171 + }); 1172 + // transform children 1173 + this.children.forEach( function( child ) { 1174 + child.transform( translation, rotation, scale ); 1175 + }); 1176 + }; 1177 + 1178 + 1179 + Shape.prototype.updateSortValue = function() { 1180 + var sortValueTotal = 0; 1181 + this.pathCommands.forEach( function( command ) { 1182 + sortValueTotal += command.endRenderPoint.z; 1183 + }); 1184 + // average sort value of all points 1185 + // def not geometrically correct, but works for me 1186 + this.sortValue = sortValueTotal / this.pathCommands.length; 1187 + }; 1188 + 1189 + // ----- render ----- // 1190 + 1191 + Shape.prototype.render = function( ctx, renderer ) { 1192 + var length = this.pathCommands.length; 1193 + if ( !this.visible || !length ) { 1194 + return; 1195 + } 1196 + // do not render if hiding backface 1197 + this.isFacingBack = this.renderNormal.z > 0; 1198 + if ( !this.backface && this.isFacingBack ) { 1199 + return; 1200 + } 1201 + if ( !renderer ) { 1202 + throw new Error( 'Zdog renderer required. Set to ' + renderer ); 1203 + } 1204 + // render dot or path 1205 + var isDot = length == 1; 1206 + if ( renderer.isCanvas && isDot ) { 1207 + this.renderCanvasDot( ctx, renderer ); 1208 + } else { 1209 + this.renderPath( ctx, renderer ); 1210 + } 1211 + }; 1212 + 1213 + var TAU = utils.TAU; 1214 + // Safari does not render lines with no size, have to render circle instead 1215 + Shape.prototype.renderCanvasDot = function( ctx ) { 1216 + var lineWidth = this.getLineWidth(); 1217 + if ( !lineWidth ) { 1218 + return; 1219 + } 1220 + ctx.fillStyle = this.getRenderColor(); 1221 + var point = this.pathCommands[0].endRenderPoint; 1222 + ctx.beginPath(); 1223 + var radius = lineWidth/2; 1224 + ctx.arc( point.x, point.y, radius, 0, TAU ); 1225 + ctx.fill(); 1226 + }; 1227 + 1228 + Shape.prototype.getLineWidth = function() { 1229 + if ( !this.stroke ) { 1230 + return 0; 1231 + } 1232 + if ( this.stroke == true ) { 1233 + return 1; 1234 + } 1235 + return this.stroke; 1236 + }; 1237 + 1238 + Shape.prototype.getRenderColor = function() { 1239 + // use backface color if applicable 1240 + var isBackfaceColor = typeof this.backface == 'string' && this.isFacingBack; 1241 + var color = isBackfaceColor ? this.backface : this.color; 1242 + return color; 1243 + }; 1244 + 1245 + Shape.prototype.renderPath = function( ctx, renderer ) { 1246 + var elem = this.getRenderElement( ctx, renderer ); 1247 + var isTwoPoints = this.pathCommands.length == 2 && 1248 + this.pathCommands[1].method == 'line'; 1249 + var isClosed = !isTwoPoints && this.closed; 1250 + var color = this.getRenderColor(); 1251 + 1252 + renderer.renderPath( ctx, elem, this.pathCommands, isClosed ); 1253 + renderer.stroke( ctx, elem, this.stroke, color, this.getLineWidth() ); 1254 + renderer.fill( ctx, elem, this.fill, color ); 1255 + renderer.end( ctx, elem ); 1256 + }; 1257 + 1258 + var svgURI = 'http://www.w3.org/2000/svg'; 1259 + 1260 + Shape.prototype.getRenderElement = function( ctx, renderer ) { 1261 + if ( !renderer.isSvg ) { 1262 + return; 1263 + } 1264 + if ( !this.svgElement ) { 1265 + // create svgElement 1266 + this.svgElement = document.createElementNS( svgURI, 'path'); 1267 + this.svgElement.setAttribute( 'stroke-linecap', 'round' ); 1268 + this.svgElement.setAttribute( 'stroke-linejoin', 'round' ); 1269 + } 1270 + return this.svgElement; 1271 + }; 1272 + 1273 + return Shape; 1274 + 1275 + })); 1276 + /** 1277 + * Group 1278 + */ 1279 + 1280 + ( function( root, factory ) { 1281 + // module definition 1282 + if ( typeof module == 'object' && module.exports ) { 1283 + /* globals module, require */ // CommonJS 1284 + module.exports = factory( require('./anchor') ); 1285 + } else { 1286 + // browser global 1287 + var Zdog = root.Zdog; 1288 + Zdog.Group = factory( Zdog.Anchor ); 1289 + } 1290 + }( this, function factory( Anchor ) { 1291 + 1292 + var Group = Anchor.subclass({ 1293 + updateSort: false, 1294 + visible: true, 1295 + }); 1296 + 1297 + // ----- update ----- // 1298 + 1299 + Group.prototype.updateSortValue = function() { 1300 + var sortValueTotal = 0; 1301 + this.checkFlatGraph(); 1302 + this.flatGraph.forEach( function( item ) { 1303 + item.updateSortValue(); 1304 + sortValueTotal += item.sortValue; 1305 + }); 1306 + // average sort value of all points 1307 + // def not geometrically correct, but works for me 1308 + this.sortValue = sortValueTotal / this.flatGraph.length; 1309 + 1310 + if ( this.updateSort ) { 1311 + this.flatGraph.sort( Anchor.shapeSorter ); 1312 + } 1313 + }; 1314 + 1315 + // ----- render ----- // 1316 + 1317 + Group.prototype.render = function( ctx, renderer ) { 1318 + if ( !this.visible ) { 1319 + return; 1320 + } 1321 + 1322 + this.checkFlatGraph(); 1323 + this.flatGraph.forEach( function( item ) { 1324 + item.render( ctx, renderer ); 1325 + }); 1326 + }; 1327 + 1328 + // do not include children, group handles rendering & sorting internally 1329 + Group.prototype.getFlatGraph = function() { 1330 + return [ this ]; 1331 + }; 1332 + 1333 + // get flat graph only used for group 1334 + // do not include in parent flatGraphs 1335 + Group.prototype.updateFlatGraph = function() { 1336 + // do not include self 1337 + var flatGraph = []; 1338 + this.children.forEach( function( child ) { 1339 + var childFlatGraph = child.getFlatGraph(); 1340 + flatGraph = flatGraph.concat( childFlatGraph ); 1341 + }); 1342 + this.flatGraph = flatGraph; 1343 + }; 1344 + 1345 + return Group; 1346 + 1347 + })); 1348 + /** 1349 + * Rect 1350 + */ 1351 + 1352 + ( function( root, factory ) { 1353 + // module definition 1354 + if ( typeof module == 'object' && module.exports ) { 1355 + /* globals module, require */// CommonJS 1356 + module.exports = factory( require('./shape') ); 1357 + } else { 1358 + // browser global 1359 + var Zdog = root.Zdog; 1360 + Zdog.Rect = factory( Zdog.Shape ); 1361 + } 1362 + }( this, function factory( Shape ) { 1363 + 1364 + var Rect = Shape.subclass({ 1365 + width: 1, 1366 + height: 1, 1367 + }); 1368 + 1369 + Rect.prototype.setPath = function() { 1370 + var x = this.width / 2; 1371 + var y = this.height / 2; 1372 + this.path = [ 1373 + { x: -x, y: -y }, 1374 + { x: x, y: -y }, 1375 + { x: x, y: y }, 1376 + { x: -x, y: y }, 1377 + ]; 1378 + }; 1379 + 1380 + return Rect; 1381 + 1382 + })); 1383 + /** 1384 + * RoundedRect 1385 + */ 1386 + 1387 + ( function( root, factory ) { 1388 + // module definition 1389 + if ( typeof module == 'object' && module.exports ) { 1390 + /* globals module, require */ // CommonJS 1391 + module.exports = factory( require('./shape') ); 1392 + } else { 1393 + // browser global 1394 + var Zdog = root.Zdog; 1395 + Zdog.RoundedRect = factory( Zdog.Shape ); 1396 + } 1397 + }( this, function factory( Shape ) { 1398 + 1399 + var RoundedRect = Shape.subclass({ 1400 + width: 1, 1401 + height: 1, 1402 + cornerRadius: 0.25, 1403 + closed: false, 1404 + }); 1405 + 1406 + RoundedRect.prototype.setPath = function() { 1407 + var xA = this.width / 2; 1408 + var yA = this.height / 2; 1409 + var shortSide = Math.min( xA, yA ); 1410 + var cornerRadius = Math.min( this.cornerRadius, shortSide ); 1411 + var xB = xA - cornerRadius; 1412 + var yB = yA - cornerRadius; 1413 + var path = [ 1414 + // top right corner 1415 + { x: xB, y: -yA }, 1416 + { arc: [ 1417 + { x: xA, y: -yA }, 1418 + { x: xA, y: -yB }, 1419 + ]}, 1420 + ]; 1421 + // bottom right corner 1422 + if ( yB ) { 1423 + path.push({ x: xA, y: yB }); 1424 + } 1425 + path.push({ arc: [ 1426 + { x: xA, y: yA }, 1427 + { x: xB, y: yA }, 1428 + ]}); 1429 + // bottom left corner 1430 + if ( xB ) { 1431 + path.push({ x: -xB, y: yA }); 1432 + } 1433 + path.push({ arc: [ 1434 + { x: -xA, y: yA }, 1435 + { x: -xA, y: yB }, 1436 + ]}); 1437 + // top left corner 1438 + if ( yB ) { 1439 + path.push({ x: -xA, y: -yB }); 1440 + } 1441 + path.push({ arc: [ 1442 + { x: -xA, y: -yA }, 1443 + { x: -xB, y: -yA }, 1444 + ]}); 1445 + 1446 + // back to top right corner 1447 + if ( xB ) { 1448 + path.push({ x: xB, y: -yA }); 1449 + } 1450 + 1451 + this.path = path; 1452 + }; 1453 + 1454 + RoundedRect.prototype.updateSortValue = function() { 1455 + Shape.prototype.updateSortValue.apply( this, arguments ); 1456 + // ellipse is self closing, do not count last point twice 1457 + var length = this.pathCommands.length; 1458 + var lastPoint = this.pathCommands[ length - 1 ].endRenderPoint; 1459 + this.sortValue -= lastPoint.z / length; 1460 + }; 1461 + 1462 + return RoundedRect; 1463 + 1464 + })); 1465 + /** 1466 + * Ellipse 1467 + */ 1468 + 1469 + ( function( root, factory ) { 1470 + // module definition 1471 + if ( typeof module == 'object' && module.exports ) { 1472 + /* globals module, require */ // CommonJS 1473 + module.exports = factory( require('./shape') ); 1474 + } else { 1475 + // browser global 1476 + var Zdog = root.Zdog; 1477 + Zdog.Ellipse = factory( Zdog.Shape ); 1478 + } 1479 + 1480 + }( this, function factory( Shape ) { 1481 + 1482 + var Ellipse = Shape.subclass({ 1483 + diameter: 1, 1484 + width: undefined, 1485 + height: undefined, 1486 + quarters: 4, 1487 + closed: false, 1488 + }); 1489 + 1490 + Ellipse.prototype.setPath = function() { 1491 + var width = this.width != undefined ? this.width : this.diameter; 1492 + var height = this.height != undefined ? this.height : this.diameter; 1493 + var x = width / 2; 1494 + var y = height / 2; 1495 + this.path = [ 1496 + { x: 0, y: -y }, 1497 + { arc: [ // top right 1498 + { x: x, y: -y }, 1499 + { x: x, y: 0 }, 1500 + ]} 1501 + ]; 1502 + // bottom right 1503 + if ( this.quarters > 1 ) { 1504 + this.path.push({ arc: [ 1505 + { x: x, y: y }, 1506 + { x: 0, y: y }, 1507 + ]}); 1508 + } 1509 + // bottom left 1510 + if ( this.quarters > 2 ) { 1511 + this.path.push({ arc: [ 1512 + { x: -x, y: y }, 1513 + { x: -x, y: 0 }, 1514 + ]}); 1515 + } 1516 + // top left 1517 + if ( this.quarters > 3 ) { 1518 + this.path.push({ arc: [ 1519 + { x: -x, y: -y }, 1520 + { x: 0, y: -y }, 1521 + ]}); 1522 + } 1523 + }; 1524 + 1525 + Ellipse.prototype.updateSortValue = function() { 1526 + Shape.prototype.updateSortValue.apply( this, arguments ); 1527 + if ( this.quarters != 4 ) { 1528 + return; 1529 + } 1530 + // ellipse is self closing, do not count last point twice 1531 + var length = this.pathCommands.length; 1532 + var lastPoint = this.pathCommands[ length - 1 ].endRenderPoint; 1533 + this.sortValue -= lastPoint.z / length; 1534 + }; 1535 + 1536 + return Ellipse; 1537 + 1538 + })); 1539 + /** 1540 + * Shape 1541 + */ 1542 + 1543 + ( function( root, factory ) { 1544 + // module definition 1545 + if ( typeof module == 'object' && module.exports ) { 1546 + /* globals module, require */ // CommonJS 1547 + module.exports = factory( require('./boilerplate'), require('./shape') ); 1548 + } else { 1549 + // browser global 1550 + var Zdog = root.Zdog; 1551 + Zdog.Polygon = factory( Zdog, Zdog.Shape ); 1552 + } 1553 + }( this, function factory( utils, Shape ) { 1554 + 1555 + var Polygon = Shape.subclass({ 1556 + sides: 3, 1557 + radius: 0.5, 1558 + }); 1559 + 1560 + var TAU = utils.TAU; 1561 + 1562 + Polygon.prototype.setPath = function() { 1563 + this.path = []; 1564 + for ( var i=0; i < this.sides; i++ ) { 1565 + var theta = i/this.sides * TAU - TAU/4; 1566 + var x = Math.cos( theta ) * this.radius; 1567 + var y = Math.sin( theta ) * this.radius; 1568 + this.path.push({ x: x, y: y }); 1569 + } 1570 + }; 1571 + 1572 + return Polygon; 1573 + 1574 + })); 1575 + /** 1576 + * Hemisphere composite shape 1577 + */ 1578 + 1579 + ( function( root, factory ) { 1580 + // module definition 1581 + if ( typeof module == 'object' && module.exports ) { 1582 + /* globals module, require */ // CommonJS 1583 + module.exports = factory( require('./boilerplate'), require('./ellipse') ); 1584 + } else { 1585 + // browser global 1586 + var Zdog = root.Zdog; 1587 + Zdog.Hemisphere = factory( Zdog, Zdog.Ellipse ); 1588 + } 1589 + }( this, function factory( utils, Ellipse ) { 1590 + 1591 + var Hemisphere = Ellipse.subclass({ 1592 + fill: true, 1593 + }); 1594 + 1595 + var TAU = utils.TAU; 1596 + 1597 + Hemisphere.prototype.render = function( ctx, renderer ) { 1598 + this.renderDome( ctx, renderer ); 1599 + // call super 1600 + Ellipse.prototype.render.apply( this, arguments ); 1601 + }; 1602 + 1603 + Hemisphere.prototype.renderDome = function( ctx, renderer ) { 1604 + if ( !this.visible ) { 1605 + return; 1606 + } 1607 + var elem = this.getDomeRenderElement( ctx, renderer ); 1608 + var contourAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x ); 1609 + var domeRadius = this.diameter/2 * this.renderNormal.magnitude(); 1610 + var x = this.renderOrigin.x; 1611 + var y = this.renderOrigin.y; 1612 + 1613 + if ( renderer.isCanvas ) { 1614 + // canvas 1615 + var startAngle = contourAngle + TAU/4; 1616 + var endAngle = contourAngle - TAU/4; 1617 + ctx.beginPath(); 1618 + ctx.arc( x, y, domeRadius, startAngle, endAngle ); 1619 + } else if ( renderer.isSvg ) { 1620 + // svg 1621 + contourAngle = (contourAngle - TAU/4) / TAU * 360; 1622 + this.domeSvgElement.setAttribute( 'd', 'M ' + (-domeRadius) + ',0 A ' + 1623 + domeRadius + ',' + domeRadius + ' 0 0 1 ' + domeRadius + ',0' ); 1624 + this.domeSvgElement.setAttribute( 'transform', 1625 + 'translate(' + x + ',' + y + ' ) rotate(' + contourAngle + ')' ); 1626 + } 1627 + 1628 + renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() ); 1629 + renderer.fill( ctx, elem, this.fill, this.color ); 1630 + renderer.end( ctx, elem ); 1631 + }; 1632 + 1633 + var svgURI = 'http://www.w3.org/2000/svg'; 1634 + 1635 + Hemisphere.prototype.getDomeRenderElement = function( ctx, renderer ) { 1636 + if ( !renderer.isSvg ) { 1637 + return; 1638 + } 1639 + if ( !this.domeSvgElement ) { 1640 + // create svgElement 1641 + this.domeSvgElement = document.createElementNS( svgURI, 'path'); 1642 + this.domeSvgElement.setAttribute( 'stroke-linecap', 'round' ); 1643 + this.domeSvgElement.setAttribute( 'stroke-linejoin', 'round' ); 1644 + } 1645 + return this.domeSvgElement; 1646 + }; 1647 + 1648 + return Hemisphere; 1649 + 1650 + })); 1651 + /** 1652 + * Cylinder composite shape 1653 + */ 1654 + 1655 + ( function( root, factory ) { 1656 + // module definition 1657 + if ( typeof module == 'object' && module.exports ) { 1658 + /* globals module, require */ // CommonJS 1659 + module.exports = factory( require('./boilerplate'), 1660 + require('./path-command'), require('./shape'), require('./group'), 1661 + require('./ellipse') ); 1662 + } else { 1663 + // browser global 1664 + var Zdog = root.Zdog; 1665 + Zdog.Cylinder = factory( Zdog, Zdog.PathCommand, Zdog.Shape, 1666 + Zdog.Group, Zdog.Ellipse ); 1667 + } 1668 + }( this, function factory( utils, PathCommand, Shape, Group, Ellipse ) { 1669 + 1670 + function noop() {} 1671 + 1672 + // ----- CylinderGroup ----- // 1673 + 1674 + var CylinderGroup = Group.subclass({ 1675 + color: '#333', 1676 + updateSort: true, 1677 + }); 1678 + 1679 + CylinderGroup.prototype.create = function() { 1680 + Group.prototype.create.apply( this, arguments ); 1681 + this.pathCommands = [ 1682 + new PathCommand( 'move', [ {} ] ), 1683 + new PathCommand( 'line', [ {} ] ), 1684 + ]; 1685 + }; 1686 + 1687 + CylinderGroup.prototype.render = function( ctx, renderer ) { 1688 + this.renderCylinderSurface( ctx, renderer ); 1689 + Group.prototype.render.apply( this, arguments ); 1690 + }; 1691 + 1692 + CylinderGroup.prototype.renderCylinderSurface = function( ctx, renderer ) { 1693 + if ( !this.visible ) { 1694 + return; 1695 + } 1696 + // render cylinder surface 1697 + var elem = this.getRenderElement( ctx, renderer ); 1698 + var frontBase = this.frontBase; 1699 + var rearBase = this.rearBase; 1700 + var scale = frontBase.renderNormal.magnitude(); 1701 + var strokeWidth = frontBase.diameter * scale + frontBase.getLineWidth(); 1702 + // set path command render points 1703 + this.pathCommands[0].renderPoints[0].set( frontBase.renderOrigin ); 1704 + this.pathCommands[1].renderPoints[0].set( rearBase.renderOrigin ); 1705 + 1706 + if ( renderer.isCanvas ) { 1707 + ctx.lineCap = 'butt'; // nice 1708 + } 1709 + renderer.renderPath( ctx, elem, this.pathCommands ); 1710 + renderer.stroke( ctx, elem, true, this.color, strokeWidth ); 1711 + renderer.end( ctx, elem ); 1712 + 1713 + if ( renderer.isCanvas ) { 1714 + ctx.lineCap = 'round'; // reset 1715 + } 1716 + }; 1717 + 1718 + var svgURI = 'http://www.w3.org/2000/svg'; 1719 + 1720 + CylinderGroup.prototype.getRenderElement = function( ctx, renderer ) { 1721 + if ( !renderer.isSvg ) { 1722 + return; 1723 + } 1724 + if ( !this.svgElement ) { 1725 + // create svgElement 1726 + this.svgElement = document.createElementNS( svgURI, 'path'); 1727 + } 1728 + return this.svgElement; 1729 + }; 1730 + 1731 + // prevent double-creation in parent.copyGraph() 1732 + // only create in Cylinder.create() 1733 + CylinderGroup.prototype.copyGraph = noop; 1734 + 1735 + // ----- CylinderEllipse ----- // 1736 + 1737 + var CylinderEllipse = Ellipse.subclass(); 1738 + 1739 + CylinderEllipse.prototype.copyGraph = noop; 1740 + 1741 + // ----- Cylinder ----- // 1742 + 1743 + var Cylinder = Shape.subclass({ 1744 + diameter: 1, 1745 + length: 1, 1746 + frontBaseColor: undefined, 1747 + rearBaseColor: undefined, 1748 + fill: true, 1749 + }); 1750 + 1751 + var TAU = utils.TAU; 1752 + 1753 + Cylinder.prototype.create = function(/* options */) { 1754 + // call super 1755 + Shape.prototype.create.apply( this, arguments ); 1756 + // composite shape, create child shapes 1757 + // CylinderGroup to render cylinder surface then bases 1758 + this.group = new CylinderGroup({ 1759 + addTo: this, 1760 + color: this.color, 1761 + visible: this.visible, 1762 + }); 1763 + var baseZ = this.length/2; 1764 + var baseColor = this.backface || true; 1765 + // front outside base 1766 + this.frontBase = this.group.frontBase = new Ellipse({ 1767 + addTo: this.group, 1768 + diameter: this.diameter, 1769 + translate: { z: baseZ }, 1770 + rotate: { y: TAU/2 }, 1771 + color: this.color, 1772 + stroke: this.stroke, 1773 + fill: this.fill, 1774 + backface: this.frontBaseColor || baseColor, 1775 + visible: this.visible, 1776 + }); 1777 + // back outside base 1778 + this.rearBase = this.group.rearBase = this.frontBase.copy({ 1779 + translate: { z: -baseZ }, 1780 + rotate: { y: 0 }, 1781 + backface: this.rearBaseColor || baseColor, 1782 + }); 1783 + }; 1784 + 1785 + // Cylinder shape does not render anything 1786 + Cylinder.prototype.render = function() {}; 1787 + 1788 + // ----- set child properties ----- // 1789 + 1790 + var childProperties = [ 'stroke', 'fill', 'color', 'visible' ]; 1791 + childProperties.forEach( function( property ) { 1792 + // use proxy property for custom getter & setter 1793 + var _prop = '_' + property; 1794 + Object.defineProperty( Cylinder.prototype, property, { 1795 + get: function() { 1796 + return this[ _prop ]; 1797 + }, 1798 + set: function( value ) { 1799 + this[ _prop ] = value; 1800 + // set property on children 1801 + if ( this.frontBase ) { 1802 + this.frontBase[ property ] = value; 1803 + this.rearBase[ property ] = value; 1804 + this.group[ property ] = value; 1805 + } 1806 + }, 1807 + }); 1808 + }); 1809 + 1810 + // TODO child property setter for backface, frontBaseColor, & rearBaseColor 1811 + 1812 + return Cylinder; 1813 + 1814 + })); 1815 + /** 1816 + * Cone composite shape 1817 + */ 1818 + 1819 + ( function( root, factory ) { 1820 + // module definition 1821 + if ( typeof module == 'object' && module.exports ) { 1822 + /* globals module, require */ // CommonJS 1823 + module.exports = factory( require('./boilerplate'), require('./vector'), 1824 + require('./path-command'), require('./anchor'), require('./ellipse') ); 1825 + } else { 1826 + // browser global 1827 + var Zdog = root.Zdog; 1828 + Zdog.Cone = factory( Zdog, Zdog.Vector, Zdog.PathCommand, 1829 + Zdog.Anchor, Zdog.Ellipse ); 1830 + } 1831 + }( this, function factory( utils, Vector, PathCommand, Anchor, Ellipse ) { 1832 + 1833 + var Cone = Ellipse.subclass({ 1834 + length: 1, 1835 + fill: true, 1836 + }); 1837 + 1838 + var TAU = utils.TAU; 1839 + 1840 + Cone.prototype.create = function(/* options */) { 1841 + // call super 1842 + Ellipse.prototype.create.apply( this, arguments ); 1843 + // composite shape, create child shapes 1844 + this.apex = new Anchor({ 1845 + addTo: this, 1846 + translate: { z: this.length }, 1847 + }); 1848 + 1849 + // vectors used for calculation 1850 + this.renderApex = new Vector(); 1851 + this.tangentA = new Vector(); 1852 + this.tangentB = new Vector(); 1853 + 1854 + this.surfacePathCommands = [ 1855 + new PathCommand( 'move', [ {} ] ), // points set in renderConeSurface 1856 + new PathCommand( 'line', [ {} ] ), 1857 + new PathCommand( 'line', [ {} ] ), 1858 + ]; 1859 + }; 1860 + 1861 + Cone.prototype.render = function( ctx, renderer ) { 1862 + this.renderConeSurface( ctx, renderer ); 1863 + Ellipse.prototype.render.apply( this, arguments ); 1864 + }; 1865 + 1866 + Cone.prototype.renderConeSurface = function( ctx, renderer ) { 1867 + if ( !this.visible ) { 1868 + return; 1869 + } 1870 + this.renderApex.set( this.apex.renderOrigin ) 1871 + .subtract( this.renderOrigin ); 1872 + 1873 + var scale = this.renderNormal.magnitude(); 1874 + var apexDistance = this.renderApex.magnitude2d(); 1875 + var normalDistance = this.renderNormal.magnitude2d(); 1876 + // eccentricity 1877 + var eccenAngle = Math.acos( normalDistance / scale ); 1878 + var eccen = Math.sin( eccenAngle ); 1879 + var radius = this.diameter/2 * scale; 1880 + // does apex extend beyond eclipse of face 1881 + var isApexVisible = radius * eccen < apexDistance; 1882 + if ( !isApexVisible ) { 1883 + return; 1884 + } 1885 + // update tangents 1886 + var apexAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x ) + TAU/2; 1887 + var projectLength = apexDistance / eccen; 1888 + var projectAngle = Math.acos( radius / projectLength ); 1889 + // set tangent points 1890 + var tangentA = this.tangentA; 1891 + var tangentB = this.tangentB; 1892 + 1893 + tangentA.x = Math.cos( projectAngle ) * radius * eccen; 1894 + tangentA.y = Math.sin( projectAngle ) * radius; 1895 + 1896 + tangentB.set( this.tangentA ); 1897 + tangentB.y *= -1; 1898 + 1899 + tangentA.rotateZ( apexAngle ); 1900 + tangentB.rotateZ( apexAngle ); 1901 + tangentA.add( this.renderOrigin ); 1902 + tangentB.add( this.renderOrigin ); 1903 + 1904 + this.setSurfaceRenderPoint( 0, tangentA ); 1905 + this.setSurfaceRenderPoint( 1, this.apex.renderOrigin ); 1906 + this.setSurfaceRenderPoint( 2, tangentB ); 1907 + 1908 + // render 1909 + var elem = this.getSurfaceRenderElement( ctx, renderer ); 1910 + renderer.renderPath( ctx, elem, this.surfacePathCommands ); 1911 + renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() ); 1912 + renderer.fill( ctx, elem, this.fill, this.color ); 1913 + renderer.end( ctx, elem ); 1914 + }; 1915 + 1916 + var svgURI = 'http://www.w3.org/2000/svg'; 1917 + 1918 + Cone.prototype.getSurfaceRenderElement = function( ctx, renderer ) { 1919 + if ( !renderer.isSvg ) { 1920 + return; 1921 + } 1922 + if ( !this.surfaceSvgElement ) { 1923 + // create svgElement 1924 + this.surfaceSvgElement = document.createElementNS( svgURI, 'path'); 1925 + this.surfaceSvgElement.setAttribute( 'stroke-linecap', 'round' ); 1926 + this.surfaceSvgElement.setAttribute( 'stroke-linejoin', 'round' ); 1927 + } 1928 + return this.surfaceSvgElement; 1929 + }; 1930 + 1931 + Cone.prototype.setSurfaceRenderPoint = function( index, point ) { 1932 + var renderPoint = this.surfacePathCommands[ index ].renderPoints[0]; 1933 + renderPoint.set( point ); 1934 + }; 1935 + 1936 + return Cone; 1937 + 1938 + })); 1939 + /** 1940 + * Box composite shape 1941 + */ 1942 + 1943 + ( function( root, factory ) { 1944 + // module definition 1945 + if ( typeof module == 'object' && module.exports ) { 1946 + /* globals module, require */ // CommonJS 1947 + module.exports = factory( require('./boilerplate'), require('./anchor'), 1948 + require('./shape'), require('./rect') ); 1949 + } else { 1950 + // browser global 1951 + var Zdog = root.Zdog; 1952 + Zdog.Box = factory( Zdog, Zdog.Anchor, Zdog.Shape, Zdog.Rect ); 1953 + } 1954 + }( this, function factory( utils, Anchor, Shape, Rect ) { 1955 + 1956 + // ----- BoxRect ----- // 1957 + 1958 + var BoxRect = Rect.subclass(); 1959 + // prevent double-creation in parent.copyGraph() 1960 + // only create in Box.create() 1961 + BoxRect.prototype.copyGraph = function() {}; 1962 + 1963 + // ----- Box ----- // 1964 + 1965 + var boxDefaults = utils.extend( { 1966 + width: 1, 1967 + height: 1, 1968 + depth: 1, 1969 + frontFace: true, 1970 + rearFace: true, 1971 + leftFace: true, 1972 + rightFace: true, 1973 + topFace: true, 1974 + bottomFace: true, 1975 + }, Shape.defaults ); 1976 + // default fill 1977 + boxDefaults.fill = true; 1978 + delete boxDefaults.path; 1979 + 1980 + var Box = Anchor.subclass( boxDefaults ); 1981 + 1982 + var TAU = utils.TAU; 1983 + 1984 + Box.prototype.create = function( options ) { 1985 + Anchor.prototype.create.call( this, options ); 1986 + this.updatePath(); 1987 + }; 1988 + 1989 + Box.prototype.updatePath = function() { 1990 + this.setFace( 'frontFace', { 1991 + width: this.width, 1992 + height: this.height, 1993 + translate: { z: this.depth/2 }, 1994 + }); 1995 + this.setFace( 'rearFace', { 1996 + width: this.width, 1997 + height: this.height, 1998 + translate: { z: -this.depth/2 }, 1999 + rotate: { y: TAU/2 }, 2000 + }); 2001 + this.setFace( 'leftFace', { 2002 + width: this.depth, 2003 + height: this.height, 2004 + translate: { x: -this.width/2 }, 2005 + rotate: { y: -TAU/4 }, 2006 + }); 2007 + this.setFace( 'rightFace', { 2008 + width: this.depth, 2009 + height: this.height, 2010 + translate: { x: this.width/2 }, 2011 + rotate: { y: TAU/4 }, 2012 + }); 2013 + this.setFace( 'topFace', { 2014 + width: this.width, 2015 + height: this.depth, 2016 + translate: { y: -this.height/2 }, 2017 + rotate: { x: -TAU/4 }, 2018 + }); 2019 + this.setFace( 'bottomFace', { 2020 + width: this.width, 2021 + height: this.depth, 2022 + translate: { y: this.height/2 }, 2023 + rotate: { x: -TAU/4 }, 2024 + }); 2025 + }; 2026 + 2027 + Box.prototype.setFace = function( faceName, options ) { 2028 + var property = this[ faceName ]; 2029 + var rectProperty = faceName + 'Rect'; 2030 + var rect = this[ rectProperty ]; 2031 + // remove if false 2032 + if ( !property ) { 2033 + this.removeChild( rect ); 2034 + return; 2035 + } 2036 + // update & add face 2037 + utils.extend( options, { 2038 + // set color from option, i.e. `front: '#19F'` 2039 + color: typeof property == 'string' ? property : this.color, 2040 + stroke: this.stroke, 2041 + fill: this.fill, 2042 + backface: this.backface, 2043 + front: this.front, 2044 + visible: this.visible, 2045 + }); 2046 + if ( rect ) { 2047 + // update previous 2048 + rect.setOptions( options ); 2049 + } else { 2050 + // create new 2051 + rect = this[ rectProperty ] = new BoxRect( options ); 2052 + } 2053 + rect.updatePath(); 2054 + this.addChild( rect ); 2055 + }; 2056 + 2057 + return Box; 2058 + 2059 + })); 2060 + /** 2061 + * Index 2062 + */ 2063 + 2064 + ( function( root, factory ) { 2065 + // module definition 2066 + if ( typeof module == 'object' && module.exports ) { 2067 + /* globals module, require */ // CommonJS 2068 + module.exports = factory( 2069 + require('./boilerplate'), 2070 + require('./canvas-renderer'), 2071 + require('./svg-renderer'), 2072 + require('./vector'), 2073 + require('./anchor'), 2074 + require('./dragger'), 2075 + require('./illustration'), 2076 + require('./path-command'), 2077 + require('./shape'), 2078 + require('./group'), 2079 + require('./rect'), 2080 + require('./rounded-rect'), 2081 + require('./ellipse'), 2082 + require('./polygon'), 2083 + require('./hemisphere'), 2084 + require('./cylinder'), 2085 + require('./cone'), 2086 + require('./box') 2087 + ); 2088 + } else if ( typeof define == 'function' && define.amd ) { 2089 + /* globals define */ // AMD 2090 + define( 'zdog', [], root.Zdog ); 2091 + } 2092 + })( this, function factory( Zdog ) { 2093 + 2094 + return Zdog; 2095 + 2096 + });
+8
dist/zdog.dist.min.js
··· 1 + /*! 2 + * Zdog v1.0.0 3 + * Round, flat, designer-friendly pseudo-3D engine 4 + * Licensed MIT 5 + * https://zzz.dog 6 + * Copyright 2019 Metafizzy 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=i/.5;var o=s[e]||s[2];var n=o(i);n=n/2;return r?n:1-n};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.CanvasRenderer=e()}})(this,function t(){var o={isCanvas:true};o.begin=function(t){t.beginPath()};o.move=function(t,e,r){t.moveTo(r.x,r.y)};o.line=function(t,e,r){t.lineTo(r.x,r.y)};o.bezier=function(t,e,r,i,o){t.bezierCurveTo(r.x,r.y,i.x,i.y,o.x,o.y)};o.closePath=function(t){t.closePath()};o.setPath=function(){};o.renderPath=function(e,r,t,i){this.begin(e,r);t.forEach(function(t){t.render(e,r,o)});if(i){this.closePath(e,r)}};o.stroke=function(t,e,r,i,o){if(!r){return}t.strokeStyle=i;t.lineWidth=o;t.stroke()};o.fill=function(t,e,r,i){if(!r){return}t.fillStyle=i;t.fill()};o.end=function(){};return o});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.SvgRenderer=e()}})(this,function t(){var n={isSvg:true};var e=n.round=function(t){return Math.round(t*1e3)/1e3};function s(t){return e(t.x)+","+e(t.y)+" "}n.begin=function(){};n.move=function(t,e,r){return"M"+s(r)};n.line=function(t,e,r){return"L"+s(r)};n.bezier=function(t,e,r,i,o){return"C"+s(r)+s(i)+s(o)};n.closePath=function(){return"Z"};n.setPath=function(t,e,r){e.setAttribute("d",r)};n.renderPath=function(e,r,t,i){var o="";t.forEach(function(t){o+=t.render(e,r,n)});if(i){o+=this.closePath(e,r)}this.setPath(e,r,o)};n.stroke=function(t,e,r,i,o){if(!r){return}e.setAttribute("stroke",i);e.setAttribute("stroke-width",o)};n.fill=function(t,e,r,i){var o=r?i:"none";e.setAttribute("fill",o)};n.end=function(t,e){t.appendChild(e)};return n});(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 o=Math.cos(e);var n=Math.sin(e);var s=t[r];var a=t[i];t[r]=s*o-a*n;t[i]=a*o+s*n}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 o(t)};function o(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 o(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"))}else{var r=t.Zdog;r.Anchor=e(r,r.Vector)}})(this,function t(o,e){var r=o.TAU;var i={x:1,y:1,z:1};function n(t){this.create(t||{})}n.prototype.create=function(t){o.extend(this,this.constructor.defaults);this.setOptions(t);this.translate=new e(t.translate);this.rotate=new e(t.rotate);this.scale=new e(i).multiply(this.scale);this.origin=new e;this.renderOrigin=new e;this.children=[];if(this.addTo){this.addTo.addChild(this)}};n.defaults={};n.optionKeys=Object.keys(n.defaults).concat(["rotate","translate","scale","addTo"]);n.prototype.setOptions=function(t){var e=this.constructor.optionKeys;for(var r in t){if(e.includes(r)){this[r]=t[r]}}};n.prototype.addChild=function(t){var e=this.children.indexOf(t);if(e!=-1){return}t.remove();t.addTo=this;this.children.push(t)};n.prototype.removeChild=function(t){var e=this.children.indexOf(t);if(e!=-1){this.children.splice(e,1)}};n.prototype.remove=function(){if(this.addTo){this.addTo.removeChild(this)}};n.prototype.update=function(){this.reset();this.children.forEach(function(t){t.update()});this.transform(this.translate,this.rotate,this.scale)};n.prototype.reset=function(){this.renderOrigin.set(this.origin)};n.prototype.transform=function(e,r,i){this.renderOrigin.transform(e,r,i);this.children.forEach(function(t){t.transform(e,r,i)})};n.prototype.updateGraph=function(){this.update();this.checkFlatGraph();this.flatGraph.forEach(function(t){t.updateSortValue()});this.flatGraph.sort(n.shapeSorter)};n.shapeSorter=function(t,e){return t.sortValue-e.sortValue};n.prototype.checkFlatGraph=function(){if(!this.flatGraph){this.updateFlatGraph()}};n.prototype.updateFlatGraph=function(){this.flatGraph=this.getFlatGraph()};n.prototype.getFlatGraph=function(){var r=[this];this.children.forEach(function(t){var e=t.getFlatGraph();r=r.concat(e)});return r};n.prototype.updateSortValue=function(){this.sortValue=this.renderOrigin.z};n.prototype.render=function(){};n.prototype.renderGraphCanvas=function(e){if(!e){throw new Error("ctx is "+e+". "+"Canvas context required for render. Check .renderGraphCanvas( ctx ).")}this.checkFlatGraph();this.flatGraph.forEach(function(t){t.render(e,Zdog.CanvasRenderer)})};n.prototype.renderGraphSvg=function(e){if(!e){throw new Error("svg is "+e+". "+"SVG required for render. Check .renderGraphSvg( svg ).")}this.checkFlatGraph();this.flatGraph.forEach(function(t){t.render(e,Zdog.SvgRenderer)})};n.prototype.copy=function(t){var e={};var r=this.constructor.optionKeys;r.forEach(function(t){e[t]=this[t]},this);o.extend(e,t);var i=this.constructor;return new i(e)};n.prototype.copyGraph=function(t){var e=this.copy(t);this.children.forEach(function(t){t.copyGraph({addTo:e})});return e};n.prototype.normalizeRotate=function(){this.rotate.x=o.modulo(this.rotate.x,r);this.rotate.y=o.modulo(this.rotate.y,r);this.rotate.z=o.modulo(this.rotate.z,r)};function s(r){return function(t){function e(t){this.create(t||{})}e.prototype=Object.create(r.prototype);e.prototype.constructor=e;e.defaults=o.extend({},r.defaults);o.extend(e.defaults,t);e.optionKeys=r.optionKeys.slice(0);Object.keys(e.defaults).forEach(function(t){if(!e.optionKeys.includes(t)){e.optionKeys.push(t)}});e.subclass=s(e);return e}}n.subclass=s(n);return n});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.Dragger=e()}})(this,function t(){var e="mousedown";var r="mousemove";var i="mouseup";if(window.PointerEvent){e="pointerdown";r="pointermove";i="pointerup"}else if("ontouchstart"in window){e="touchstart";r="touchmove";i="touchend"}function o(){}function n(t){this.create(t||{})}n.prototype.create=function(t){this.onDragStart=t.onDragStart||o;this.onDragMove=t.onDragMove||o;this.onDragEnd=t.onDragEnd||o;this.bindDrag(t.startElement)};n.prototype.bindDrag=function(t){t=this.getQueryElement(t);if(t){t.addEventListener(e,this)}};n.prototype.getQueryElement=function(t){if(typeof t=="string"){t=document.querySelector(t)}return t};n.prototype.handleEvent=function(t){var e=this["on"+t.type];if(e){e.call(this,t)}};n.prototype.onmousedown=n.prototype.onpointerdown=function(t){this.dragStart(t,t)};n.prototype.ontouchstart=function(t){this.dragStart(t,t.changedTouches[0])};n.prototype.dragStart=function(t,e){t.preventDefault();this.dragStartX=e.pageX;this.dragStartY=e.pageY;window.addEventListener(r,this);window.addEventListener(i,this);this.onDragStart(e)};n.prototype.ontouchmove=function(t){this.dragMove(t,t.changedTouches[0])};n.prototype.onmousemove=n.prototype.onpointermove=function(t){this.dragMove(t,t)};n.prototype.dragMove=function(t,e){t.preventDefault();var r=e.pageX-this.dragStartX;var i=e.pageY-this.dragStartY;this.onDragMove(e,r,i)};n.prototype.onmouseup=n.prototype.onpointerup=n.prototype.ontouchend=n.prototype.dragEnd=function(){window.removeEventListener(r,this);window.removeEventListener(i,this);this.onDragEnd()};return n});(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 o=r.subclass({element:undefined,centered:true,zoom:1,dragRotate:false,resize:false,onPrerender:i,onDragStart:i,onDragMove:i,onDragEnd:i,onResize:i});e.extend(o.prototype,a.prototype);o.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)};o.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)}};o.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)}};o.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)}};o.prototype.onWindowResize=function(){this.setMeasuredSize();this.onResize(this.width,this.height)};o.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)};o.prototype.renderGraph=function(t){if(this.isCanvas){this.renderGraphCanvas(t)}else if(this.isSvg){this.renderGraphSvg(t)}};o.prototype.updateRenderGraph=function(t){this.updateGraph();this.renderGraph(t)};o.prototype.setCanvas=function(t){this.element=t;this.isCanvas=true;this.ctx=this.element.getContext("2d");this.setSizeCanvas(t.width,t.height)};o.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;if(r>1){this.element.style.width=t+"px";this.element.style.height=e+"px"}};o.prototype.renderGraphCanvas=function(t){t=t||this;this.prerenderCanvas();r.prototype.renderGraphCanvas.call(t,this.ctx);this.postrenderCanvas()};o.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){t.translate(this.width/2,this.height/2)}var e=this.pixelRatio*this.zoom;t.scale(e,e);this.onPrerender(t)};o.prototype.postrenderCanvas=function(){this.ctx.restore()};o.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)};o.prototype.setSizeSvg=function(t,e){this.width=t;this.height=e;var r=t/this.zoom;var i=e/this.zoom;var o=this.centered?-r/2:0;var n=this.centered?-i/2:0;this.element.setAttribute("viewBox",o+" "+n+" "+r+" "+i);if(this.resize){this.element.removeAttribute("width");this.element.removeAttribute("height")}else{this.element.setAttribute("width",t);this.element.setAttribute("height",e)}};o.prototype.renderGraphSvg=function(t){t=t||this;n(this.element);this.onPrerender(this.element);r.prototype.renderGraphSvg.call(t,this.element)};function n(t){while(t.firstChild){t.removeChild(t.firstChild)}}o.prototype.setDragRotate=function(t){if(!t){return}else if(t===true){t=this}this.dragRotate=t;this.bindDrag(this.element)};o.prototype.dragStart=function(){this.dragStartRX=this.dragRotate.rotate.x;this.dragStartRY=this.dragRotate.rotate.y;a.prototype.dragStart.apply(this,arguments)};o.prototype.dragMove=function(t,e){var r=e.pageX-this.dragStartX;var i=e.pageY-this.dragStartY;var o=Math.min(this.width,this.height);var n=r/o*h;var s=i/o*h;this.dragRotate.rotate.x=this.dragStartRX-s;this.dragRotate.rotate.y=this.dragStartRY-n;a.prototype.dragMove.apply(this,arguments)};return o});(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(o);this.renderPoints=e.map(n);this.previousPoint=r;this.endRenderPoint=this.renderPoints[this.renderPoints.length-1];if(t=="arc"){this.controlPoints=[new i,new i]}}function o(t){if(t instanceof i){return t}else{return new i(t)}}function n(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 o=this.renderPoints[1];var n=this.renderPoints[2];return r.bezier(t,e,i,o,n)};e.prototype.arc=function(t,e,r){var i=this.previousPoint;var o=this.renderPoints[0];var n=this.renderPoints[1];var s=this.controlPoints[0];var a=this.controlPoints[1];s.set(i).lerp(o,9/16);a.set(n).lerp(o,9/16);return r.bezier(t,e,s,a,n)};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 o=i.subclass({stroke:1,fill:false,color:"#333",closed:true,visible:true,path:[{}],front:{z:1},backface:true});o.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"];o.prototype.updatePath=function(){this.setPath();this.updatePathCommands()};o.prototype.setPath=function(){};o.prototype.updatePathCommands=function(){var u;this.pathCommands=this.path.map(function(t,e){var r=Object.keys(t);var i=r[0];var o=t[i];var n=r.length==1&&d.includes(i);if(!n){i="line";o=t}var s=i=="line"||i=="move";var a=Array.isArray(o);if(s&&!a){o=[o]}i=e===0?"move":i;var h=new p(i,o,u);u=h.endRenderPoint;return h})};o.prototype.reset=function(){this.renderOrigin.set(this.origin);this.renderFront.set(this.front);this.pathCommands.forEach(function(t){t.reset()})};o.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)})};o.prototype.updateSortValue=function(){var e=0;this.pathCommands.forEach(function(t){e+=t.endRenderPoint.z});this.sortValue=e/this.pathCommands.length};o.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 n=e.TAU;o.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,n);t.fill()};o.prototype.getLineWidth=function(){if(!this.stroke){return 0}if(this.stroke==true){return 1}return this.stroke};o.prototype.getRenderColor=function(){var t=typeof this.backface=="string"&&this.isFacingBack;var e=t?this.backface:this.color;return e};o.prototype.renderPath=function(t,e){var r=this.getRenderElement(t,e);var i=this.pathCommands.length==2&&this.pathCommands[1].method=="line";var o=!i&&this.closed;var n=this.getRenderColor();e.renderPath(t,r,this.pathCommands,o);e.stroke(t,r,this.stroke,n,this.getLineWidth());e.fill(t,r,this.fill,n);e.end(t,r)};var s="http://www.w3.org/2000/svg";o.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 o});(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.checkFlatGraph();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.checkFlatGraph();this.flatGraph.forEach(function(t){t.render(e,r)})};e.prototype.getFlatGraph=function(){return[this]};e.prototype.updateFlatGraph=function(){var r=[];this.children.forEach(function(t){var e=t.getFlatGraph();r=r.concat(e)});this.flatGraph=r};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(r){var e=r.subclass({width:1,height:1,cornerRadius:.25,closed:false});e.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 o=t-i;var n=e-i;var s=[{x:o,y:-e},{arc:[{x:t,y:-e},{x:t,y:-n}]}];if(n){s.push({x:t,y:n})}s.push({arc:[{x:t,y:e},{x:o,y:e}]});if(o){s.push({x:-o,y:e})}s.push({arc:[{x:-t,y:e},{x:-t,y:n}]});if(n){s.push({x:-t,y:-n})}s.push({arc:[{x:-t,y:-e},{x:-o,y:-e}]});if(o){s.push({x:o,y:-e})}this.path=s};e.prototype.updateSortValue=function(){r.prototype.updateSortValue.apply(this,arguments);var t=this.pathCommands.length;var e=this.pathCommands[t-1].endRenderPoint;this.sortValue-=e.z/t};return e});(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(r){var e=r.subclass({diameter:1,width:undefined,height:undefined,quarters:4,closed:false});e.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}]})}};e.prototype.updateSortValue=function(){r.prototype.updateSortValue.apply(this,arguments);if(this.quarters!=4){return}var t=this.pathCommands.length;var e=this.pathCommands[t-1].endRenderPoint;this.sortValue-=e.z/t};return e});(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 o=e.TAU;i.prototype.setPath=function(){this.path=[];for(var t=0;t<this.sides;t++){var e=t/this.sides*o-o/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("./ellipse"))}else{var r=t.Zdog;r.Hemisphere=e(r,r.Ellipse)}})(this,function t(e,r){var i=r.subclass({fill:true});var u=e.TAU;i.prototype.render=function(t,e){this.renderDome(t,e);r.prototype.render.apply(this,arguments)};i.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 o=this.diameter/2*this.renderNormal.magnitude();var n=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(n,s,o,a,h)}else if(e.isSvg){i=(i-u/4)/u*360;this.domeSvgElement.setAttribute("d","M "+-o+",0 A "+o+","+o+" 0 0 1 "+o+",0");this.domeSvgElement.setAttribute("transform","translate("+n+","+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 o="http://www.w3.org/2000/svg";i.prototype.getDomeRenderElement=function(t,e){if(!e.isSvg){return}if(!this.domeSvgElement){this.domeSvgElement=document.createElementNS(o,"path");this.domeSvgElement.setAttribute("stroke-linecap","round");this.domeSvgElement.setAttribute("stroke-linejoin","round")}return this.domeSvgElement};return i});(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,o,n){function s(){}var a=o.subclass({color:"#333",updateSort:true});a.prototype.create=function(){o.prototype.create.apply(this,arguments);this.pathCommands=[new r("move",[{}]),new r("line",[{}])]};a.prototype.render=function(t,e){this.renderCylinderSurface(t,e);o.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 o=this.rearBase;var n=i.renderNormal.magnitude();var s=i.diameter*n+i.getLineWidth();this.pathCommands[0].renderPoints[0].set(i.renderOrigin);this.pathCommands[1].renderPoints[0].set(o.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=n.subclass();u.prototype.copyGraph=s;var p=i.subclass({diameter:1,length:1,frontBaseColor:undefined,rearBaseColor: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 n({addTo:this.group,diameter:this.diameter,translate:{z:t},rotate:{y:d/2},color:this.color,stroke:this.stroke,fill:this.fill,backface:this.frontBaseColor||e,visible:this.visible});this.rearBase=this.group.rearBase=this.frontBase.copy({translate:{z:-t},rotate:{y:0},backface:this.rearBaseColor||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,o,n){var s=n.subclass({length:1,fill:true});var v=e.TAU;s.prototype.create=function(){n.prototype.create.apply(this,arguments);this.apex=new o({addTo:this,translate:{z:this.length}});this.renderApex=new r;this.tangentA=new r;this.tangentB=new r;this.surfacePathCommands=[new i("move",[{}]),new i("line",[{}]),new i("line",[{}])]};s.prototype.render=function(t,e){this.renderConeSurface(t,e);n.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 o=this.renderNormal.magnitude2d();var n=Math.acos(o/r);var s=Math.sin(n);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 l=this.tangentB;c.x=Math.cos(d)*a*s;c.y=Math.sin(d)*a;l.set(this.tangentA);l.y*=-1;c.rotateZ(u);l.rotateZ(u);c.add(this.renderOrigin);l.add(this.renderOrigin);this.setSurfaceRenderPoint(0,c);this.setSurfaceRenderPoint(1,this.apex.renderOrigin);this.setSurfaceRenderPoint(2,l);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("./anchor"),require("./shape"),require("./rect"))}else{var r=t.Zdog;r.Box=e(r,r.Anchor,r.Shape,r.Rect)}})(this,function t(n,e,r,i){var s=i.subclass();s.prototype.copyGraph=function(){};var o=n.extend({width:1,height:1,depth:1,frontFace:true,rearFace:true,leftFace:true,rightFace:true,topFace:true,bottomFace:true},r.defaults);o.fill=true;delete o.path;var a=e.subclass(o);var h=n.TAU;a.prototype.create=function(t){e.prototype.create.call(this,t);this.updatePath()};a.prototype.updatePath=function(){this.setFace("frontFace",{width:this.width,height:this.height,translate:{z:this.depth/2}});this.setFace("rearFace",{width:this.width,height:this.height,translate:{z:-this.depth/2},rotate:{y:h/2}});this.setFace("leftFace",{width:this.depth,height:this.height,translate:{x:-this.width/2},rotate:{y:-h/4}});this.setFace("rightFace",{width:this.depth,height:this.height,translate:{x:this.width/2},rotate:{y:h/4}});this.setFace("topFace",{width:this.width,height:this.depth,translate:{y:-this.height/2},rotate:{x:-h/4}});this.setFace("bottomFace",{width:this.width,height:this.depth,translate:{y:this.height/2},rotate:{x:-h/4}})};a.prototype.setFace=function(t,e){var r=this[t];var i=t+"Rect";var o=this[i];if(!r){this.removeChild(o);return}n.extend(e,{color:typeof r=="string"?r:this.color,stroke:this.stroke,fill:this.fill,backface:this.backface,front:this.front,visible:this.visible});if(o){o.setOptions(e)}else{o=this[i]=new s(e)}o.updatePath();this.addChild(o)};return a});(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){return e});
+1 -1
js/boilerplate.js
··· 1 1 /*! 2 - * Zdog v0.1.0 2 + * Zdog v1.0.0 3 3 * Round, flat, designer-friendly pseudo-3D engine 4 4 * Licensed MIT 5 5 * https://zzz.dog
+2
js/index.js
··· 12 12 require('./svg-renderer'), 13 13 require('./vector'), 14 14 require('./anchor'), 15 + require('./dragger'), 16 + require('./illustration'), 15 17 require('./path-command'), 16 18 require('./shape'), 17 19 require('./group'),
+1 -1
package-lock.json
··· 1 1 { 2 2 "name": "zdog", 3 - "version": "0.1.0", 3 + "version": "1.0.0", 4 4 "lockfileVersion": 1, 5 5 "requires": true, 6 6 "dependencies": {
+1 -1
package.json
··· 1 1 { 2 2 "name": "zdog", 3 - "version": "0.1.0", 3 + "version": "1.0.0", 4 4 "description": "Round, flat, designer-friendly pseudo-3D engine", 5 5 "main": "js/index.js", 6 6 "dependencies": {},