├── .eslintrc.js ├── .github ├── contributing.md └── issue_template.md ├── .gitignore ├── README.md ├── bower.json ├── demos ├── .eslintrc.js ├── box-cross │ ├── box-cross.js │ └── index.html ├── dot-cube │ ├── dot-cube.js │ └── index.html ├── fullscreen │ ├── fullscreen.js │ └── index.html ├── hello-world-canvas │ ├── hello-world-canvas.js │ └── index.html ├── hello-world-svg │ ├── hello-world-svg.js │ └── index.html ├── hemisphere-cone-ball │ ├── hemisphere-cone-ball.js │ └── index.html ├── houses │ ├── houses.js │ └── index.html ├── kid-kit │ ├── index.html │ └── kid-kit.js ├── kirby-parasol │ ├── index.html │ └── kirby-parasol.js ├── no-illo-canvas │ ├── index.html │ └── no-illo-canvas.js ├── no-illo-svg │ ├── index.html │ └── no-illo-svg.js ├── path-commands │ ├── index.html │ └── path-commands.js ├── resize │ ├── index.html │ └── resize.js ├── shade-and-shades │ ├── index.html │ └── shade-and-shades.js ├── shapes │ ├── index.html │ └── shapes.js ├── solids │ ├── index.html │ └── solids.js ├── strutter │ ├── index.html │ └── strutter.js └── zdog-logo │ ├── index.html │ └── zdog-logo.js ├── dist ├── zdog.dist.js └── zdog.dist.min.js ├── js ├── anchor.js ├── boilerplate.js ├── box.js ├── canvas-renderer.js ├── cone.js ├── cylinder.js ├── dragger.js ├── ellipse.js ├── group.js ├── hemisphere.js ├── illustration.js ├── index.js ├── path-command.js ├── polygon.js ├── rect.js ├── rounded-rect.js ├── shape.js ├── svg-renderer.js └── vector.js ├── package-lock.json ├── package.json └── tasks ├── .eslintrc.js ├── bundle.js └── version.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | plugins: [ 'metafizzy' ], 5 | extends: 'plugin:metafizzy/browser', 6 | env: { 7 | browser: true, 8 | commonjs: true, 9 | }, 10 | parserOptions: { 11 | ecmaVersion: 5, 12 | }, 13 | rules: { 14 | 'no-var': 'off', 15 | 'max-params': [ 'error', { 16 | max: 5, 17 | } ], 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | ## Feature requests 2 | 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 | 5 | ## Reduced test cases required 6 | 7 | All bug reports and problem issues require a [**reduced test case**](https://css-tricks.com/reduced-test-cases/). Create one by forking this any one of the [Zdog demos on CodePen](https://codepen.io/desandro/pens/tags/?grid_type=list&selected_tag=zdog-v1-docs&sort_col=created_at&sort_order=asc). 8 | 9 | **CodePen** 10 | 11 | + [Hello world canvas](https://codepen.io/desandro/pen/YbrLaO) 12 | + [Hello world SVG](https://codepen.io/desandro/pen/Bewxme) 13 | + [resize fullscreen](https://codepen.io/desandro/pen/dEJxaV) 14 | + [Strutter](https://codepen.io/desandro/pen/xNPaoP) 15 | 16 | **Test cases** 17 | 18 | + A reduced test case clearly demonstrates the bug or issue. 19 | + It contains the bare minimum HTML, CSS, and JavaScript required to demonstrate the bug. 20 | + A link to your production site is **not** a reduced test case. 21 | 22 | Providing a reduced test case is the best way to get your issue addressed. They help you point out the problem. They help me verify and debug the problem. They help others understand the problem. Without a reduced test case, your issue may be closed. 23 | 24 | ## Pull requests 25 | 26 | Contributions are welcome! 27 | 28 | + **For typos and one-line fixes,** send those right in. 29 | + **For larger features,** open an issue before starting any significant work. Let's discuss to see how your feature fits within Zdog's vision. 30 | + **Follow the code style.** Spaces in brackets, semicolons, trailing commas. 31 | + **Do not edit `dist/` files.** Make your edits to source files in `js/` and `css/`. 32 | + **Do not run `make` to update `dist/` files.** I'll take care of this when I create a new release. 33 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Test case:** https://codepen.io/desandro/pen/YbrLaO 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | bower_components/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zdog 2 | 3 | _Round, flat, designer-friendly pseudo-3D engine_ 4 | 5 | View complete documentation and live demos at [zzz.dog](https://zzz.dog). 6 | 7 | ## Install 8 | 9 | ### Download 10 | 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 | 14 | ### CDN 15 | 16 | Link directly to Zdog JS on [unpkg](https://unpkg.com). 17 | 18 | ``` html 19 | 20 | ``` 21 | 22 | ### Package managers 23 | 24 | npm: `npm install zdog` 25 | 26 | Bower: `bower install zdog` 27 | 28 | ## Hello world demo 29 | 30 | Create 3D models with Zdog by adding shapes. See [Getting started](https://zzz.dog/getting-started) for a walk-through of this demo. 31 | 32 | ``` js 33 | let isSpinning = true; 34 | 35 | let illo = new Zdog.Illustration({ 36 | element: '.zdog-canvas', 37 | zoom: 4, 38 | dragRotate: true, 39 | // stop spinning when drag starts 40 | onDragStart: function() { 41 | isSpinning = false; 42 | }, 43 | }); 44 | 45 | // circle 46 | new Zdog.Ellipse({ 47 | addTo: illo, 48 | diameter: 20, 49 | translate: { z: 10 }, 50 | stroke: 5, 51 | color: '#636', 52 | }); 53 | 54 | // square 55 | new Zdog.Rect({ 56 | addTo: illo, 57 | width: 20, 58 | height: 20, 59 | translate: { z: -10 }, 60 | stroke: 3, 61 | color: '#E62', 62 | fill: true, 63 | }); 64 | 65 | function animate() { 66 | illo.rotate.y += isSpinning ? 0.03 : 0; 67 | illo.updateRenderGraph(); 68 | requestAnimationFrame( animate ); 69 | } 70 | animate(); 71 | ``` 72 | 73 | ## About Zdog 74 | 75 | Hi, [Dave here](https://desandro.com). I wanted to make a video game. I needed a 3D engine, but most engines were too powerful and complex for me. I made Zdog so I could design and display simple 3D models without a lot of overhead. 76 | 77 | Zdog is directly inspired by [Dogz](https://en.wikipedia.org/wiki/Petz), a virtual pet game by P.F. Magic released in 1995. It used flat 2D circle sprites to render the Dogz’ models, but in a 3D scene. [See Dogz playthrough video here.](https://www.youtube.com/watch?v=6lKSn_cHw5k) Dogz were fully animated in real time, running, flopping, scratching (on Windows 3.1!). It was remarkable. 78 | 79 | Zdog uses the same principle. It renders all shapes using the 2D drawing APIs in either `` or ``. Spheres are actually dots. Toruses are actually circles. Capsules are actually thick lines. It’s a simple, but effective trick. The underlying 3D math comes from [Rotating 3D Shapes](https://www.khanacademy.org/computing/computer-programming/programming-games-visualizations/programming-3d-shapes/a/rotating-3d-shapes) by [Peter Collingridge](https://petercollingridge.appspot.com/3D-tutorial/rotating-objects). 80 | 81 | Zdog is pronounced "Zee-dog" in American parlance or "Zed-dog" in British. 82 | 83 | ### Beta! 84 | 85 | Zdog v1 is a beta-release, of sorts. This is my first time creating a 3D engine, so I probably got some stuff wrong. Expect lots of changes for v2. Provide input and select new features on the [Zdog issue tracker on GitHub](https://github.com/metafizzy/zdog/issues). 86 | 87 | ### More Zdog resources 88 | 89 | Other people's stuff: 90 | 91 | + [Zfont](https://jaames.github.io/zfont/) - Text plugin for Zdog 92 | + [vue-zdog](https://github.com/AlexandreBonaventure/vue-zdog) - Vue wrapper for Zdog 93 | + [zDogPy](https://github.com/gferreira/zdogpy) - Python port of Zdog for DrawBot 94 | + [Made with Zdog CodePen Collection](https://codepen.io/collection/DzdGMe/) 95 | + [Made with Zdog on Twitter](https://twitter.com/i/moments/1135000612356206592) 96 | 97 | My stuff: 98 | 99 | + [Zdog demos on CodePen](https://github.com/metafizzy/zdog-demos), source code at [zdog-demos](https://github.com/metafizzy/zdog-demos) 100 | + [zdog-docs](https://github.com/metafizzy/zdog-docs) - Docs site source code 101 | 102 | --- 103 | 104 | Licensed MIT. Made by Metafizzy 🌈🐻 105 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zdog", 3 | "description": "Round, flat, designer-friendly pseudo-3D engine", 4 | "main": "js/index.js", 5 | "authors": [ 6 | "David DeSandro" 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "3D", 11 | "canvas", 12 | "svg" 13 | ], 14 | "homepage": "https://zzz.dog", 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "test", 20 | "tests", 21 | "package.json", 22 | "demo", 23 | "demos" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /demos/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | extends: '../.eslintrc.js', 5 | globals: { 6 | Zdog: 'readonly', 7 | }, 8 | rules: { 9 | 'key-spacing': 'off', 10 | 'max-lines': 'off', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /demos/box-cross/box-cross.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var sceneSize = 9; 4 | var isSpinning = true; 5 | var TAU = Zdog.TAU; 6 | // colors 7 | var yellow = '#ED0'; 8 | var gold = '#EA0'; 9 | var orange = '#E62'; 10 | var garnet = '#C25'; 11 | var eggplant = '#636'; 12 | 13 | var initRotate = { x: ( 35/360 ) * TAU, y: TAU/8 }; 14 | 15 | var illo = new Zdog.Illustration({ 16 | element: '.illo', 17 | rotate: initRotate, 18 | resize: 'fullscreen', 19 | dragRotate: true, 20 | onDragStart: function() { 21 | isSpinning = false; 22 | }, 23 | onResize: function( width, height ) { 24 | this.zoom = Math.floor( Math.min( width, height ) / sceneSize ); 25 | }, 26 | }); 27 | 28 | // ----- model ----- // 29 | 30 | var model = new Zdog.Anchor({ 31 | addTo: illo, 32 | }); 33 | 34 | function addBox( options ) { 35 | var boxOptions = { 36 | addTo: model, 37 | stroke: false, 38 | topFace: yellow, 39 | rearFace: gold, 40 | leftFace: orange, 41 | rightFace: orange, 42 | frontFace: garnet, 43 | bottomFace: eggplant, 44 | }; 45 | Zdog.extend( boxOptions, options ); 46 | 47 | new Zdog.Box( boxOptions ); 48 | } 49 | 50 | // top 51 | addBox({ 52 | bottomFace: false, 53 | translate: { y: -1 }, 54 | }); 55 | // bottom 56 | addBox({ 57 | topFace: false, 58 | translate: { y: 1 }, 59 | }); 60 | // front 61 | addBox({ 62 | rearFace: false, 63 | translate: { z: 1 }, 64 | }); 65 | // back 66 | addBox({ 67 | frontFace: false, 68 | translate: { z: -1 }, 69 | }); 70 | // left 71 | addBox({ 72 | rightFace: false, 73 | translate: { x: -1 }, 74 | }); 75 | // right 76 | addBox({ 77 | leftFace: false, 78 | translate: { x: 1 }, 79 | }); 80 | 81 | var dot = new Zdog.Shape({ 82 | addTo: model, 83 | translate: { y: -2 }, 84 | stroke: 1, 85 | color: gold, 86 | }); 87 | dot.copy({ 88 | translate: { y: 2 }, 89 | color: gold, 90 | }); 91 | dot.copy({ 92 | translate: { x: -2 }, 93 | color: yellow, 94 | }); 95 | dot.copy({ 96 | translate: { x: 2 }, 97 | color: garnet, 98 | }); 99 | dot.copy({ 100 | translate: { z: -2 }, 101 | color: orange, 102 | }); 103 | dot.copy({ 104 | translate: { z: 2 }, 105 | color: eggplant, 106 | }); 107 | 108 | // ----- animate ----- // 109 | 110 | var ticker = 0; 111 | var cycleCount = 150; 112 | 113 | function animate() { 114 | spin(); 115 | illo.updateRenderGraph(); 116 | requestAnimationFrame( animate ); 117 | } 118 | 119 | function spin() { 120 | if ( !isSpinning ) { 121 | return; 122 | } 123 | var progress = ticker/cycleCount; 124 | var turn = Math.floor( progress % 4 ); 125 | var theta = Zdog.easeInOut( progress % 1, 3 ) * TAU; 126 | if ( turn == 0 || turn == 2 ) { 127 | model.rotate.y = theta; 128 | } else if ( turn == 1 ) { 129 | model.rotate.x = theta; 130 | } else if ( turn == 3 ) { 131 | model.rotate.z = theta; 132 | } 133 | ticker++; 134 | } 135 | 136 | animate(); 137 | 138 | -------------------------------------------------------------------------------- /demos/box-cross/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | box cross 8 | 9 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /demos/dot-cube/dot-cube.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var sceneSize = 24; 4 | var TAU = Zdog.TAU; 5 | 6 | var illo = new Zdog.Illustration({ 7 | element: '.illo', 8 | rotate: { x: TAU * -35/360, y: TAU * 1/8 }, 9 | dragRotate: true, 10 | resize: 'fullscreen', 11 | onResize: function( width, height ) { 12 | this.zoom = Math.floor( Math.min( width, height ) / sceneSize ); 13 | }, 14 | }); 15 | 16 | // ----- model ----- // 17 | 18 | var cube = new Zdog.Anchor({ 19 | addTo: illo, 20 | scale: 4, 21 | }); 22 | 23 | var oneUnit = new Zdog.Vector({ x: 1, y: 1 }); 24 | 25 | var side = new Zdog.Anchor({ 26 | addTo: cube, 27 | translate: { z: 1 }, 28 | }); 29 | 30 | var dot = new Zdog.Shape({ 31 | addTo: side, 32 | translate: oneUnit.copy(), 33 | stroke: 1, 34 | color: 'white', 35 | }); 36 | 37 | dot.copy({ translate: { x: -1, y: 1 } }); 38 | dot.copy({ translate: { x: 1, y: -1 } }); 39 | dot.copy({ translate: { x: -1, y: -1 } }); 40 | 41 | // more dots 42 | dot.copy({ translate: { x: 1 } }); 43 | dot.copy({ translate: { x: -1 } }); 44 | dot.copy({ translate: { y: -1 } }); 45 | dot.copy({ translate: { y: 1 } }); 46 | 47 | side.copyGraph({ 48 | translate: { z: -1 }, 49 | }); 50 | 51 | var midDot = dot.copy({ 52 | addTo: cube, 53 | }); 54 | 55 | midDot.copy({ translate: { x: -1, y: 1 } }); 56 | midDot.copy({ translate: { x: 1, y: -1 } }); 57 | midDot.copy({ translate: { x: -1, y: -1 } }); 58 | 59 | // ----- animate ----- // 60 | 61 | var keyframes = [ 62 | { x: 0, y: 0, z: 0 }, 63 | { x: 0, y: 0, z: TAU/4 }, 64 | { x: -TAU/4, y: 0, z: TAU/4 }, 65 | { x: -TAU/4, y: 0, z: TAU/2 }, 66 | ]; 67 | 68 | var ticker = 0; 69 | var cycleCount = 75; 70 | var turnLimit = keyframes.length - 1; 71 | 72 | function animate() { 73 | var progress = ticker/cycleCount; 74 | var tween = Zdog.easeInOut( progress % 1, 4 ); 75 | var turn = Math.floor( progress % turnLimit ); 76 | var keyA = keyframes[ turn ]; 77 | var keyB = keyframes[ turn + 1 ]; 78 | cube.rotate.x = Zdog.lerp( keyA.x, keyB.x, tween ); 79 | cube.rotate.y = Zdog.lerp( keyA.y, keyB.y, tween ); 80 | cube.rotate.z = Zdog.lerp( keyA.z, keyB.z, tween ); 81 | ticker++; 82 | 83 | illo.updateRenderGraph(); 84 | requestAnimationFrame( animate ); 85 | } 86 | 87 | animate(); 88 | 89 | -------------------------------------------------------------------------------- /demos/dot-cube/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | dot cube 8 | 9 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /demos/fullscreen/fullscreen.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var isSpinning = true; 4 | var gold = '#EA0'; 5 | var orange = '#E62'; 6 | var garnet = '#C25'; 7 | var eggplant = '#636'; 8 | 9 | var illo = new Zdog.Illustration({ 10 | element: '.illo', 11 | zoom: 4, 12 | resize: 'fullscreen', 13 | dragRotate: true, 14 | onDragStart: function() { 15 | isSpinning = false; 16 | }, 17 | onResize: function( width, height ) { 18 | this.zoom = Math.min( width, height ) / 50; 19 | }, 20 | }); 21 | 22 | // ----- model ----- // 23 | 24 | new Zdog.Rect({ 25 | width: 20, 26 | height: 20, 27 | addTo: illo, 28 | translate: { z: -10 }, 29 | stroke: 2, 30 | color: garnet, 31 | }); 32 | 33 | new Zdog.Ellipse({ 34 | diameter: 16, 35 | addTo: illo, 36 | translate: { z: 10 }, 37 | stroke: 4, 38 | color: eggplant, 39 | }); 40 | 41 | new Zdog.Shape({ 42 | path: [ 43 | { x: 0, z: 1 }, 44 | { x: -1, z: -1 }, 45 | { x: 1, z: -1 }, 46 | ], 47 | scale: { x: 5, z: 5 }, 48 | addTo: illo, 49 | stroke: 2, 50 | fill: true, 51 | color: gold, 52 | }); 53 | 54 | new Zdog.Shape({ 55 | translate: { x: 10, y: -5 }, 56 | addTo: illo, 57 | stroke: 7, 58 | color: orange, 59 | }); 60 | 61 | // ----- animate ----- // 62 | 63 | function animate() { 64 | illo.rotate.y += isSpinning ? 0.03 : 0; 65 | illo.updateRenderGraph(); 66 | requestAnimationFrame( animate ); 67 | } 68 | 69 | animate(); 70 | 71 | -------------------------------------------------------------------------------- /demos/fullscreen/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | fullscreen 8 | 9 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /demos/hello-world-canvas/hello-world-canvas.js: -------------------------------------------------------------------------------- 1 | // ----- variables ----- // 2 | 3 | var isSpinning = true; 4 | 5 | // ----- model ----- // 6 | 7 | var illo = new Zdog.Illustration({ 8 | element: '.illo', 9 | zoom: 5, 10 | dragRotate: true, 11 | onDragStart: function() { 12 | isSpinning = false; 13 | }, 14 | }); 15 | 16 | // circle 17 | new Zdog.Ellipse({ 18 | addTo: illo, 19 | diameter: 20, 20 | translate: { z: 10 }, 21 | stroke: 5, 22 | color: '#636', 23 | }); 24 | 25 | // square 26 | new Zdog.Rect({ 27 | addTo: illo, 28 | width: 20, 29 | height: 20, 30 | translate: { z: -10 }, 31 | stroke: 3, 32 | color: '#E62', 33 | fill: true, 34 | }); 35 | 36 | // ----- animate ----- // 37 | 38 | function animate() { 39 | illo.rotate.y += isSpinning ? 0.03 : 0; 40 | illo.updateRenderGraph(); 41 | requestAnimationFrame( animate ); 42 | } 43 | 44 | animate(); 45 | -------------------------------------------------------------------------------- /demos/hello-world-canvas/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello world canvas 8 | 9 | 29 | 30 | 31 | 32 | 33 |
34 |

Hello world canvas

35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /demos/hello-world-svg/hello-world-svg.js: -------------------------------------------------------------------------------- 1 | // ----- variables ----- // 2 | 3 | var isSpinning = true; 4 | 5 | // ----- model ----- // 6 | 7 | var illo = new Zdog.Illustration({ 8 | element: '.illo', 9 | zoom: 5, 10 | dragRotate: true, 11 | onDragStart: function() { 12 | isSpinning = false; 13 | }, 14 | }); 15 | 16 | // circle 17 | new Zdog.Ellipse({ 18 | addTo: illo, 19 | diameter: 20, 20 | translate: { z: 10 }, 21 | stroke: 5, 22 | color: '#636', 23 | }); 24 | 25 | // square 26 | new Zdog.Rect({ 27 | addTo: illo, 28 | width: 20, 29 | height: 20, 30 | translate: { z: -10 }, 31 | stroke: 3, 32 | color: '#E62', 33 | fill: true, 34 | }); 35 | 36 | // ----- animate ----- // 37 | 38 | function animate() { 39 | illo.rotate.y += isSpinning ? 0.03 : 0; 40 | illo.updateRenderGraph(); 41 | requestAnimationFrame( animate ); 42 | } 43 | 44 | animate(); 45 | -------------------------------------------------------------------------------- /demos/hello-world-svg/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello world SVG 8 | 9 | 29 | 30 | 31 | 32 | 33 |
34 |

Hello world SVG

35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /demos/hemisphere-cone-ball/hemisphere-cone-ball.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var sceneSize = 48; 4 | var isSpinning = true; 5 | var TAU = Zdog.TAU; 6 | 7 | var illo = new Zdog.Illustration({ 8 | element: '.illo', 9 | dragRotate: true, 10 | resize: 'fullscreen', 11 | onDragStart: function() { 12 | isSpinning = false; 13 | }, 14 | onResize: function( width, height ) { 15 | this.zoom = Math.floor( Math.min( width, height ) / sceneSize ); 16 | }, 17 | }); 18 | 19 | // colors 20 | var yellow = '#ED0'; 21 | var gold = '#EA0'; 22 | var orange = '#E62'; 23 | var garnet = '#C25'; 24 | var eggplant = '#636'; 25 | 26 | // ----- model ----- // 27 | 28 | var hemi = new Zdog.Hemisphere({ 29 | addTo: illo, 30 | diameter: 13, 31 | translate: { y: -16 }, 32 | rotate: { x: -TAU/4 }, 33 | color: garnet, 34 | backface: eggplant, 35 | stroke: false, 36 | }); 37 | var cone = new Zdog.Cone({ 38 | addTo: illo, 39 | diameter: 13, 40 | length: 6.5, 41 | translate: { y: 16 }, 42 | rotate: { x: TAU/4 }, 43 | color: garnet, 44 | backface: eggplant, 45 | stroke: false, 46 | }); 47 | 48 | var colorWheel = [ eggplant, garnet, orange, gold, yellow ]; 49 | 50 | [ true, false ].forEach( function( isHemi ) { 51 | var shape = isHemi ? hemi : cone; 52 | 53 | for ( var i = 0; i < 5; i++ ) { 54 | var rotor1 = new Zdog.Anchor({ 55 | addTo: illo, 56 | rotate: { y: TAU/5 * i }, 57 | }); 58 | var rotor2 = new Zdog.Anchor({ 59 | addTo: rotor1, 60 | rotate: { x: TAU/6 }, 61 | }); 62 | 63 | shape.copy({ 64 | addTo: rotor2, 65 | color: colorWheel[i], 66 | backface: colorWheel[ ( i + 7 ) % 5 ], 67 | }); 68 | } 69 | } ); 70 | 71 | // ----- animate ----- // 72 | 73 | var keyframes = [ 74 | { x: TAU * 0, y: TAU * 0 }, 75 | { x: TAU/2, y: TAU/2 }, 76 | { x: TAU * 1, y: TAU * 1 }, 77 | ]; 78 | 79 | var ticker = 0; 80 | var cycleCount = 180; 81 | var turnLimit = keyframes.length - 1; 82 | 83 | function animate() { 84 | spin(); 85 | illo.updateRenderGraph(); 86 | requestAnimationFrame( animate ); 87 | } 88 | 89 | function spin() { 90 | if ( !isSpinning ) { 91 | return; 92 | } 93 | var progress = ticker/cycleCount; 94 | var tween = Zdog.easeInOut( progress % 1, 3 ); 95 | var turn = Math.floor( progress % turnLimit ); 96 | var keyA = keyframes[ turn ]; 97 | var keyB = keyframes[ turn + 1 ]; 98 | var thetaX = Zdog.lerp( keyA.x, keyB.x, tween ); 99 | illo.rotate.x = Math.cos( thetaX ) * TAU/12; 100 | illo.rotate.y = Zdog.lerp( keyA.y, keyB.y, tween ); 101 | ticker++; 102 | } 103 | 104 | animate(); 105 | -------------------------------------------------------------------------------- /demos/hemisphere-cone-ball/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | hemisphere-cone-ball 8 | 9 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /demos/houses/houses.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var sceneSize = 56; 4 | var isSpinning = true; 5 | var TAU = Zdog.TAU; 6 | 7 | var offWhite = '#FED'; 8 | var yellow = '#ED0'; 9 | var gold = '#EA0'; 10 | var orange = '#E62'; 11 | var garnet = '#C25'; 12 | var eggplant = '#636'; 13 | 14 | // enable fill, disable stroke for all defaults 15 | [ Zdog.Rect, Zdog.Shape, Zdog.Ellipse ].forEach( function( Item ) { 16 | Item.defaults.fill = true; 17 | Item.defaults.stroke = false; 18 | } ); 19 | 20 | var initRotate = { y: TAU/8 }; 21 | var turnRatio = 1 / Math.sin( TAU/8 ); 22 | 23 | var illo = new Zdog.Illustration({ 24 | element: '.illo', 25 | rotate: initRotate, 26 | // stretch looks circular at 1/8 turn 27 | scale: { x: turnRatio, z: turnRatio }, 28 | dragRotate: true, 29 | resize: 'fullscreen', 30 | onDragStart: function() { 31 | isSpinning = false; 32 | }, 33 | onResize: function( width, height ) { 34 | this.zoom = Math.floor( Math.min( width, height ) / sceneSize ); 35 | }, 36 | }); 37 | 38 | // ----- model ----- // 39 | 40 | var house = new Zdog.Anchor({ 41 | addTo: illo, 42 | translate: { x: -2, y: 2, z: 8 }, 43 | }); 44 | 45 | var frontGroup = new Zdog.Group({ 46 | addTo: house, 47 | translate: { z: 5 }, 48 | }); 49 | // front wall 50 | new Zdog.Rect({ 51 | addTo: frontGroup, 52 | width: 14, 53 | height: 14, 54 | color: garnet, 55 | }); 56 | 57 | var frontWindow = new Zdog.Rect({ 58 | addTo: frontGroup, 59 | width: 2, 60 | height: 4, 61 | translate: { x: -4, y: -3 }, 62 | color: eggplant, 63 | }); 64 | frontWindow.copy({ 65 | translate: { y: -3 }, 66 | }); 67 | frontWindow.copy({ 68 | translate: { x: 4, y: -3 }, 69 | }); 70 | frontWindow.copy({ 71 | translate: { x: -4, y: 3 }, 72 | }); 73 | // door 74 | new Zdog.Shape({ 75 | addTo: frontGroup, 76 | path: [ 77 | { x: -2, y: 3 }, 78 | { x: -2, y: -1 }, 79 | { arc: [ 80 | { x: -2, y: -3 }, 81 | { x: 0, y: -3 }, 82 | ] }, 83 | { arc: [ 84 | { x: 2, y: -3 }, 85 | { x: 2, y: -1 }, 86 | ] }, 87 | { x: 2, y: 3 }, 88 | ], 89 | translate: { x: 2, y: 4 }, 90 | color: eggplant, 91 | }); 92 | 93 | // backWall 94 | var backGroup = frontGroup.copyGraph({ 95 | translate: { z: -5 }, 96 | rotate: { y: TAU/2 }, 97 | }); 98 | 99 | backGroup.children.forEach( function( child, i ) { 100 | // orange windows, yellow wall 101 | child.color = i ? orange : yellow; 102 | } ); 103 | 104 | var rightGroup = new Zdog.Group({ 105 | addTo: house, 106 | translate: { x: 7 }, 107 | rotate: { y: -TAU/4 }, 108 | }); 109 | // right wall 110 | new Zdog.Shape({ 111 | addTo: rightGroup, 112 | path: [ 113 | { x: -5, y: 7 }, 114 | { x: -5, y: -7 }, 115 | { x: 0, y: -12 }, 116 | { x: 5, y: -7 }, 117 | { x: 5, y: 7 }, 118 | ], 119 | width: 10, 120 | height: 14, 121 | color: offWhite, 122 | }); 123 | 124 | var sideWindow = frontWindow.copy({ 125 | addTo: rightGroup, 126 | translate: { x: -2, y: -3 }, 127 | color: gold, 128 | }); 129 | sideWindow.copy({ 130 | translate: { x: 2, y: -3 }, 131 | }); 132 | sideWindow.copy({ 133 | translate: { x: 2, y: 3 }, 134 | }); 135 | sideWindow.copy({ 136 | translate: { x: -2, y: 3 }, 137 | }); 138 | 139 | // porthole 140 | new Zdog.Ellipse({ 141 | addTo: rightGroup, 142 | width: 2, 143 | height: 2, 144 | translate: { y: -8 }, 145 | color: gold, 146 | }); 147 | 148 | var leftGroup = rightGroup.copyGraph({ 149 | translate: { x: -7 }, 150 | rotate: { y: TAU/4 }, 151 | }); 152 | 153 | leftGroup.children.forEach( function( child, i ) { 154 | // eggplant windows, yellow wall 155 | child.color = i ? eggplant : orange; 156 | } ); 157 | 158 | // front roof 159 | var frontRoof = new Zdog.Shape({ 160 | addTo: house, 161 | path: [ 162 | { x: -7, y: -7, z: 5 }, 163 | { x: -7, y: -12, z: 0 }, 164 | { x: 7, y: -12, z: 0 }, 165 | { x: 7, y: -7, z: 5 }, 166 | ], 167 | color: eggplant, 168 | }); 169 | 170 | frontRoof.copy({ 171 | scale: { z: -1 }, 172 | color: garnet, 173 | }); 174 | 175 | // floor 176 | new Zdog.Rect({ 177 | addTo: house, 178 | width: 14, 179 | height: 10, 180 | translate: { y: 7 }, 181 | rotate: { x: TAU/4 }, 182 | color: eggplant, 183 | }); 184 | 185 | house.copyGraph({ 186 | translate: house.translate.copy().multiply( -1 ), 187 | }); 188 | 189 | // ----- animate ----- // 190 | 191 | var ticker = 0; 192 | var cycleCount = 240; 193 | 194 | function animate() { 195 | if ( isSpinning ) { 196 | var progress = ticker/cycleCount; 197 | var tween = Zdog.easeInOut( progress % 1, 3 ); 198 | illo.rotate.y = tween * TAU + initRotate.y; 199 | ticker++; 200 | } 201 | illo.updateRenderGraph(); 202 | requestAnimationFrame( animate ); 203 | } 204 | 205 | animate(); 206 | 207 | -------------------------------------------------------------------------------- /demos/houses/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | houses 8 | 9 | 31 | 32 | 33 | 34 | 35 |
36 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /demos/kid-kit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Kid Kit 8 | 9 | 29 | 30 | 31 | 32 | 33 |
34 |

Kid Kit

35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /demos/kid-kit/kid-kit.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var offWhite = '#FED'; 4 | var gold = '#EA0'; 5 | var garnet = '#C25'; 6 | var eggplant = '#636'; 7 | 8 | var illo = new Zdog.Illustration({ 9 | element: '.illo', 10 | zoom: 4, 11 | dragRotate: true, 12 | }); 13 | 14 | // ----- model ----- // 15 | 16 | // body center 17 | new Zdog.Shape({ 18 | path: [ 19 | { x: -3, y: 10 }, 20 | { x: 0, y: 14 }, 21 | { x: 3, y: 10 }, 22 | ], 23 | addTo: illo, 24 | color: offWhite, 25 | stroke: 13, 26 | }); 27 | 28 | // head circle 29 | new Zdog.Shape({ 30 | addTo: illo, 31 | translate: { y: -12 }, 32 | color: gold, 33 | stroke: 32, 34 | }); 35 | 36 | // nose 37 | var nose = new Zdog.Anchor({ 38 | addTo: illo, 39 | translate: { y: -7, z: 17 }, 40 | }); 41 | new Zdog.Shape({ 42 | path: [ 43 | { x: -1 }, 44 | { x: 1 }, 45 | ], 46 | addTo: nose, 47 | color: eggplant, 48 | stroke: 3, 49 | }); 50 | new Zdog.Shape({ 51 | path: [ 52 | { y: 0 }, 53 | { y: 1 }, 54 | ], 55 | addTo: nose, 56 | color: eggplant, 57 | stroke: 3, 58 | }); 59 | 60 | // snout 61 | new Zdog.Shape({ 62 | path: [ 63 | { x: -2, y: -5, z: 11 }, 64 | { x: 2, y: -5, z: 11 }, 65 | { x: 2, y: -3, z: 7 }, 66 | { x: -2, y: -3, z: 7 }, 67 | ], 68 | addTo: illo, 69 | color: gold, 70 | stroke: 12, 71 | }); 72 | 73 | // eyes 74 | var eye = new Zdog.Shape({ 75 | path: [ 76 | { y: -12 }, 77 | { y: -9 }, 78 | ], 79 | addTo: illo, 80 | translate: { x: -8, z: 11 }, 81 | color: eggplant, 82 | stroke: 4, 83 | }); 84 | eye.copy({ 85 | translate: { x: 8, z: 11 }, 86 | }); 87 | 88 | // ears 89 | var frontEarZ = 4; 90 | var topEarY = -30; 91 | var earColor = gold; 92 | 93 | var earAnchor = new Zdog.Anchor({ 94 | addTo: illo, 95 | translate: { y: topEarY, z: frontEarZ }, 96 | }); 97 | 98 | var earA = { x: 14, y: 12, z: -4 }; 99 | var earB = { x: 14, y: 0, z: 0 }; 100 | var earC = { x: 7, y: 11, z: -14 }; 101 | var earD = { x: 10, y: 0, z: 0 }; 102 | var earE = { x: 3, y: 5, z: 0 }; 103 | // outer ear 104 | new Zdog.Shape({ 105 | path: [ earA, earB, earC ], 106 | addTo: earAnchor, 107 | color: earColor, 108 | fill: true, 109 | stroke: 4, 110 | }); 111 | new Zdog.Shape({ 112 | path: [ earB, earC, earD ], 113 | addTo: earAnchor, 114 | color: earColor, 115 | fill: true, 116 | stroke: 4, 117 | }); 118 | new Zdog.Shape({ 119 | path: [ earC, earD, earE ], 120 | addTo: earAnchor, 121 | color: earColor, 122 | fill: true, 123 | stroke: 4, 124 | }); 125 | // inner ear 126 | var innerEarXShift = 4; 127 | new Zdog.Shape({ 128 | path: [ 129 | { x: earA.x - innerEarXShift, y: earA.y - 3 }, 130 | { x: earD.x, y: earD.y + 5 }, 131 | { x: earE.x + innerEarXShift, y: earE.y + 2 }, 132 | ], 133 | addTo: earAnchor, 134 | color: offWhite, 135 | fill: true, 136 | stroke: 3, 137 | }); 138 | 139 | earAnchor.copyGraph({ 140 | scale: { x: -1 }, 141 | }); 142 | 143 | // var whiskerX0 = 10*xSide; 144 | // var whiskerX1 = 17*xSide; 145 | // var whiskerY0 = -6+yShift; 146 | // var whiskerY1 = -2+yShift; 147 | 148 | // whiskers 149 | var whisker = new Zdog.Shape({ 150 | path: [ 151 | { x: 10, y: -6 }, 152 | { x: 10, y: -2 }, 153 | { x: 17, y: -2 }, 154 | ], 155 | addTo: illo, 156 | translate: { z: 6 }, 157 | fill: true, 158 | color: gold, 159 | stroke: 3, 160 | }); 161 | whisker.copy({ 162 | translate: { y: -6, z: 6 }, 163 | }); 164 | whisker.copy({ 165 | scale: { x: -1 }, 166 | }); 167 | whisker.copy({ 168 | scale: { x: -1 }, 169 | translate: { y: -6, z: 6 }, 170 | }); 171 | 172 | // arms 173 | 174 | var armAnchor = new Zdog.Anchor({ 175 | addTo: illo, 176 | }); 177 | 178 | // shoulder 179 | new Zdog.Shape({ 180 | path: [ 181 | { x: 11, y: 6, z: -2 }, 182 | { x: 12, y: 9, z: -2.5 }, 183 | ], 184 | addTo: armAnchor, 185 | closed: true, 186 | color: eggplant, 187 | stroke: 8, 188 | }); 189 | // forearm 190 | new Zdog.Shape({ 191 | path: [ 192 | { x: 12, y: 12, z: -2.5 }, 193 | { x: 12, y: 15, z: -2 }, 194 | ], 195 | addTo: armAnchor, 196 | color: gold, 197 | stroke: 8, 198 | }); 199 | // hand 200 | new Zdog.Shape({ 201 | path: [ { x: 11, y: 18, z: -1 } ], 202 | addTo: armAnchor, 203 | color: eggplant, 204 | stroke: 10, 205 | }); 206 | 207 | armAnchor.copyGraph({ 208 | scale: { x: -1 }, 209 | }); 210 | 211 | // legs 212 | var leg = new Zdog.Shape({ 213 | path: [ 214 | { y: 20 }, 215 | { y: 27 }, 216 | ], 217 | addTo: illo, 218 | translate: { x: -6 }, 219 | color: eggplant, 220 | stroke: 8, 221 | }); 222 | leg.copy({ 223 | translate: { x: 6 }, 224 | }); 225 | 226 | var cloakX0 = 8; 227 | var cloakX1 = 5; 228 | 229 | var cloakY0 = 4; 230 | var cloakY1 = 6; 231 | var cloakY2 = 13; 232 | var cloakY3 = 21; 233 | 234 | var cloakZ0 = 0; 235 | var cloakZ1 = 6; 236 | var cloakZ2 = 8; 237 | 238 | var cloakSide = new Zdog.Anchor({ 239 | addTo: illo, 240 | }); 241 | 242 | // top straps 243 | var topCloakStrap = new Zdog.Shape({ 244 | path: [ 245 | { x: cloakX0, y: cloakY0, z: cloakZ0 }, 246 | { x: cloakX0, y: cloakY1, z: cloakZ1 }, 247 | { x: cloakX1, y: cloakY1, z: cloakZ1 }, 248 | ], 249 | addTo: cloakSide, 250 | fill: true, 251 | color: garnet, 252 | stroke: 4, 253 | }); 254 | 255 | topCloakStrap.copy({ 256 | scale: { x: -1 }, 257 | }); 258 | 259 | var vNeckY = ( cloakY1 + cloakY2 )/2; 260 | var vNeckZ = ( cloakZ2 + cloakZ1 )/2; 261 | new Zdog.Shape({ 262 | path: [ 263 | { x: -cloakX0, y: cloakY1, z: cloakZ1 }, 264 | { x: -cloakX1, y: cloakY1, z: cloakZ1 }, 265 | { x: 0, y: vNeckY, z: vNeckZ }, 266 | { x: cloakX1, y: cloakY1, z: cloakZ1 }, 267 | { x: cloakX0, y: cloakY1, z: cloakZ1 }, 268 | { x: cloakX0, y: cloakY2, z: cloakZ2 }, 269 | { x: -cloakX0, y: cloakY2, z: cloakZ2 }, 270 | ], 271 | addTo: cloakSide, 272 | fill: true, 273 | color: garnet, 274 | stroke: 4, 275 | }); 276 | new Zdog.Shape({ 277 | path: [ 278 | { x: -cloakX0, y: cloakY2 }, 279 | { x: cloakX0, y: cloakY2 }, 280 | { x: cloakX0, y: cloakY3 }, 281 | { x: -cloakX0, y: cloakY3 }, 282 | ], 283 | addTo: cloakSide, 284 | translate: { z: cloakZ2 }, 285 | fill: true, 286 | color: garnet, 287 | stroke: 4, 288 | }); 289 | 290 | cloakSide.copyGraph({ 291 | scale: { z: -1 }, 292 | }); 293 | 294 | // ----- animate ----- // 295 | 296 | function animate() { 297 | illo.updateRenderGraph(); 298 | requestAnimationFrame( animate ); 299 | } 300 | 301 | animate(); 302 | 303 | -------------------------------------------------------------------------------- /demos/kirby-parasol/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | kirby parasol 8 | 9 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /demos/kirby-parasol/kirby-parasol.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var sceneSize = 80; 4 | var isSpinning = true; 5 | var TAU = Zdog.TAU; 6 | // colors 7 | var pink = '#F8B'; 8 | var blush = '#F5A'; 9 | var black = '#333'; 10 | var shoe = '#D03'; 11 | var red = '#E10'; 12 | var yellow = '#FD0'; 13 | 14 | var illo = new Zdog.Illustration({ 15 | element: '.illo', 16 | dragRotate: true, 17 | resize: 'fullscreen', 18 | onDragStart: function() { 19 | isSpinning = false; 20 | }, 21 | onResize: function( width, height ) { 22 | this.zoom = Math.floor( Math.min( width, height ) / sceneSize ); 23 | }, 24 | }); 25 | 26 | // ----- model ----- // 27 | 28 | var body = new Zdog.Shape({ 29 | stroke: 22, 30 | translate: { y: 11 }, 31 | rotate: { x: 0.3, z: 0.1 }, 32 | addTo: illo, 33 | color: pink, 34 | }); 35 | 36 | var face = new Zdog.Anchor({ 37 | translate: { z: 10.5 }, 38 | addTo: body, 39 | }); 40 | 41 | [ -1, 1 ].forEach( function( xSide ) { 42 | var eyeGroup = new Zdog.Group({ 43 | addTo: face, 44 | translate: { x: 2.4 * xSide, y: -2 }, 45 | rotate: { x: -0.1 }, 46 | }); 47 | // eye 48 | new Zdog.Ellipse({ 49 | width: 1.4, 50 | height: 5.5, 51 | addTo: eyeGroup, 52 | stroke: 1, 53 | color: black, 54 | fill: true, 55 | }); 56 | // eye highlight 57 | new Zdog.Ellipse({ 58 | width: 1, 59 | height: 2, 60 | addTo: eyeGroup, 61 | translate: { y: -1.5, z: 0.5 }, 62 | stroke: 0.5, 63 | color: '#FFF', 64 | fill: true, 65 | }); 66 | 67 | // cheek holder 68 | var cheekHolder = new Zdog.Anchor({ 69 | addTo: body, 70 | rotate: { y: 0.6 * xSide }, 71 | }); 72 | 73 | new Zdog.Ellipse({ 74 | width: 2.5, 75 | height: 1, 76 | translate: { y: 1, z: 10.5 }, 77 | addTo: cheekHolder, 78 | color: blush, 79 | stroke: 1, 80 | }); 81 | 82 | } ); 83 | 84 | // mouth 85 | new Zdog.Shape({ 86 | path: [ 87 | { x: 0, y: 0 }, 88 | { bezier: [ 89 | { x: 1.1, y: 0 }, 90 | { x: 1.1, y: 0.2 }, 91 | { x: 1.1, y: 0.5 }, 92 | ] }, 93 | { bezier: [ 94 | { x: 1.1, y: 1.1 }, 95 | { x: 0.2, y: 1.8 }, 96 | { x: 0, y: 1.8 }, 97 | ] }, 98 | { bezier: [ 99 | { x: -0.2, y: 1.8 }, 100 | { x: -1.1, y: 1.1 }, 101 | { x: -1.1, y: 0.5 }, 102 | ] }, 103 | { bezier: [ 104 | { x: -1.1, y: 0.2 }, 105 | { x: -1.1, y: 0 }, 106 | { x: 0, y: 0 }, 107 | ] }, 108 | ], 109 | addTo: face, 110 | translate: { y: 2, z: -0.5 }, 111 | stroke: 1, 112 | color: shoe, 113 | fill: true, 114 | }); 115 | 116 | var rightArm = new Zdog.Shape({ 117 | path: [ 118 | { y: 0 }, 119 | { y: -7 }, 120 | ], 121 | addTo: body, 122 | translate: { x: -6, y: -4, z: 0 }, 123 | color: pink, 124 | stroke: 7, 125 | }); 126 | 127 | // left arm 128 | rightArm.copy({ 129 | path: [ 130 | { x: 0 }, 131 | { x: 6 }, 132 | ], 133 | translate: { x: 6, y: -2, z: 0 }, 134 | }); 135 | 136 | // right foot 137 | var rightFoot = new Zdog.Shape({ 138 | path: [ 139 | { x: 0, y: -2 }, 140 | { arc: [ 141 | { x: 2, y: -2 }, 142 | { x: 2, y: 0 }, 143 | ] }, 144 | { arc: [ 145 | { x: 2, y: 5 }, 146 | { x: 0, y: 5 }, 147 | ] }, 148 | { arc: [ 149 | { x: -2, y: 5 }, 150 | { x: -2, y: 0 }, 151 | ] }, 152 | { arc: [ 153 | { x: -2, y: -2 }, 154 | { x: 0, y: -2 }, 155 | ] }, 156 | ], 157 | addTo: body, 158 | translate: { x: -1, y: 9, z: -9 }, 159 | rotate: { z: 0.2 }, 160 | stroke: 6, 161 | color: shoe, 162 | fill: true, 163 | closed: false, 164 | }); 165 | 166 | rightFoot.copy({ 167 | translate: { x: 9.5, y: 6, z: -6 }, 168 | rotate: { z: -1.1, y: 0.8 }, 169 | }); 170 | 171 | // ----- umbrella ----- // 172 | 173 | // umbrella rod 174 | var umbrella = new Zdog.Shape({ 175 | path: [ 176 | { y: 0 }, 177 | { y: 22 }, 178 | ], 179 | addTo: rightArm, 180 | translate: { y: -33, z: 2 }, 181 | rotate: { y: 0.5 }, 182 | color: yellow, 183 | stroke: 1, 184 | }); 185 | 186 | // star 187 | var starPath = ( function() { 188 | var path = []; 189 | var starRadiusA = 3; 190 | var starRadiusB = 1.7; 191 | for ( var i = 0; i < 10; i++ ) { 192 | var radius = i % 2 ? starRadiusA : starRadiusB; 193 | var angle = TAU * i/10 + TAU/4; 194 | var point = { 195 | x: Math.cos( angle ) * radius, 196 | y: Math.sin( angle ) * radius, 197 | }; 198 | path.push( point ); 199 | } 200 | return path; 201 | } )(); 202 | // star shape 203 | var star = new Zdog.Shape({ 204 | path: starPath, 205 | addTo: umbrella, 206 | translate: { y: -4.5 }, 207 | stroke: 2, 208 | color: yellow, 209 | fill: true, 210 | }); 211 | 212 | // umbrella handle 213 | new Zdog.Shape({ 214 | path: [ 215 | { z: 0, y: 0 }, 216 | { z: 0, y: 1 }, 217 | { arc: [ 218 | { z: 0, y: 4 }, 219 | { z: 3, y: 4 }, 220 | ] }, 221 | { arc: [ 222 | { z: 6, y: 4 }, 223 | { z: 6, y: 1 }, 224 | ] }, 225 | ], 226 | addTo: umbrella, 227 | translate: { y: 23 }, 228 | stroke: 2, 229 | color: '#37F', 230 | closed: false, 231 | }); 232 | 233 | // umbrella shield panels 234 | ( function() { 235 | var umbPanelX = 14 * Math.sin( TAU/24 ); 236 | var umbPanelZ = 14 * Math.cos( TAU/24 ); 237 | for ( var i = 0; i < 12; i++ ) { 238 | var colorSide = Math.floor( i/2 ) % 2; 239 | new Zdog.Shape({ 240 | path: [ 241 | { x: 0, y: 0, z: 0 }, 242 | { arc: [ 243 | { x: -umbPanelX, y: 0, z: umbPanelZ }, 244 | { x: -umbPanelX, y: 14, z: umbPanelZ }, 245 | ] }, 246 | { x: umbPanelX, y: 14, z: umbPanelZ }, 247 | { arc: [ 248 | { x: umbPanelX, y: 0, z: umbPanelZ }, 249 | { x: 0, y: 0, z: 0 }, 250 | ] }, 251 | ], 252 | addTo: umbrella, 253 | rotate: { y: TAU/12 * i }, 254 | stroke: 1, 255 | color: colorSide ? red : 'white', 256 | fill: true, 257 | }); 258 | } 259 | } )(); 260 | 261 | // floater stars 262 | ( function() { 263 | for ( var i = 0; i < 6; i++ ) { 264 | var starHolder = new Zdog.Anchor({ 265 | addTo: umbrella, 266 | translate: { y: 10 }, 267 | rotate: { y: TAU/6 * i + TAU/24 }, 268 | }); 269 | star.copy({ 270 | addTo: starHolder, 271 | translate: { z: 28 }, 272 | }); 273 | } 274 | } )(); 275 | 276 | // ----- animate ----- // 277 | 278 | function animate() { 279 | illo.rotate.y += isSpinning ? -0.03 : 0; 280 | illo.updateRenderGraph(); 281 | requestAnimationFrame( animate ); 282 | } 283 | 284 | animate(); 285 | 286 | -------------------------------------------------------------------------------- /demos/no-illo-canvas/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | No Illo canvas 8 | 9 | 29 | 30 | 31 | 32 | 33 |
34 |

No Illo canvas

35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /demos/no-illo-canvas/no-illo-canvas.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | // get canvas element and its context 4 | var canvas = document.querySelector('.zdog-canvas'); 5 | var ctx = canvas.getContext('2d'); 6 | // get canvas size 7 | var canvasWidth = canvas.width; 8 | var canvasHeight = canvas.height; 9 | // illustration variables 10 | var zoom = 5; 11 | var isSpinning = true; 12 | var TAU = Zdog.TAU; 13 | 14 | // ----- model ----- // 15 | 16 | var scene = new Zdog.Anchor(); 17 | 18 | // circle 19 | new Zdog.Ellipse({ 20 | addTo: scene, 21 | diameter: 20, 22 | translate: { z: 10 }, 23 | stroke: 5, 24 | color: '#636', 25 | }); 26 | 27 | // square 28 | new Zdog.Rect({ 29 | addTo: scene, 30 | width: 20, 31 | height: 20, 32 | translate: { z: -10 }, 33 | stroke: 3, 34 | color: '#E62', 35 | fill: true, 36 | }); 37 | 38 | // ----- animate ----- // 39 | 40 | function animate() { 41 | scene.rotate.y += isSpinning ? 0.03 : 0; 42 | scene.updateGraph(); 43 | render(); 44 | requestAnimationFrame( animate ); 45 | } 46 | 47 | function render() { 48 | // clear canvas 49 | ctx.clearRect( 0, 0, canvasWidth, canvasHeight ); 50 | ctx.save(); 51 | // center canvas & zoom 52 | ctx.translate( canvasWidth/2, canvasHeight/2 ); 53 | ctx.scale( zoom, zoom ); 54 | // set lineJoin and lineCap to round 55 | ctx.lineJoin = 'round'; 56 | ctx.lineCap = 'round'; 57 | // render scene graph 58 | scene.renderGraphCanvas( ctx ); 59 | ctx.restore(); 60 | } 61 | 62 | animate(); 63 | 64 | // ----- drag ----- // 65 | 66 | var dragStartRX, dragStartRY; 67 | var minSize = Math.min( canvasWidth, canvasHeight ); 68 | 69 | // add drag-rotatation with Dragger 70 | new Zdog.Dragger({ 71 | startElement: canvas, 72 | onDragStart: function() { 73 | isSpinning = false; 74 | dragStartRX = scene.rotate.x; 75 | dragStartRY = scene.rotate.y; 76 | }, 77 | onDragMove: function( pointer, moveX, moveY ) { 78 | scene.rotate.x = dragStartRX - ( moveY/minSize * TAU ); 79 | scene.rotate.y = dragStartRY - ( moveX/minSize * TAU ); 80 | }, 81 | }); 82 | -------------------------------------------------------------------------------- /demos/no-illo-svg/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | No Illo SVG 8 | 9 | 29 | 30 | 31 | 32 | 33 |
34 |

No Illo SVG

35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /demos/no-illo-svg/no-illo-svg.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | // svg element 4 | var svg = document.querySelector('svg'); 5 | // set size 6 | var zoom = 5; 7 | var svgWidth = svg.getAttribute('width'); 8 | var svgHeight = svg.getAttribute('height'); 9 | // set viewBox for zoom & centering 10 | var viewWidth = svgWidth/zoom; 11 | var viewHeight = svgHeight/zoom; 12 | svg.setAttribute( 'viewBox', -viewWidth/2 + ' ' + -viewHeight/2 + ' ' + 13 | viewWidth + ' ' + viewHeight ); 14 | // rendering variable 15 | var isSpinning = true; 16 | var TAU = Zdog.TAU; 17 | 18 | var scene = new Zdog.Anchor(); 19 | 20 | // ----- model ----- // 21 | 22 | // circle 23 | new Zdog.Ellipse({ 24 | addTo: scene, 25 | diameter: 20, 26 | translate: { z: 10 }, 27 | stroke: 5, 28 | color: '#636', 29 | }); 30 | 31 | // square 32 | new Zdog.Rect({ 33 | addTo: scene, 34 | width: 20, 35 | height: 20, 36 | translate: { z: -10 }, 37 | stroke: 3, 38 | color: '#E62', 39 | fill: true, 40 | }); 41 | 42 | // ----- animate ----- // 43 | 44 | function animate() { 45 | scene.rotate.y += isSpinning ? 0.03 : 0; 46 | scene.updateGraph(); 47 | render(); 48 | requestAnimationFrame( animate ); 49 | } 50 | 51 | function render() { 52 | empty( svg ); 53 | scene.renderGraphSvg( svg ); 54 | } 55 | 56 | animate(); 57 | 58 | function empty( element ) { 59 | while ( element.firstChild ) { 60 | element.removeChild( element.firstChild ); 61 | } 62 | } 63 | 64 | // ----- drag ----- // 65 | 66 | var dragStartRX, dragStartRY; 67 | var minSize = Math.min( svgWidth, svgHeight ); 68 | 69 | // add drag-rotatation with Dragger 70 | new Zdog.Dragger({ 71 | startElement: svg, 72 | onDragStart: function() { 73 | isSpinning = false; 74 | dragStartRX = scene.rotate.x; 75 | dragStartRY = scene.rotate.y; 76 | }, 77 | onDragMove: function( pointer, moveX, moveY ) { 78 | scene.rotate.x = dragStartRX - ( moveY/minSize * TAU ); 79 | scene.rotate.y = dragStartRY - ( moveX/minSize * TAU ); 80 | }, 81 | }); 82 | -------------------------------------------------------------------------------- /demos/path-commands/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Path commands 8 | 9 | 29 | 30 | 31 | 32 | 33 |
34 |

Path commands

35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /demos/path-commands/path-commands.js: -------------------------------------------------------------------------------- 1 | // ----- variables ----- // 2 | 3 | var eggplant = '#636'; 4 | 5 | // ----- model ----- // 6 | 7 | var illo = new Zdog.Illustration({ 8 | element: '.illo', 9 | zoom: 5, 10 | dragRotate: true, 11 | }); 12 | 13 | // lines 14 | new Zdog.Shape({ 15 | addTo: illo, 16 | path: [ 17 | { x: -6, y: -6 }, 18 | { x: 6, y: -6 }, 19 | { x: -6, y: 6 }, 20 | { x: 6, y: 6 }, 21 | ], 22 | translate: { x: -12, y: -12 }, 23 | closed: false, 24 | color: eggplant, 25 | stroke: 2, 26 | }); 27 | 28 | // move 29 | new Zdog.Shape({ 30 | addTo: illo, 31 | path: [ 32 | { x: -6, y: -6 }, 33 | { x: 6, y: -6 }, 34 | { move: { x: -6, y: 6 } }, 35 | { x: 6, y: 6 }, 36 | ], 37 | translate: { x: 12, y: -12 }, 38 | closed: false, 39 | color: eggplant, 40 | stroke: 2, 41 | }); 42 | 43 | // arc 44 | new Zdog.Shape({ 45 | addTo: illo, 46 | path: [ 47 | { x: -6, y: -6 }, // start 48 | { arc: [ 49 | { x: 2, y: -6 }, // corner 50 | { x: 2, y: 2 }, // end point 51 | ] }, 52 | { arc: [ // start next arc from last end point 53 | { x: 2, y: 6 }, // corner 54 | { x: 6, y: 6 }, // end point 55 | ] }, 56 | ], 57 | translate: { x: -12, y: 12 }, 58 | closed: false, 59 | color: eggplant, 60 | stroke: 2, 61 | }); 62 | 63 | // bezier 64 | new Zdog.Shape({ 65 | addTo: illo, 66 | path: [ 67 | { x: -6, y: -6 }, // start 68 | { bezier: [ 69 | { x: 2, y: -6 }, // start control point 70 | { x: 2, y: 6 }, // end control point 71 | { x: 6, y: 6 }, // end control point 72 | ] }, 73 | ], 74 | translate: { x: 12, y: 12 }, 75 | closed: false, 76 | color: eggplant, 77 | stroke: 2, 78 | }); 79 | 80 | // ----- animate ----- // 81 | 82 | function animate() { 83 | illo.updateRenderGraph(); 84 | requestAnimationFrame( animate ); 85 | } 86 | 87 | animate(); 88 | -------------------------------------------------------------------------------- /demos/resize/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | resize 8 | 9 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /demos/resize/resize.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var zoom = 4; 4 | var isSpinning = true; 5 | var gold = '#EA0'; 6 | var orange = '#E62'; 7 | var garnet = '#C25'; 8 | var eggplant = '#636'; 9 | 10 | var model = new Zdog.Anchor(); 11 | 12 | var canvasIllo = new Zdog.Illustration({ 13 | element: 'canvas', 14 | zoom: zoom, 15 | resize: true, 16 | dragRotate: model, 17 | onDragStart: function() { 18 | isSpinning = false; 19 | }, 20 | onResize: function( width, height ) { 21 | this.zoom = Math.min( width, height ) / 50; 22 | }, 23 | }); 24 | 25 | var svgIllo = new Zdog.Illustration({ 26 | element: 'svg', 27 | zoom: zoom, 28 | resize: true, 29 | dragRotate: model, 30 | onDragStart: function() { 31 | isSpinning = false; 32 | }, 33 | onResize: function( width, height ) { 34 | this.zoom = Math.min( width, height ) / 50; 35 | }, 36 | }); 37 | 38 | // HACK set initial zoom for SVG 39 | svgIllo.setSize( svgIllo.width, svgIllo.height ); 40 | 41 | // ----- model ----- // 42 | 43 | new Zdog.Rect({ 44 | width: 20, 45 | height: 20, 46 | addTo: model, 47 | translate: { z: -10 }, 48 | stroke: 2, 49 | color: garnet, 50 | }); 51 | 52 | new Zdog.Ellipse({ 53 | diameter: 16, 54 | addTo: model, 55 | translate: { z: 10 }, 56 | stroke: 4, 57 | color: eggplant, 58 | }); 59 | 60 | new Zdog.Shape({ 61 | path: [ 62 | { x: 0, z: 1 }, 63 | { x: -1, z: -1 }, 64 | { x: 1, z: -1 }, 65 | ], 66 | scale: { x: 5, z: 5 }, 67 | addTo: model, 68 | stroke: 2, 69 | fill: true, 70 | color: gold, 71 | }); 72 | 73 | new Zdog.Shape({ 74 | translate: { x: 10, y: -5 }, 75 | addTo: model, 76 | stroke: 7, 77 | color: orange, 78 | }); 79 | 80 | model.copyGraph({ 81 | addTo: svgIllo, 82 | }); 83 | 84 | // ----- animate ----- // 85 | 86 | function animate() { 87 | model.rotate.y += isSpinning ? 0.03 : 0; 88 | model.updateGraph(); 89 | svgIllo.renderGraph( model ); 90 | canvasIllo.renderGraph( model ); 91 | requestAnimationFrame( animate ); 92 | } 93 | 94 | animate(); 95 | 96 | -------------------------------------------------------------------------------- /demos/shade-and-shades/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | shade & shades 8 | 9 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /demos/shade-and-shades/shade-and-shades.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var sceneSize = 96; 4 | var orange = '#E62'; 5 | var eggplant = '#636'; 6 | 7 | // shape defaults 8 | Zdog.Shape.defaults.closed = false; 9 | [ Zdog.Shape, Zdog.Ellipse ].forEach( function( ShapeClass ) { 10 | ShapeClass.defaults.stroke = 3; 11 | ShapeClass.defaults.color = orange; 12 | } ); 13 | 14 | var isSpinning = true; 15 | var TAU = Zdog.TAU; 16 | var initialRotate = { y: -TAU/8 }; 17 | 18 | var illo = new Zdog.Illustration({ 19 | element: '.illo', 20 | rotate: initialRotate, 21 | dragRotate: true, 22 | resize: 'fullscreen', 23 | onDragStart: function() { 24 | isSpinning = false; 25 | }, 26 | onResize: function( width, height ) { 27 | this.zoom = Math.floor( Math.min( width, height ) / sceneSize ); 28 | }, 29 | }); 30 | 31 | // ----- model ----- // 32 | 33 | // cap top 34 | [ 0, 1, 2, 3, 4 ].forEach( function( i ) { 35 | new Zdog.Shape({ 36 | path: [ 37 | { x: -20, y: 4 }, 38 | { x: -20, y: 0 }, 39 | { arc: [ 40 | { x: -20, y: -20 }, 41 | { x: 0, y: -20 }, 42 | ] }, 43 | ], 44 | rotate: { y: TAU/6 * i - TAU/12 }, 45 | addTo: illo, 46 | }); 47 | } ); 48 | 49 | // cap back 50 | new Zdog.Ellipse({ 51 | addTo: illo, 52 | diameter: 40, 53 | quarters: 2, 54 | translate: { y: 4 }, 55 | rotate: { x: TAU/4, z: -TAU/4 }, 56 | }); 57 | 58 | // cap back to brim bottom connect 59 | var brimConnector = new Zdog.Shape({ 60 | path: [ 61 | { x: -20, z: 0 }, 62 | { arc: [ 63 | { x: -20, z: 6 }, 64 | { x: -16, z: 12 }, 65 | ] }, 66 | ], 67 | addTo: illo, 68 | translate: { y: 4 }, 69 | }); 70 | 71 | brimConnector.copy({ 72 | scale: { x: -1 }, 73 | }); 74 | 75 | // brim back arch 76 | new Zdog.Ellipse({ 77 | addTo: illo, 78 | diameter: 32, 79 | quarters: 2, 80 | translate: { y: 4, z: 12 }, 81 | rotate: { z: -TAU/4 }, 82 | }); 83 | 84 | var brimTip = new Zdog.Vector({ x: 0, y: -12, z: 34 }); 85 | var brimEdge = brimTip.copy(); 86 | brimEdge.x = -14; 87 | 88 | // brim top line 89 | new Zdog.Shape({ 90 | addTo: illo, 91 | path: [ 92 | { x: 0, y: -12, z: 12 }, 93 | brimTip, 94 | ], 95 | }); 96 | 97 | var brimBridge = new Zdog.Shape({ 98 | addTo: illo, 99 | path: [ 100 | { x: -16, y: 4, z: 12 }, 101 | { x: -16, y: 4, z: 18 }, 102 | { bezier: [ 103 | { x: -16, y: 4, z: 30 }, 104 | brimEdge, 105 | brimTip, 106 | ] }, 107 | ], 108 | }); 109 | brimBridge.copy({ 110 | scale: { x: -1 }, 111 | }); 112 | 113 | // glasses front top 114 | var glassFront = new Zdog.Shape({ 115 | addTo: illo, 116 | path: [ 117 | { x: -16 }, 118 | { x: 16 }, 119 | ], 120 | translate: { y: 8, z: 12 }, 121 | color: eggplant, 122 | }); 123 | 124 | // glass lens 125 | var glassLens = new Zdog.Shape({ 126 | addTo: glassFront, 127 | path: [ 128 | { x: -1, y: -1 }, 129 | { x: 1, y: -1 }, 130 | { x: 1, y: 0 }, 131 | { arc: [ 132 | { x: 1, y: 1 }, 133 | { x: 0, y: 1 }, 134 | ] }, 135 | { arc: [ 136 | { x: -1, y: 1 }, 137 | { x: -1, y: 0 }, 138 | ] }, 139 | ], 140 | closed: true, 141 | scale: 5, 142 | translate: { x: -8, y: 5 }, 143 | color: eggplant, 144 | fill: true, 145 | }); 146 | 147 | glassLens.copy({ 148 | translate: { x: 8, y: 5 }, 149 | }); 150 | 151 | // glasses arm 152 | var glassesArm = new Zdog.Shape({ 153 | addTo: illo, 154 | path: [ 155 | { x: 12, y: 0 }, 156 | { x: -1, y: 0 }, 157 | { arc: [ 158 | { x: -12, y: 0 }, 159 | { x: -12, y: 8 }, 160 | ] }, 161 | ], 162 | rotate: { y: TAU/4 }, 163 | translate: { x: -16, y: 8 }, 164 | color: eggplant, 165 | // only see one arm at time 166 | backface: false, 167 | }); 168 | glassesArm.copy({ 169 | scale: { x: -1 }, 170 | rotate: { y: -TAU/4 }, 171 | translate: { x: 16, y: 8 }, 172 | }); 173 | 174 | // ----- animate ----- // 175 | 176 | var ticker = 0; 177 | var cycleCount = 150; 178 | 179 | function animate() { 180 | if ( isSpinning ) { 181 | var progress = ticker/cycleCount; 182 | var tween = Zdog.easeInOut( progress % 1, 4 ); 183 | illo.rotate.y = tween * TAU + initialRotate.y; 184 | ticker++; 185 | } 186 | 187 | illo.updateRenderGraph(); 188 | requestAnimationFrame( animate ); 189 | } 190 | 191 | animate(); 192 | -------------------------------------------------------------------------------- /demos/shapes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | shapes 8 | 9 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /demos/shapes/shapes.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var sceneSize = 24; 4 | var isSpinning = true; 5 | var TAU = Zdog.TAU; 6 | var offWhite = '#FED'; 7 | var gold = '#EA0'; 8 | var orange = '#E62'; 9 | var garnet = '#C25'; 10 | var eggplant = '#636'; 11 | 12 | var illo = new Zdog.Illustration({ 13 | element: '.illo', 14 | dragRotate: true, 15 | resize: 'fullscreen', 16 | onDragStart: function() { 17 | isSpinning = false; 18 | }, 19 | onResize: function( width, height ) { 20 | this.zoom = Math.floor( Math.min( width, height ) / sceneSize ); 21 | }, 22 | }); 23 | 24 | // ----- model ----- // 25 | 26 | new Zdog.Rect({ 27 | addTo: illo, 28 | width: 4, 29 | height: 4, 30 | translate: { x: -4, y: -4, z: 4 }, 31 | stroke: 1, 32 | color: orange, 33 | }); 34 | 35 | new Zdog.RoundedRect({ 36 | addTo: illo, 37 | width: 4, 38 | height: 4, 39 | cornerRadius: 1, 40 | translate: { x: -4, y: 4, z: -4 }, 41 | stroke: 1, 42 | color: eggplant, 43 | }); 44 | 45 | new Zdog.Ellipse({ 46 | addTo: illo, 47 | diameter: 4, 48 | translate: { x: 4, y: 4, z: 4 }, 49 | stroke: 1, 50 | color: garnet, 51 | }); 52 | 53 | new Zdog.Polygon({ 54 | addTo: illo, 55 | sides: 3, 56 | radius: 2.5, 57 | translate: { x: 4, y: -4, z: -4 }, 58 | stroke: 1, 59 | color: orange, 60 | }); 61 | 62 | new Zdog.Shape({ 63 | addTo: illo, 64 | path: [ 65 | { x: -1 }, 66 | { x: 1 }, 67 | { move: { y: -1 } }, 68 | { y: 1 }, 69 | { move: { z: -1 } }, 70 | { z: 1 }, 71 | ], 72 | scale: 1.25, 73 | stroke: 1, 74 | color: offWhite, 75 | }); 76 | 77 | new Zdog.Hemisphere({ 78 | addTo: illo, 79 | diameter: 5, 80 | translate: { x: -4, y: -4, z: -4 }, 81 | color: garnet, 82 | backface: gold, 83 | stroke: false, 84 | }); 85 | 86 | new Zdog.Cylinder({ 87 | addTo: illo, 88 | diameter: 5, 89 | length: 4, 90 | translate: { x: -4, y: 4, z: 4 }, 91 | color: gold, 92 | backface: offWhite, 93 | stroke: false, 94 | }); 95 | 96 | new Zdog.Cone({ 97 | addTo: illo, 98 | diameter: 5, 99 | length: 4, 100 | translate: { x: 4, y: -4, z: 4 }, 101 | color: eggplant, 102 | backface: garnet, 103 | stroke: false, 104 | }); 105 | 106 | new Zdog.Box({ 107 | addTo: illo, 108 | width: 5, 109 | height: 5, 110 | depth: 5, 111 | translate: { x: 4, y: 4, z: -4 }, 112 | color: orange, 113 | topFace: gold, 114 | leftFace: garnet, 115 | rightFace: garnet, 116 | bottomFace: eggplant, 117 | stroke: false, 118 | }); 119 | 120 | // ----- animate ----- // 121 | 122 | var ticker = 0; 123 | var cycleCount = 360; 124 | 125 | function animate() { 126 | if ( isSpinning ) { 127 | var progress = ticker/cycleCount; 128 | var theta = Zdog.easeInOut( progress % 1, 3 ) * TAU; 129 | illo.rotate.y = theta * 2; 130 | illo.rotate.x = Math.sin( theta ) * 0.5; 131 | ticker++; 132 | } 133 | illo.updateRenderGraph(); 134 | requestAnimationFrame( animate ); 135 | } 136 | 137 | animate(); 138 | 139 | -------------------------------------------------------------------------------- /demos/solids/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | solids 8 | 9 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /demos/solids/solids.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var illoElem = document.querySelector('.illo'); 4 | var sceneSize = 96; 5 | var TAU = Zdog.TAU; 6 | var ROOT3 = Math.sqrt( 3 ); 7 | var ROOT5 = Math.sqrt( 5 ); 8 | var PHI = ( 1 + ROOT5 )/2; 9 | var isSpinning = true; 10 | var viewRotation = new Zdog.Vector(); 11 | var displaySize; 12 | 13 | // colors 14 | var eggplant = '#636'; 15 | var garnet = '#C25'; 16 | var orange = '#E62'; 17 | var gold = '#EA0'; 18 | var yellow = '#ED0'; 19 | 20 | var illo = new Zdog.Illustration({ 21 | element: illoElem, 22 | scale: 8, 23 | resize: 'fullscreen', 24 | onResize: function( width, height ) { 25 | displaySize = Math.min( width, height ); 26 | this.zoom = Math.floor( displaySize/sceneSize ); 27 | }, 28 | }); 29 | 30 | var solids = []; 31 | 32 | // ----- hourglass ----- // 33 | 34 | ( function() { 35 | 36 | var hourglass = new Zdog.Anchor({ 37 | addTo: illo, 38 | translate: { x: 0, y: -4 }, 39 | }); 40 | 41 | solids.push( hourglass ); 42 | 43 | var hemi = new Zdog.Hemisphere({ 44 | diameter: 2, 45 | translate: { z: -1 }, 46 | addTo: hourglass, 47 | color: garnet, 48 | backface: orange, 49 | stroke: false, 50 | }); 51 | 52 | hemi.copy({ 53 | translate: { z: 1 }, 54 | rotate: { y: TAU/2 }, 55 | color: eggplant, 56 | backface: gold, 57 | }); 58 | 59 | } )(); 60 | 61 | // ----- sphere ----- // 62 | 63 | ( function() { 64 | 65 | var sphere = new Zdog.Anchor({ 66 | addTo: illo, 67 | translate: { x: -4, y: -4 }, 68 | }); 69 | 70 | solids.push( sphere ); 71 | 72 | var hemi = new Zdog.Hemisphere({ 73 | diameter: 2, 74 | addTo: sphere, 75 | color: orange, 76 | backface: eggplant, 77 | stroke: false, 78 | }); 79 | 80 | hemi.copy({ 81 | rotate: { y: TAU/2 }, 82 | color: eggplant, 83 | backface: orange, 84 | }); 85 | 86 | } )(); 87 | 88 | // ----- cylinder ----- // 89 | 90 | var cylinder = new Zdog.Cylinder({ 91 | diameter: 2, 92 | length: 2, 93 | addTo: illo, 94 | translate: { x: 4, y: -4 }, 95 | // rotate: { x: TAU/4 }, 96 | color: gold, 97 | backface: garnet, 98 | stroke: false, 99 | }); 100 | 101 | solids.push( cylinder ); 102 | 103 | // ----- cone ----- // 104 | 105 | var cone = new Zdog.Anchor({ 106 | addTo: illo, 107 | translate: { x: -4, y: 0 }, 108 | }); 109 | 110 | solids.push( cone ); 111 | 112 | new Zdog.Cone({ 113 | diameter: 2, 114 | length: 2, 115 | addTo: cone, 116 | translate: { z: 1 }, 117 | rotate: { y: TAU/2 }, 118 | color: garnet, 119 | backface: gold, 120 | stroke: false, 121 | }); 122 | 123 | // ----- tetrahedron ----- // 124 | 125 | ( function() { 126 | 127 | var tetrahedron = new Zdog.Anchor({ 128 | addTo: illo, 129 | translate: { x: 0, y: 0 }, 130 | scale: 2.5, 131 | }); 132 | 133 | var radius = 0.5; 134 | var inradius = Math.cos( TAU/6 ) * radius; 135 | var height = radius + inradius; 136 | 137 | solids.push( tetrahedron ); 138 | 139 | var triangle = new Zdog.Polygon({ 140 | sides: 3, 141 | radius: radius, 142 | addTo: tetrahedron, 143 | translate: { y: height/2 }, 144 | fill: true, 145 | stroke: false, 146 | color: eggplant, 147 | // backface: false, 148 | }); 149 | 150 | for ( var i = 0; i < 3; i++ ) { 151 | var rotor1 = new Zdog.Anchor({ 152 | addTo: tetrahedron, 153 | rotate: { y: TAU/3 * -i }, 154 | }); 155 | var rotor2 = new Zdog.Anchor({ 156 | addTo: rotor1, 157 | translate: { z: inradius, y: height/2 }, 158 | rotate: { x: Math.acos( 1/3 ) * -1 + TAU/4 }, 159 | }); 160 | triangle.copy({ 161 | addTo: rotor2, 162 | translate: { y: -inradius }, 163 | color: [ gold, garnet, orange ][i], 164 | }); 165 | } 166 | 167 | triangle.rotate.set({ x: -TAU/4, z: -TAU/2 }); 168 | 169 | } )(); 170 | 171 | // ----- octahedron ----- // 172 | 173 | ( function() { 174 | 175 | var octahedron = new Zdog.Anchor({ 176 | addTo: illo, 177 | translate: { x: -4, y: 4 }, 178 | scale: 1.75, 179 | }); 180 | 181 | solids.push( octahedron ); 182 | 183 | var colorWheel = [ eggplant, garnet, orange, gold, yellow ]; 184 | 185 | // radius of triangle with side length = 1 186 | var radius = ROOT3/2 * 2/3; 187 | var height = radius * 3/2; 188 | var tilt = Math.asin( 0.5/height ); 189 | 190 | [ -1, 1 ].forEach( function( ySide ) { 191 | for ( var i = 0; i < 4; i++ ) { 192 | var rotor = new Zdog.Anchor({ 193 | addTo: octahedron, 194 | rotate: { y: TAU/4 * ( i + 1.5 ) * -1 }, 195 | }); 196 | 197 | var anchor = new Zdog.Anchor({ 198 | addTo: rotor, 199 | translate: { z: 0.5 }, 200 | rotate: { x: tilt * ySide }, 201 | // scale: { y: -ySide }, 202 | }); 203 | 204 | new Zdog.Polygon({ 205 | sides: 3, 206 | radius: radius, 207 | addTo: anchor, 208 | translate: { y: -radius/2 * ySide }, 209 | scale: { y: ySide }, 210 | stroke: false, 211 | fill: true, 212 | color: colorWheel[ i + 0.5 + 0.5 * ySide ], 213 | backface: false, 214 | }); 215 | } 216 | } ); 217 | 218 | } )(); 219 | 220 | // ----- cube ----- // 221 | 222 | var cube = new Zdog.Box({ 223 | addTo: illo, 224 | width: 2, 225 | height: 2, 226 | depth: 2, 227 | translate: { x: 4, y: 0 }, 228 | topFace: yellow, 229 | frontFace: gold, 230 | leftFace: orange, 231 | rightFace: orange, 232 | rearFace: garnet, 233 | bottomFace: eggplant, 234 | stroke: false, 235 | }); 236 | 237 | solids.push( cube ); 238 | 239 | // ----- dodecahedron ----- // 240 | 241 | ( function() { 242 | 243 | var dodecahedron = new Zdog.Anchor({ 244 | addTo: illo, 245 | translate: { x: 0, y: 4 }, 246 | scale: 0.75, 247 | }); 248 | 249 | solids.push( dodecahedron ); 250 | 251 | // https://en.wikipedia.org/wiki/Regular_dodecahedron#Dimensions 252 | var midradius = ( PHI * PHI )/2; 253 | 254 | // top & bottom faces 255 | var face = new Zdog.Polygon({ 256 | sides: 5, 257 | radius: 1, 258 | addTo: dodecahedron, 259 | translate: { y: -midradius }, 260 | rotate: { x: TAU/4 }, 261 | fill: true, 262 | stroke: false, 263 | color: yellow, 264 | // backface: false, 265 | }); 266 | 267 | face.copy({ 268 | translate: { y: midradius }, 269 | rotate: { x: -TAU/4 }, 270 | color: eggplant, 271 | }); 272 | 273 | [ -1, 1 ].forEach( function( ySide ) { 274 | 275 | var colorWheel = { 276 | '-1': [ eggplant, garnet, gold, orange, garnet ], 277 | 1: [ yellow, gold, garnet, orange, gold ], 278 | }[ ySide ]; 279 | 280 | for ( var i = 0; i < 5; i++ ) { 281 | var rotor1 = new Zdog.Anchor({ 282 | addTo: dodecahedron, 283 | rotate: { y: TAU/5 * i }, 284 | }); 285 | var rotor2 = new Zdog.Anchor({ 286 | addTo: rotor1, 287 | rotate: { x: TAU/4 * ySide - Math.atan( 2 ) }, 288 | }); 289 | 290 | face.copy({ 291 | addTo: rotor2, 292 | translate: { z: midradius }, 293 | rotate: { z: TAU/2 }, 294 | color: colorWheel[i], 295 | }); 296 | } 297 | } ); 298 | 299 | } )(); 300 | 301 | // ----- isocahedron ----- // 302 | 303 | ( function() { 304 | 305 | var isocahedron = new Zdog.Anchor({ 306 | addTo: illo, 307 | translate: { x: 4, y: 4 }, 308 | scale: 1.2, 309 | }); 310 | 311 | solids.push( isocahedron ); 312 | 313 | // geometry 314 | // radius of triangle with side length = 1 315 | var faceRadius = ROOT3/2 * 2/3; 316 | var faceHeight = faceRadius * 3/2; 317 | var capApothem = 0.5 / Math.tan( TAU/10 ); 318 | var capRadius = 0.5 / Math.sin( TAU/10 ); 319 | var capTilt = Math.asin( capApothem/faceHeight ); 320 | var capSagitta = capRadius - capApothem; 321 | var sideTilt = Math.asin( capSagitta/faceHeight ); 322 | var sideHeight = Math.sqrt( faceHeight * faceHeight - capSagitta * capSagitta ); 323 | 324 | // var colorWheel = [ eggplant, garnet, orange, gold, yellow ]; 325 | 326 | [ -1, 1 ].forEach( function( ySide ) { 327 | var capColors = { 328 | '-1': [ garnet, gold, yellow, gold, orange ], 329 | 1: [ gold, garnet, eggplant, garnet, orange ], 330 | }[ ySide ]; 331 | 332 | var sideColors = { 333 | '-1': [ garnet, gold, yellow, orange, garnet ], 334 | 1: [ gold, garnet, eggplant, orange, orange ], 335 | }[ ySide ]; 336 | 337 | for ( var i = 0; i < 5; i++ ) { 338 | var rotor = new Zdog.Anchor({ 339 | addTo: isocahedron, 340 | rotate: { y: TAU/5 * -i }, 341 | translate: { y: sideHeight/2 * ySide }, 342 | }); 343 | 344 | var capRotateX = -capTilt; 345 | var isYPos = ySide > 0; 346 | capRotateX += isYPos ? TAU/2 : 0; 347 | 348 | var capAnchor = new Zdog.Anchor({ 349 | addTo: rotor, 350 | translate: { z: capApothem * ySide }, 351 | rotate: { x: capRotateX }, 352 | }); 353 | 354 | // cap face 355 | var face = new Zdog.Polygon({ 356 | sides: 3, 357 | radius: faceRadius, 358 | addTo: capAnchor, 359 | translate: { y: -faceRadius/2 }, 360 | stroke: false, 361 | fill: true, 362 | color: capColors[i], 363 | // backface: false, 364 | }); 365 | 366 | var sideRotateX = -sideTilt; 367 | sideRotateX += isYPos ? 0 : TAU/2; 368 | var sideAnchor = capAnchor.copy({ 369 | rotate: { x: sideRotateX }, 370 | }); 371 | 372 | face.copy({ 373 | addTo: sideAnchor, 374 | translate: { y: -faceRadius/2 }, 375 | rotate: { y: TAU/2 }, 376 | color: sideColors[i], 377 | }); 378 | 379 | } 380 | } ); 381 | 382 | } )(); 383 | 384 | // ----- animate ----- // 385 | 386 | var keyframes = [ 387 | { x: 0, y: 0 }, 388 | { x: 0, y: TAU }, 389 | { x: TAU, y: TAU }, 390 | ]; 391 | 392 | var ticker = 0; 393 | var cycleCount = 180; 394 | var turnLimit = keyframes.length - 1; 395 | 396 | function animate() { 397 | update(); 398 | illo.renderGraph(); 399 | requestAnimationFrame( animate ); 400 | } 401 | 402 | animate(); 403 | 404 | function update() { 405 | 406 | if ( isSpinning ) { 407 | var progress = ticker/cycleCount; 408 | var tween = Zdog.easeInOut( progress % 1, 4 ); 409 | var turn = Math.floor( progress % turnLimit ); 410 | var keyA = keyframes[ turn ]; 411 | var keyB = keyframes[ turn + 1 ]; 412 | viewRotation.x = Zdog.lerp( keyA.x, keyB.x, tween ); 413 | viewRotation.y = Zdog.lerp( keyA.y, keyB.y, tween ); 414 | ticker++; 415 | } 416 | 417 | solids.forEach( function( solid ) { 418 | solid.rotate.set( viewRotation ); 419 | } ); 420 | 421 | illo.updateGraph(); 422 | } 423 | 424 | // ----- inputs ----- // 425 | 426 | var dragStartRX, dragStartRY; 427 | 428 | new Zdog.Dragger({ 429 | startElement: illoElem, 430 | onDragStart: function() { 431 | isSpinning = false; 432 | dragStartRX = viewRotation.x; 433 | dragStartRY = viewRotation.y; 434 | }, 435 | onDragMove: function( pointer, moveX, moveY ) { 436 | viewRotation.x = dragStartRX - ( moveY/displaySize * TAU ); 437 | viewRotation.y = dragStartRY - ( moveX/displaySize * TAU ); 438 | }, 439 | }); 440 | -------------------------------------------------------------------------------- /demos/strutter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | strutter 8 | 9 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /demos/strutter/strutter.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var sceneSize = 48; 4 | var isSpinning = true; 5 | var TAU = Zdog.TAU; 6 | // colors 7 | var gold = '#EA0'; 8 | var orange = '#C25'; 9 | var eggplant = '#636'; 10 | var midnight = '#424'; 11 | 12 | var illo = new Zdog.Illustration({ 13 | element: '.illo', 14 | rotate: { y: -TAU/8 }, 15 | translate: { y: 4 }, 16 | dragRotate: true, 17 | resize: 'fullscreen', 18 | onDragStart: function() { 19 | isSpinning = false; 20 | }, 21 | onResize: function( width, height ) { 22 | this.zoom = Math.floor( Math.min( width, height ) / sceneSize ); 23 | }, 24 | }); 25 | 26 | // ----- model ----- // 27 | 28 | var hipX = 3; 29 | 30 | new Zdog.Shape({ 31 | addTo: illo, 32 | path: [ { x: -1 }, { x: 1 } ], 33 | scale: hipX, 34 | color: eggplant, 35 | stroke: 4, 36 | }); 37 | 38 | var rightLeg = new Zdog.Shape({ 39 | addTo: illo, 40 | path: [ { y: 0 }, { y: 12 } ], 41 | translate: { x: -hipX }, 42 | rotate: { x: TAU/4 }, 43 | color: eggplant, 44 | stroke: 4, 45 | }); 46 | // foot 47 | new Zdog.RoundedRect({ 48 | addTo: rightLeg, 49 | width: 2, 50 | height: 4, 51 | cornerRadius: 1, 52 | translate: { y: 14, z: 2 }, 53 | rotate: { x: TAU/4 }, 54 | color: orange, 55 | fill: true, 56 | stroke: 4, 57 | }); 58 | 59 | var plantAngle = -TAU/32 * 3; 60 | var leftLeg = rightLeg.copyGraph({ 61 | translate: { x: hipX }, 62 | rotate: { x: plantAngle }, 63 | color: midnight, 64 | }); 65 | 66 | leftLeg.children[0].rotate.set({ x: TAU/4 - plantAngle }); 67 | 68 | // chest 69 | new Zdog.Shape({ 70 | addTo: illo, 71 | path: [ { x: -1 }, { x: 1 } ], 72 | scale: 1.5, 73 | translate: { y: -5.5, z: -3 }, 74 | color: orange, 75 | stroke: 9, 76 | fill: true, 77 | }); 78 | 79 | var armSize = 6; 80 | 81 | [ true, false ].forEach( function( isRight ) { 82 | var xSide = isRight ? -1 : 1; 83 | 84 | var upperArm = new Zdog.Shape({ 85 | addTo: illo, 86 | path: [ { x: 0 }, { x: armSize } ], 87 | scale: { x: xSide }, 88 | translate: { x: 4.5 * xSide, y: -8, z: -4 }, 89 | rotate: isRight ? { y: TAU/8, z: -TAU/16 } : { y: TAU/8 }, 90 | color: eggplant, 91 | stroke: 4, 92 | }); 93 | 94 | var forearm = new Zdog.Shape({ 95 | addTo: upperArm, 96 | path: [ { x: 0 }, { x: armSize - 2 } ], 97 | translate: { x: armSize }, 98 | rotate: isRight ? { z: TAU/16 * 3, y: TAU/4 } : 99 | { z: -TAU/4, x: -TAU/32 * 2, y: TAU/8 }, 100 | color: orange, 101 | stroke: 4, 102 | }); 103 | // hand 104 | new Zdog.Shape({ 105 | addTo: forearm, 106 | translate: { x: armSize, z: 1 }, 107 | stroke: 6, 108 | color: gold, 109 | }); 110 | 111 | } ); 112 | 113 | var head = new Zdog.Anchor({ 114 | addTo: illo, 115 | translate: { y: -12, z: -10 }, 116 | rotate: { x: TAU/8 }, 117 | }); 118 | 119 | // face 120 | new Zdog.Hemisphere({ 121 | addTo: head, 122 | diameter: 12, 123 | color: gold, 124 | backface: orange, 125 | rotate: { x: -TAU/4 }, 126 | stroke: false, 127 | }); 128 | 129 | var eye = new Zdog.Ellipse({ 130 | addTo: head, 131 | diameter: 2, 132 | quarters: 2, 133 | translate: { x: -2, y: 1.5, z: 5 }, 134 | rotate: { z: -TAU/4 }, 135 | color: eggplant, 136 | stroke: 0.5, 137 | backface: false, 138 | }); 139 | eye.copy({ 140 | translate: { x: 2, y: 1.5, z: 5 }, 141 | rotate: { z: -TAU/4 }, 142 | }); 143 | // smile 144 | new Zdog.Ellipse({ 145 | addTo: head, 146 | diameter: 3, 147 | quarters: 2, 148 | translate: { y: 3, z: 4.5 }, 149 | rotate: { z: TAU/4 }, 150 | closed: true, 151 | color: '#FED', 152 | stroke: 0.5, 153 | fill: true, 154 | backface: false, 155 | }); 156 | 157 | new Zdog.Hemisphere({ 158 | addTo: head, 159 | diameter: 12, 160 | color: orange, 161 | backface: gold, 162 | rotate: { x: TAU/4 }, 163 | stroke: false, 164 | }); 165 | 166 | var brim = new Zdog.Anchor({ 167 | addTo: head, 168 | scale: 5.5, 169 | translate: { y: -0.5, z: 6 }, 170 | }); 171 | 172 | new Zdog.Shape({ 173 | addTo: brim, 174 | path: [ 175 | { x: 0, z: 0 }, 176 | { arc: [ 177 | { x: -1, z: 0 }, 178 | { x: -1, z: -1 }, 179 | ] }, 180 | { x: -1, z: 0 }, 181 | ], 182 | color: eggplant, 183 | fill: true, 184 | }); 185 | 186 | new Zdog.Shape({ 187 | addTo: brim, 188 | path: [ 189 | { x: -1, z: 0 }, 190 | { arc: [ 191 | { x: -1, z: 1 }, 192 | { x: 0, z: 1 }, 193 | ] }, 194 | { x: 0, z: 0 }, 195 | ], 196 | color: eggplant, 197 | fill: true, 198 | }); 199 | 200 | brim.copyGraph({ 201 | scale: brim.scale.copy().multiply({ x: -1 }), 202 | }); 203 | 204 | // ----- animate ----- // 205 | 206 | var ticker = 0; 207 | var cycleCount = 150; 208 | 209 | function animate() { 210 | if ( isSpinning ) { 211 | var progress = ticker/cycleCount; 212 | illo.rotate.y = Zdog.easeInOut( progress % 1, 4 ) * TAU - TAU/8; 213 | ticker++; 214 | } 215 | illo.updateRenderGraph(); 216 | requestAnimationFrame( animate ); 217 | } 218 | 219 | animate(); 220 | -------------------------------------------------------------------------------- /demos/zdog-logo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | zdog logo 8 | 9 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /demos/zdog-logo/zdog-logo.js: -------------------------------------------------------------------------------- 1 | // ----- setup ----- // 2 | 3 | var sceneSize = 100; 4 | var isSpinning = true; 5 | var TAU = Zdog.TAU; 6 | var initRotate = { x: 20/360 * TAU, y: -50/360 * TAU }; 7 | var orange = '#E62'; 8 | var gold = '#EA0'; 9 | var eggplant = '#636'; 10 | var depth = 20; 11 | var lineWidth = 8; 12 | 13 | var illo = new Zdog.Illustration({ 14 | element: '.illo', 15 | rotate: initRotate, 16 | dragRotate: true, 17 | resize: 'fullscreen', 18 | onDragStart: function() { 19 | isSpinning = false; 20 | }, 21 | onResize: function( width, height ) { 22 | this.zoom = Math.floor( Math.min( width, height ) * 2/sceneSize ) / 2; 23 | }, 24 | }); 25 | 26 | // ----- model ----- // 27 | 28 | var bigGroup = new Zdog.Group({ 29 | addTo: illo, 30 | }); 31 | 32 | var backGroup = new Zdog.Group({ 33 | addTo: bigGroup, 34 | updateSort: true, 35 | }); 36 | 37 | // top 38 | var topSide = new Zdog.Rect({ 39 | addTo: backGroup, 40 | width: 40, 41 | height: depth, 42 | translate: { y: -20 }, 43 | rotate: { x: TAU/4 }, 44 | fill: true, 45 | stroke: lineWidth, 46 | color: orange, 47 | }); 48 | topSide.copy({ 49 | translate: { y: 20 }, 50 | rotate: { x: -TAU/4 }, 51 | }); 52 | 53 | var endCap = new Zdog.Rect({ 54 | addTo: backGroup, 55 | width: depth, 56 | height: 8, 57 | translate: { x: -20, y: -16 }, 58 | rotate: { y: TAU/4 }, 59 | fill: true, 60 | color: orange, 61 | stroke: lineWidth, 62 | backface: false, 63 | }); 64 | endCap.copy({ 65 | translate: { x: 20, y: 16 }, 66 | rotate: { y: -TAU/4 }, 67 | }); 68 | 69 | var cornerCap = endCap.copy({ 70 | height: 10, 71 | translate: { x: -20, y: 15 }, 72 | }); 73 | cornerCap.copy({ 74 | translate: { x: 20, y: -15 }, 75 | rotate: { y: -TAU/4 }, 76 | }); 77 | 78 | var underside = new Zdog.Rect({ 79 | addTo: backGroup, 80 | width: 30, 81 | height: depth, 82 | translate: { x: -5, y: -12 }, 83 | rotate: { x: -TAU/4 }, 84 | stroke: lineWidth, 85 | fill: true, 86 | color: orange, 87 | }); 88 | underside.copy({ 89 | translate: { x: 5, y: 12 }, 90 | rotate: { x: TAU/4 }, 91 | }); 92 | 93 | var slopeW = 30; 94 | var slopeH = 22; 95 | var slopeAngle = Math.atan( slopeH/slopeW ); 96 | 97 | var slope = new Zdog.Rect({ 98 | addTo: backGroup, 99 | width: Math.sqrt( slopeH * slopeH + slopeW * slopeW ), 100 | height: depth, 101 | translate: { x: -5, y: -1 }, 102 | rotate: { x: TAU/4, y: slopeAngle }, 103 | stroke: lineWidth, 104 | fill: true, 105 | color: orange, 106 | backface: false, 107 | }); 108 | 109 | slope.copy({ 110 | translate: { x: 5, y: 1 }, 111 | rotate: { x: -TAU/4, y: -slopeAngle }, 112 | }); 113 | 114 | // tail 115 | new Zdog.Ellipse({ 116 | addTo: backGroup, 117 | diameter: 32, 118 | quarters: 1, 119 | closed: false, 120 | translate: { x: 22, y: -4 }, 121 | rotate: { z: TAU/4 }, 122 | color: orange, 123 | stroke: lineWidth, 124 | }); 125 | 126 | // tongue 127 | 128 | var tongueAnchor = new Zdog.Anchor({ 129 | addTo: backGroup, 130 | translate: { x: -6, y: -7 }, 131 | rotate: { y: TAU/4 }, 132 | 133 | }); 134 | 135 | var tongueH = 12; 136 | var tongueS = 5; 137 | var tongueTip = tongueH + tongueS; 138 | 139 | new Zdog.Shape({ 140 | addTo: tongueAnchor, 141 | path: [ 142 | { x: -tongueS, y: 0 }, 143 | { x: tongueS, y: 0 }, 144 | { x: tongueS, y: tongueH }, 145 | { arc: [ 146 | { x: tongueS, y: tongueTip }, 147 | { x: 0, y: tongueTip }, 148 | ] }, 149 | { arc: [ 150 | { x: -tongueS, y: tongueTip }, 151 | { x: -tongueS, y: tongueH }, 152 | ] }, 153 | ], 154 | rotate: { x: TAU/4 - Math.atan( 16/22 ) }, 155 | fill: true, 156 | stroke: 4, 157 | color: eggplant, 158 | 159 | }); 160 | 161 | var foreGroup = new Zdog.Group({ 162 | addTo: bigGroup, 163 | updateSort: true, 164 | }); 165 | 166 | var zFace = new Zdog.Shape({ 167 | addTo: foreGroup, 168 | path: [ 169 | { x: -20, y: -20 }, 170 | { x: 20, y: -20 }, 171 | { x: 20, y: -10 }, 172 | { x: -10, y: 12 }, 173 | { x: 20, y: 12 }, 174 | { x: 20, y: 20 }, 175 | { x: -20, y: 20 }, 176 | { x: -20, y: 10 }, 177 | { x: 10, y: -12 }, 178 | { x: -20, y: -12 }, 179 | ], 180 | translate: { z: depth/2 }, 181 | fill: true, 182 | color: gold, 183 | stroke: lineWidth, 184 | backface: false, 185 | }); 186 | 187 | zFace.copy({ 188 | scale: { x: -1 }, 189 | translate: { z: -depth/2 }, 190 | rotate: { y: TAU/2 }, 191 | }); 192 | 193 | // nose 194 | var semiCircle = new Zdog.Ellipse({ 195 | addTo: backGroup, 196 | quarters: 2, 197 | scale: 8, 198 | translate: { x: -26, y: -20 }, 199 | rotate: { y: TAU/4, z: TAU/4 }, 200 | fill: true, 201 | stroke: 5, 202 | color: eggplant, 203 | closed: true, 204 | // backface: false, 205 | }); 206 | 207 | // ears 208 | // group & extra shape are hacks 209 | var earGroup = new Zdog.Group({ 210 | addTo: illo, 211 | }); 212 | 213 | var ear = semiCircle.copy({ 214 | addTo: earGroup, 215 | quarters: 2, 216 | scale: 24, 217 | rotate: { z: -TAU/16, x: TAU/16 }, 218 | translate: { x: 10, y: -14, z: depth }, 219 | }); 220 | 221 | new Zdog.Shape({ 222 | visible: false, 223 | addTo: ear, 224 | translate: { z: 0.5, x: -0.5 }, 225 | }); 226 | 227 | earGroup.copyGraph({ 228 | scale: { z: -1 }, 229 | }); 230 | 231 | // ----- animate ----- // 232 | 233 | var keyframes = [ 234 | { y: 0 + initRotate.y, z: 0 }, 235 | { y: TAU + initRotate.y, z: 0 }, 236 | { y: TAU + initRotate.y, z: TAU }, 237 | ]; 238 | 239 | var ticker = 0; 240 | var cycleCount = 180; 241 | var turnLimit = keyframes.length - 1; 242 | 243 | function animate() { 244 | spin(); 245 | illo.updateRenderGraph(); 246 | requestAnimationFrame( animate ); 247 | } 248 | 249 | function spin() { 250 | if ( !isSpinning ) { 251 | return; 252 | } 253 | var progress = ticker/cycleCount; 254 | var tween = Zdog.easeInOut( progress % 1, 4 ); 255 | var turn = Math.floor( progress % turnLimit ); 256 | var keyA = keyframes[ turn ]; 257 | var keyB = keyframes[ turn + 1 ]; 258 | illo.rotate.y = Zdog.lerp( keyA.y, keyB.y, tween ); 259 | illo.rotate.z = Zdog.lerp( keyA.z, keyB.z, tween ); 260 | ticker++; 261 | } 262 | 263 | animate(); 264 | 265 | -------------------------------------------------------------------------------- /dist/zdog.dist.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Zdog v1.1.3 3 | * Round, flat, designer-friendly pseudo-3D engine 4 | * Licensed MIT 5 | * https://zzz.dog 6 | * Copyright 2020 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/=.5;var n=s[e]||s[2];var o=n(i);o/=2;return r?o:1-o};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.CanvasRenderer=e()}})(this,function t(){var n={isCanvas:true};n.begin=function(t){t.beginPath()};n.move=function(t,e,r){t.moveTo(r.x,r.y)};n.line=function(t,e,r){t.lineTo(r.x,r.y)};n.bezier=function(t,e,r,i,n){t.bezierCurveTo(r.x,r.y,i.x,i.y,n.x,n.y)};n.closePath=function(t){t.closePath()};n.setPath=function(){};n.renderPath=function(e,r,t,i){this.begin(e,r);t.forEach(function(t){t.render(e,r,n)});if(i){this.closePath(e,r)}};n.stroke=function(t,e,r,i,n){if(!r){return}t.strokeStyle=i;t.lineWidth=n;t.stroke()};n.fill=function(t,e,r,i){if(!r){return}t.fillStyle=i;t.fill()};n.end=function(){};return n});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.SvgRenderer=e()}})(this,function t(){var o={isSvg:true};var e=o.round=function(t){return Math.round(t*1e3)/1e3};function s(t){return e(t.x)+","+e(t.y)+" "}o.begin=function(){};o.move=function(t,e,r){return"M"+s(r)};o.line=function(t,e,r){return"L"+s(r)};o.bezier=function(t,e,r,i,n){return"C"+s(r)+s(i)+s(n)};o.closePath=function(){return"Z"};o.setPath=function(t,e,r){e.setAttribute("d",r)};o.renderPath=function(e,r,t,i){var n="";t.forEach(function(t){n+=t.render(e,r,o)});if(i){n+=this.closePath(e,r)}this.setPath(e,r,n)};o.stroke=function(t,e,r,i,n){if(!r){return}e.setAttribute("stroke",i);e.setAttribute("stroke-width",n)};o.fill=function(t,e,r,i){var n=r?i:"none";e.setAttribute("fill",n)};o.end=function(t,e){t.appendChild(e)};return o});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"))}else{var r=t.Zdog;r.Vector=e(r)}})(this,function t(r){function e(t){this.set(t)}var h=r.TAU;e.prototype.set=function(t){this.x=t&&t.x||0;this.y=t&&t.y||0;this.z=t&&t.z||0;return this};e.prototype.write=function(t){if(!t){return this}this.x=t.x!=undefined?t.x:this.x;this.y=t.y!=undefined?t.y:this.y;this.z=t.z!=undefined?t.z:this.z;return this};e.prototype.rotate=function(t){if(!t){return}this.rotateZ(t.z);this.rotateY(t.y);this.rotateX(t.x);return this};e.prototype.rotateZ=function(t){i(this,t,"x","y")};e.prototype.rotateX=function(t){i(this,t,"y","z")};e.prototype.rotateY=function(t){i(this,t,"x","z")};function i(t,e,r,i){if(!e||e%h===0){return}var n=Math.cos(e);var o=Math.sin(e);var s=t[r];var a=t[i];t[r]=s*n-a*o;t[i]=a*n+s*o}e.prototype.isSame=function(t){if(!t){return false}return this.x===t.x&&this.y===t.y&&this.z===t.z};e.prototype.add=function(t){if(!t){return this}this.x+=t.x||0;this.y+=t.y||0;this.z+=t.z||0;return this};e.prototype.subtract=function(t){if(!t){return this}this.x-=t.x||0;this.y-=t.y||0;this.z-=t.z||0;return this};e.prototype.multiply=function(t){if(t==undefined){return this}if(typeof t=="number"){this.x*=t;this.y*=t;this.z*=t}else{this.x*=t.x!=undefined?t.x:1;this.y*=t.y!=undefined?t.y:1;this.z*=t.z!=undefined?t.z:1}return this};e.prototype.transform=function(t,e,r){this.multiply(r);this.rotate(e);this.add(t);return this};e.prototype.lerp=function(t,e){this.x=r.lerp(this.x,t.x||0,e);this.y=r.lerp(this.y,t.y||0,e);this.z=r.lerp(this.z,t.z||0,e);return this};e.prototype.magnitude=function(){var t=this.x*this.x+this.y*this.y+this.z*this.z;return n(t)};function n(t){if(Math.abs(t-1)<1e-8){return 1}return Math.sqrt(t)}e.prototype.magnitude2d=function(){var t=this.x*this.x+this.y*this.y;return n(t)};e.prototype.copy=function(){return new e(this)};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./vector"),require("./canvas-renderer"),require("./svg-renderer"))}else{var r=t.Zdog;r.Anchor=e(r,r.Vector,r.CanvasRenderer,r.SvgRenderer)}})(this,function t(n,e,r,i){var o=n.TAU;var s={x:1,y:1,z:1};function a(t){this.create(t||{})}a.prototype.create=function(t){this.children=[];n.extend(this,this.constructor.defaults);this.setOptions(t);this.translate=new e(t.translate);this.rotate=new e(t.rotate);this.scale=new e(s).multiply(this.scale);this.origin=new e;this.renderOrigin=new e;if(this.addTo){this.addTo.addChild(this)}};a.defaults={};a.optionKeys=Object.keys(a.defaults).concat(["rotate","translate","scale","addTo"]);a.prototype.setOptions=function(t){var e=this.constructor.optionKeys;for(var r in t){if(e.indexOf(r)!=-1){this[r]=t[r]}}};a.prototype.addChild=function(t){if(this.children.indexOf(t)!=-1){return}t.remove();t.addTo=this;this.children.push(t)};a.prototype.removeChild=function(t){var e=this.children.indexOf(t);if(e!=-1){this.children.splice(e,1)}};a.prototype.remove=function(){if(this.addTo){this.addTo.removeChild(this)}};a.prototype.update=function(){this.reset();this.children.forEach(function(t){t.update()});this.transform(this.translate,this.rotate,this.scale)};a.prototype.reset=function(){this.renderOrigin.set(this.origin)};a.prototype.transform=function(e,r,i){this.renderOrigin.transform(e,r,i);this.children.forEach(function(t){t.transform(e,r,i)})};a.prototype.updateGraph=function(){this.update();this.updateFlatGraph();this.flatGraph.forEach(function(t){t.updateSortValue()});this.flatGraph.sort(a.shapeSorter)};a.shapeSorter=function(t,e){return t.sortValue-e.sortValue};Object.defineProperty(a.prototype,"flatGraph",{get:function(){if(!this._flatGraph){this.updateFlatGraph()}return this._flatGraph},set:function(t){this._flatGraph=t}});a.prototype.updateFlatGraph=function(){this.flatGraph=this.getFlatGraph()};a.prototype.getFlatGraph=function(){var t=[this];return this.addChildFlatGraph(t)};a.prototype.addChildFlatGraph=function(r){this.children.forEach(function(t){var e=t.getFlatGraph();Array.prototype.push.apply(r,e)});return r};a.prototype.updateSortValue=function(){this.sortValue=this.renderOrigin.z};a.prototype.render=function(){};a.prototype.renderGraphCanvas=function(e){if(!e){throw new Error("ctx is "+e+". "+"Canvas context required for render. Check .renderGraphCanvas( ctx ).")}this.flatGraph.forEach(function(t){t.render(e,r)})};a.prototype.renderGraphSvg=function(e){if(!e){throw new Error("svg is "+e+". "+"SVG required for render. Check .renderGraphSvg( svg ).")}this.flatGraph.forEach(function(t){t.render(e,i)})};a.prototype.copy=function(t){var e={};var r=this.constructor.optionKeys;r.forEach(function(t){e[t]=this[t]},this);n.extend(e,t);var i=this.constructor;return new i(e)};a.prototype.copyGraph=function(t){var e=this.copy(t);this.children.forEach(function(t){t.copyGraph({addTo:e})});return e};a.prototype.normalizeRotate=function(){this.rotate.x=n.modulo(this.rotate.x,o);this.rotate.y=n.modulo(this.rotate.y,o);this.rotate.z=n.modulo(this.rotate.z,o)};function h(r){return function(t){function e(t){this.create(t||{})}e.prototype=Object.create(r.prototype);e.prototype.constructor=e;e.defaults=n.extend({},r.defaults);n.extend(e.defaults,t);e.optionKeys=r.optionKeys.slice(0);Object.keys(e.defaults).forEach(function(t){if(!e.optionKeys.indexOf(t)!=1){e.optionKeys.push(t)}});e.subclass=h(e);return e}}a.subclass=h(a);return a});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e()}else{t.Zdog.Dragger=e()}})(this,function t(){var r=typeof window!="undefined";var e="mousedown";var i="mousemove";var n="mouseup";if(r){if(window.PointerEvent){e="pointerdown";i="pointermove";n="pointerup"}else if("ontouchstart"in window){e="touchstart";i="touchmove";n="touchend"}}function o(){}function s(t){this.create(t||{})}s.prototype.create=function(t){this.onDragStart=t.onDragStart||o;this.onDragMove=t.onDragMove||o;this.onDragEnd=t.onDragEnd||o;this.bindDrag(t.startElement)};s.prototype.bindDrag=function(t){t=this.getQueryElement(t);if(!t){return}t.style.touchAction="none";t.addEventListener(e,this)};s.prototype.getQueryElement=function(t){if(typeof t=="string"){t=document.querySelector(t)}return t};s.prototype.handleEvent=function(t){var e=this["on"+t.type];if(e){e.call(this,t)}};s.prototype.onmousedown=s.prototype.onpointerdown=function(t){this.dragStart(t,t)};s.prototype.ontouchstart=function(t){this.dragStart(t,t.changedTouches[0])};s.prototype.dragStart=function(t,e){t.preventDefault();this.dragStartX=e.pageX;this.dragStartY=e.pageY;if(r){window.addEventListener(i,this);window.addEventListener(n,this)}this.onDragStart(e)};s.prototype.ontouchmove=function(t){this.dragMove(t,t.changedTouches[0])};s.prototype.onmousemove=s.prototype.onpointermove=function(t){this.dragMove(t,t)};s.prototype.dragMove=function(t,e){t.preventDefault();var r=e.pageX-this.dragStartX;var i=e.pageY-this.dragStartY;this.onDragMove(e,r,i)};s.prototype.onmouseup=s.prototype.onpointerup=s.prototype.ontouchend=s.prototype.dragEnd=function(){window.removeEventListener(i,this);window.removeEventListener(n,this);this.onDragEnd()};return s});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./anchor"),require("./dragger"))}else{var r=t.Zdog;r.Illustration=e(r,r.Anchor,r.Dragger)}})(this,function t(e,r,a){function i(){}var h=e.TAU;var n=r.subclass({element:undefined,centered:true,zoom:1,dragRotate:false,resize:false,onPrerender:i,onDragStart:i,onDragMove:i,onDragEnd:i,onResize:i});e.extend(n.prototype,a.prototype);n.prototype.create=function(t){r.prototype.create.call(this,t);a.prototype.create.call(this,t);this.setElement(this.element);this.setDragRotate(this.dragRotate);this.setResize(this.resize)};n.prototype.setElement=function(t){t=this.getQueryElement(t);if(!t){throw new Error("Zdog.Illustration element required. Set to "+t)}var e=t.nodeName.toLowerCase();if(e=="canvas"){this.setCanvas(t)}else if(e=="svg"){this.setSvg(t)}};n.prototype.setSize=function(t,e){t=Math.round(t);e=Math.round(e);if(this.isCanvas){this.setSizeCanvas(t,e)}else if(this.isSvg){this.setSizeSvg(t,e)}};n.prototype.setResize=function(t){this.resize=t;if(!this.resizeListener){this.resizeListener=this.onWindowResize.bind(this)}if(t){window.addEventListener("resize",this.resizeListener);this.onWindowResize()}else{window.removeEventListener("resize",this.resizeListener)}};n.prototype.onWindowResize=function(){this.setMeasuredSize();this.onResize(this.width,this.height)};n.prototype.setMeasuredSize=function(){var t,e;var r=this.resize=="fullscreen";if(r){t=window.innerWidth;e=window.innerHeight}else{var i=this.element.getBoundingClientRect();t=i.width;e=i.height}this.setSize(t,e)};n.prototype.renderGraph=function(t){if(this.isCanvas){this.renderGraphCanvas(t)}else if(this.isSvg){this.renderGraphSvg(t)}};n.prototype.updateRenderGraph=function(t){this.updateGraph();this.renderGraph(t)};n.prototype.setCanvas=function(t){this.element=t;this.isCanvas=true;this.ctx=this.element.getContext("2d");this.setSizeCanvas(t.width,t.height)};n.prototype.setSizeCanvas=function(t,e){this.width=t;this.height=e;var r=this.pixelRatio=window.devicePixelRatio||1;this.element.width=this.canvasWidth=t*r;this.element.height=this.canvasHeight=e*r;var i=r>1&&!this.resize;if(i){this.element.style.width=t+"px";this.element.style.height=e+"px"}};n.prototype.renderGraphCanvas=function(t){t=t||this;this.prerenderCanvas();r.prototype.renderGraphCanvas.call(t,this.ctx);this.postrenderCanvas()};n.prototype.prerenderCanvas=function(){var t=this.ctx;t.lineCap="round";t.lineJoin="round";t.clearRect(0,0,this.canvasWidth,this.canvasHeight);t.save();if(this.centered){var e=this.width/2*this.pixelRatio;var r=this.height/2*this.pixelRatio;t.translate(e,r)}var i=this.pixelRatio*this.zoom;t.scale(i,i);this.onPrerender(t)};n.prototype.postrenderCanvas=function(){this.ctx.restore()};n.prototype.setSvg=function(t){this.element=t;this.isSvg=true;this.pixelRatio=1;var e=t.getAttribute("width");var r=t.getAttribute("height");this.setSizeSvg(e,r)};n.prototype.setSizeSvg=function(t,e){this.width=t;this.height=e;var r=t/this.zoom;var i=e/this.zoom;var n=this.centered?-r/2:0;var o=this.centered?-i/2:0;this.element.setAttribute("viewBox",n+" "+o+" "+r+" "+i);if(this.resize){this.element.removeAttribute("width");this.element.removeAttribute("height")}else{this.element.setAttribute("width",t);this.element.setAttribute("height",e)}};n.prototype.renderGraphSvg=function(t){t=t||this;o(this.element);this.onPrerender(this.element);r.prototype.renderGraphSvg.call(t,this.element)};function o(t){while(t.firstChild){t.removeChild(t.firstChild)}}n.prototype.setDragRotate=function(t){if(!t){return}else if(t===true){t=this}this.dragRotate=t;this.bindDrag(this.element)};n.prototype.dragStart=function(){this.dragStartRX=this.dragRotate.rotate.x;this.dragStartRY=this.dragRotate.rotate.y;a.prototype.dragStart.apply(this,arguments)};n.prototype.dragMove=function(t,e){var r=e.pageX-this.dragStartX;var i=e.pageY-this.dragStartY;var n=Math.min(this.width,this.height);var o=r/n*h;var s=i/n*h;this.dragRotate.rotate.x=this.dragStartRX-s;this.dragRotate.rotate.y=this.dragStartRY-o;a.prototype.dragMove.apply(this,arguments)};return n});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./vector"))}else{var r=t.Zdog;r.PathCommand=e(r.Vector)}})(this,function t(i){function e(t,e,r){this.method=t;this.points=e.map(n);this.renderPoints=e.map(o);this.previousPoint=r;this.endRenderPoint=this.renderPoints[this.renderPoints.length-1];if(t=="arc"){this.controlPoints=[new i,new i]}}function n(t){if(t instanceof i){return t}else{return new i(t)}}function o(t){return new i(t)}e.prototype.reset=function(){var i=this.points;this.renderPoints.forEach(function(t,e){var r=i[e];t.set(r)})};e.prototype.transform=function(e,r,i){this.renderPoints.forEach(function(t){t.transform(e,r,i)})};e.prototype.render=function(t,e,r){return this[this.method](t,e,r)};e.prototype.move=function(t,e,r){return r.move(t,e,this.renderPoints[0])};e.prototype.line=function(t,e,r){return r.line(t,e,this.renderPoints[0])};e.prototype.bezier=function(t,e,r){var i=this.renderPoints[0];var n=this.renderPoints[1];var o=this.renderPoints[2];return r.bezier(t,e,i,n,o)};var h=9/16;e.prototype.arc=function(t,e,r){var i=this.previousPoint;var n=this.renderPoints[0];var o=this.renderPoints[1];var s=this.controlPoints[0];var a=this.controlPoints[1];s.set(i).lerp(n,h);a.set(o).lerp(n,h);return r.bezier(t,e,s,a,o)};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./vector"),require("./path-command"),require("./anchor"))}else{var r=t.Zdog;r.Shape=e(r,r.Vector,r.PathCommand,r.Anchor)}})(this,function t(e,r,p,i){var n=i.subclass({stroke:1,fill:false,color:"#333",closed:true,visible:true,path:[{}],front:{z:1},backface:true});n.prototype.create=function(t){i.prototype.create.call(this,t);this.updatePath();this.front=new r(t.front||this.front);this.renderFront=new r(this.front);this.renderNormal=new r};var d=["move","line","bezier","arc"];n.prototype.updatePath=function(){this.setPath();this.updatePathCommands()};n.prototype.setPath=function(){};n.prototype.updatePathCommands=function(){var u;this.pathCommands=this.path.map(function(t,e){var r=Object.keys(t);var i=r[0];var n=t[i];var o=r.length==1&&d.indexOf(i)!=-1;if(!o){i="line";n=t}var s=i=="line"||i=="move";var a=Array.isArray(n);if(s&&!a){n=[n]}i=e===0?"move":i;var h=new p(i,n,u);u=h.endRenderPoint;return h})};n.prototype.reset=function(){this.renderOrigin.set(this.origin);this.renderFront.set(this.front);this.pathCommands.forEach(function(t){t.reset()})};n.prototype.transform=function(e,r,i){this.renderOrigin.transform(e,r,i);this.renderFront.transform(e,r,i);this.renderNormal.set(this.renderOrigin).subtract(this.renderFront);this.pathCommands.forEach(function(t){t.transform(e,r,i)});this.children.forEach(function(t){t.transform(e,r,i)})};n.prototype.updateSortValue=function(){var t=this.pathCommands.length;var e=this.pathCommands[0].endRenderPoint;var r=this.pathCommands[t-1].endRenderPoint;var i=t>2&&e.isSame(r);if(i){t-=1}var n=0;for(var o=0;o0;if(!this.backface&&this.isFacingBack){return}if(!e){throw new Error("Zdog renderer required. Set to "+e)}var i=r==1;if(e.isCanvas&&i){this.renderCanvasDot(t,e)}else{this.renderPath(t,e)}};var o=e.TAU;n.prototype.renderCanvasDot=function(t){var e=this.getLineWidth();if(!e){return}t.fillStyle=this.getRenderColor();var r=this.pathCommands[0].endRenderPoint;t.beginPath();var i=e/2;t.arc(r.x,r.y,i,0,o);t.fill()};n.prototype.getLineWidth=function(){if(!this.stroke){return 0}if(this.stroke==true){return 1}return this.stroke};n.prototype.getRenderColor=function(){var t=typeof this.backface=="string"&&this.isFacingBack;var e=t?this.backface:this.color;return e};n.prototype.renderPath=function(t,e){var r=this.getRenderElement(t,e);var i=this.pathCommands.length==2&&this.pathCommands[1].method=="line";var n=!i&&this.closed;var o=this.getRenderColor();e.renderPath(t,r,this.pathCommands,n);e.stroke(t,r,this.stroke,o,this.getLineWidth());e.fill(t,r,this.fill,o);e.end(t,r)};var s="http://www.w3.org/2000/svg";n.prototype.getRenderElement=function(t,e){if(!e.isSvg){return}if(!this.svgElement){this.svgElement=document.createElementNS(s,"path");this.svgElement.setAttribute("stroke-linecap","round");this.svgElement.setAttribute("stroke-linejoin","round")}return this.svgElement};return n});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./anchor"))}else{var r=t.Zdog;r.Group=e(r.Anchor)}})(this,function t(r){var e=r.subclass({updateSort:false,visible:true});e.prototype.updateSortValue=function(){var e=0;this.flatGraph.forEach(function(t){t.updateSortValue();e+=t.sortValue});this.sortValue=e/this.flatGraph.length;if(this.updateSort){this.flatGraph.sort(r.shapeSorter)}};e.prototype.render=function(e,r){if(!this.visible){return}this.flatGraph.forEach(function(t){t.render(e,r)})};e.prototype.updateFlatGraph=function(){var t=[];this.flatGraph=this.addChildFlatGraph(t)};e.prototype.getFlatGraph=function(){return[this]};return e});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./shape"))}else{var r=t.Zdog;r.Rect=e(r.Shape)}})(this,function t(e){var r=e.subclass({width:1,height:1});r.prototype.setPath=function(){var t=this.width/2;var e=this.height/2;this.path=[{x:-t,y:-e},{x:t,y:-e},{x:t,y:e},{x:-t,y:e}]};return r});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./shape"))}else{var r=t.Zdog;r.RoundedRect=e(r.Shape)}})(this,function t(e){var r=e.subclass({width:1,height:1,cornerRadius:.25,closed:false});r.prototype.setPath=function(){var t=this.width/2;var e=this.height/2;var r=Math.min(t,e);var i=Math.min(this.cornerRadius,r);var n=t-i;var o=e-i;var s=[{x:n,y:-e},{arc:[{x:t,y:-e},{x:t,y:-o}]}];if(o){s.push({x:t,y:o})}s.push({arc:[{x:t,y:e},{x:n,y:e}]});if(n){s.push({x:-n,y:e})}s.push({arc:[{x:-t,y:e},{x:-t,y:o}]});if(o){s.push({x:-t,y:-o})}s.push({arc:[{x:-t,y:-e},{x:-n,y:-e}]});if(n){s.push({x:n,y:-e})}this.path=s};return r});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./shape"))}else{var r=t.Zdog;r.Ellipse=e(r.Shape)}})(this,function t(e){var r=e.subclass({diameter:1,width:undefined,height:undefined,quarters:4,closed:false});r.prototype.setPath=function(){var t=this.width!=undefined?this.width:this.diameter;var e=this.height!=undefined?this.height:this.diameter;var r=t/2;var i=e/2;this.path=[{x:0,y:-i},{arc:[{x:r,y:-i},{x:r,y:0}]}];if(this.quarters>1){this.path.push({arc:[{x:r,y:i},{x:0,y:i}]})}if(this.quarters>2){this.path.push({arc:[{x:-r,y:i},{x:-r,y:0}]})}if(this.quarters>3){this.path.push({arc:[{x:-r,y:-i},{x:0,y:-i}]})}};return r});(function(t,e){if(typeof module=="object"&&module.exports){module.exports=e(require("./boilerplate"),require("./shape"))}else{var r=t.Zdog;r.Polygon=e(r,r.Shape)}})(this,function t(e,r){var i=r.subclass({sides:3,radius:.5});var n=e.TAU;i.prototype.setPath=function(){this.path=[];for(var t=0;t 1 ) { 40 | this.path.push({ arc: [ 41 | { x: x, y: y }, 42 | { x: 0, y: y }, 43 | ] }); 44 | } 45 | // bottom left 46 | if ( this.quarters > 2 ) { 47 | this.path.push({ arc: [ 48 | { x: -x, y: y }, 49 | { x: -x, y: 0 }, 50 | ] }); 51 | } 52 | // top left 53 | if ( this.quarters > 3 ) { 54 | this.path.push({ arc: [ 55 | { x: -x, y: -y }, 56 | { x: 0, y: -y }, 57 | ] }); 58 | } 59 | }; 60 | 61 | return Ellipse; 62 | 63 | } ) ); 64 | -------------------------------------------------------------------------------- /js/group.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Group 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory( require('./anchor') ); 10 | } else { 11 | // browser global 12 | var Zdog = root.Zdog; 13 | Zdog.Group = factory( Zdog.Anchor ); 14 | } 15 | }( this, function factory( Anchor ) { 16 | 17 | var Group = Anchor.subclass({ 18 | updateSort: false, 19 | visible: true, 20 | }); 21 | 22 | // ----- update ----- // 23 | 24 | Group.prototype.updateSortValue = function() { 25 | var sortValueTotal = 0; 26 | this.flatGraph.forEach( function( item ) { 27 | item.updateSortValue(); 28 | sortValueTotal += item.sortValue; 29 | } ); 30 | // average sort value of all points 31 | // def not geometrically correct, but works for me 32 | this.sortValue = sortValueTotal / this.flatGraph.length; 33 | 34 | if ( this.updateSort ) { 35 | this.flatGraph.sort( Anchor.shapeSorter ); 36 | } 37 | }; 38 | 39 | // ----- render ----- // 40 | 41 | Group.prototype.render = function( ctx, renderer ) { 42 | if ( !this.visible ) { 43 | return; 44 | } 45 | 46 | this.flatGraph.forEach( function( item ) { 47 | item.render( ctx, renderer ); 48 | } ); 49 | }; 50 | 51 | // actual group flatGraph only used inside group 52 | Group.prototype.updateFlatGraph = function() { 53 | // do not include self 54 | var flatGraph = []; 55 | this.flatGraph = this.addChildFlatGraph( flatGraph ); 56 | }; 57 | 58 | // do not include children, group handles rendering & sorting internally 59 | Group.prototype.getFlatGraph = function() { 60 | return [ this ]; 61 | }; 62 | 63 | return Group; 64 | 65 | } ) ); 66 | -------------------------------------------------------------------------------- /js/hemisphere.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Hemisphere composite shape 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory( require('./boilerplate'), require('./vector'), 10 | require('./anchor'), require('./ellipse') ); 11 | } else { 12 | // browser global 13 | var Zdog = root.Zdog; 14 | Zdog.Hemisphere = factory( Zdog, Zdog.Vector, Zdog.Anchor, Zdog.Ellipse ); 15 | } 16 | }( this, function factory( utils, Vector, Anchor, Ellipse ) { 17 | 18 | var Hemisphere = Ellipse.subclass({ 19 | fill: true, 20 | }); 21 | 22 | var TAU = utils.TAU; 23 | 24 | Hemisphere.prototype.create = function( /* options */) { 25 | // call super 26 | Ellipse.prototype.create.apply( this, arguments ); 27 | // composite shape, create child shapes 28 | this.apex = new Anchor({ 29 | addTo: this, 30 | translate: { z: this.diameter / 2 }, 31 | }); 32 | // vector used for calculation 33 | this.renderCentroid = new Vector(); 34 | }; 35 | 36 | Hemisphere.prototype.updateSortValue = function() { 37 | // centroid of hemisphere is 3/8 between origin and apex 38 | this.renderCentroid.set( this.renderOrigin ) 39 | .lerp( this.apex.renderOrigin, 3/8 ); 40 | this.sortValue = this.renderCentroid.z; 41 | }; 42 | 43 | Hemisphere.prototype.render = function( ctx, renderer ) { 44 | this.renderDome( ctx, renderer ); 45 | // call super 46 | Ellipse.prototype.render.apply( this, arguments ); 47 | }; 48 | 49 | Hemisphere.prototype.renderDome = function( ctx, renderer ) { 50 | if ( !this.visible ) { 51 | return; 52 | } 53 | var elem = this.getDomeRenderElement( ctx, renderer ); 54 | var contourAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x ); 55 | var domeRadius = this.diameter / 2 * this.renderNormal.magnitude(); 56 | var x = this.renderOrigin.x; 57 | var y = this.renderOrigin.y; 58 | 59 | if ( renderer.isCanvas ) { 60 | // canvas 61 | var startAngle = contourAngle + TAU/4; 62 | var endAngle = contourAngle - TAU/4; 63 | ctx.beginPath(); 64 | ctx.arc( x, y, domeRadius, startAngle, endAngle ); 65 | } else if ( renderer.isSvg ) { 66 | // svg 67 | contourAngle = ( contourAngle - TAU/4 ) / TAU * 360; 68 | this.domeSvgElement.setAttribute( 'd', 'M ' + -domeRadius + ',0 A ' + 69 | domeRadius + ',' + domeRadius + ' 0 0 1 ' + domeRadius + ',0' ); 70 | this.domeSvgElement.setAttribute( 'transform', 71 | 'translate(' + x + ',' + y + ' ) rotate(' + contourAngle + ')' ); 72 | } 73 | 74 | renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() ); 75 | renderer.fill( ctx, elem, this.fill, this.color ); 76 | renderer.end( ctx, elem ); 77 | }; 78 | 79 | var svgURI = 'http://www.w3.org/2000/svg'; 80 | 81 | Hemisphere.prototype.getDomeRenderElement = function( ctx, renderer ) { 82 | if ( !renderer.isSvg ) { 83 | return; 84 | } 85 | if ( !this.domeSvgElement ) { 86 | // create svgElement 87 | this.domeSvgElement = document.createElementNS( svgURI, 'path' ); 88 | this.domeSvgElement.setAttribute( 'stroke-linecap', 'round' ); 89 | this.domeSvgElement.setAttribute( 'stroke-linejoin', 'round' ); 90 | } 91 | return this.domeSvgElement; 92 | }; 93 | 94 | return Hemisphere; 95 | 96 | } ) ); 97 | -------------------------------------------------------------------------------- /js/illustration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Illustration 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory( require('./boilerplate'), require('./anchor'), 10 | require('./dragger') ); 11 | } else { 12 | // browser global 13 | var Zdog = root.Zdog; 14 | Zdog.Illustration = factory( Zdog, Zdog.Anchor, Zdog.Dragger ); 15 | } 16 | }( this, function factory( utils, Anchor, Dragger ) { 17 | 18 | function noop() {} 19 | var TAU = utils.TAU; 20 | 21 | var Illustration = Anchor.subclass({ 22 | element: undefined, 23 | centered: true, 24 | zoom: 1, 25 | dragRotate: false, 26 | resize: false, 27 | onPrerender: noop, 28 | onDragStart: noop, 29 | onDragMove: noop, 30 | onDragEnd: noop, 31 | onResize: noop, 32 | }); 33 | 34 | utils.extend( Illustration.prototype, Dragger.prototype ); 35 | 36 | Illustration.prototype.create = function( options ) { 37 | Anchor.prototype.create.call( this, options ); 38 | Dragger.prototype.create.call( this, options ); 39 | this.setElement( this.element ); 40 | this.setDragRotate( this.dragRotate ); 41 | this.setResize( this.resize ); 42 | }; 43 | 44 | Illustration.prototype.setElement = function( element ) { 45 | element = this.getQueryElement( element ); 46 | if ( !element ) { 47 | throw new Error( 'Zdog.Illustration element required. Set to ' + element ); 48 | } 49 | 50 | var nodeName = element.nodeName.toLowerCase(); 51 | if ( nodeName == 'canvas' ) { 52 | this.setCanvas( element ); 53 | } else if ( nodeName == 'svg' ) { 54 | this.setSvg( element ); 55 | } 56 | }; 57 | 58 | Illustration.prototype.setSize = function( width, height ) { 59 | width = Math.round( width ); 60 | height = Math.round( height ); 61 | if ( this.isCanvas ) { 62 | this.setSizeCanvas( width, height ); 63 | } else if ( this.isSvg ) { 64 | this.setSizeSvg( width, height ); 65 | } 66 | }; 67 | 68 | Illustration.prototype.setResize = function( resize ) { 69 | this.resize = resize; 70 | // create resize event listener 71 | if ( !this.resizeListener ) { 72 | this.resizeListener = this.onWindowResize.bind( this ); 73 | } 74 | // add/remove event listener 75 | if ( resize ) { 76 | window.addEventListener( 'resize', this.resizeListener ); 77 | this.onWindowResize(); 78 | } else { 79 | window.removeEventListener( 'resize', this.resizeListener ); 80 | } 81 | }; 82 | 83 | // TODO debounce this? 84 | Illustration.prototype.onWindowResize = function() { 85 | this.setMeasuredSize(); 86 | this.onResize( this.width, this.height ); 87 | }; 88 | 89 | Illustration.prototype.setMeasuredSize = function() { 90 | var width, height; 91 | var isFullscreen = this.resize == 'fullscreen'; 92 | if ( isFullscreen ) { 93 | width = window.innerWidth; 94 | height = window.innerHeight; 95 | } else { 96 | var rect = this.element.getBoundingClientRect(); 97 | width = rect.width; 98 | height = rect.height; 99 | } 100 | this.setSize( width, height ); 101 | }; 102 | 103 | // ----- render ----- // 104 | 105 | Illustration.prototype.renderGraph = function( item ) { 106 | if ( this.isCanvas ) { 107 | this.renderGraphCanvas( item ); 108 | } else if ( this.isSvg ) { 109 | this.renderGraphSvg( item ); 110 | } 111 | }; 112 | 113 | // combo method 114 | Illustration.prototype.updateRenderGraph = function( item ) { 115 | this.updateGraph(); 116 | this.renderGraph( item ); 117 | }; 118 | 119 | // ----- canvas ----- // 120 | 121 | Illustration.prototype.setCanvas = function( element ) { 122 | this.element = element; 123 | this.isCanvas = true; 124 | // update related properties 125 | this.ctx = this.element.getContext('2d'); 126 | // set initial size 127 | this.setSizeCanvas( element.width, element.height ); 128 | }; 129 | 130 | Illustration.prototype.setSizeCanvas = function( width, height ) { 131 | this.width = width; 132 | this.height = height; 133 | // up-rez for hi-DPI devices 134 | var pixelRatio = this.pixelRatio = window.devicePixelRatio || 1; 135 | this.element.width = this.canvasWidth = width * pixelRatio; 136 | this.element.height = this.canvasHeight = height * pixelRatio; 137 | var needsHighPixelRatioSizing = pixelRatio > 1 && !this.resize; 138 | if ( needsHighPixelRatioSizing ) { 139 | this.element.style.width = width + 'px'; 140 | this.element.style.height = height + 'px'; 141 | } 142 | }; 143 | 144 | Illustration.prototype.renderGraphCanvas = function( item ) { 145 | item = item || this; 146 | this.prerenderCanvas(); 147 | Anchor.prototype.renderGraphCanvas.call( item, this.ctx ); 148 | this.postrenderCanvas(); 149 | }; 150 | 151 | Illustration.prototype.prerenderCanvas = function() { 152 | var ctx = this.ctx; 153 | ctx.lineCap = 'round'; 154 | ctx.lineJoin = 'round'; 155 | ctx.clearRect( 0, 0, this.canvasWidth, this.canvasHeight ); 156 | ctx.save(); 157 | if ( this.centered ) { 158 | var centerX = this.width / 2 * this.pixelRatio; 159 | var centerY = this.height / 2 * this.pixelRatio; 160 | ctx.translate( centerX, centerY ); 161 | } 162 | var scale = this.pixelRatio * this.zoom; 163 | ctx.scale( scale, scale ); 164 | this.onPrerender( ctx ); 165 | }; 166 | 167 | Illustration.prototype.postrenderCanvas = function() { 168 | this.ctx.restore(); 169 | }; 170 | 171 | // ----- svg ----- // 172 | 173 | Illustration.prototype.setSvg = function( element ) { 174 | this.element = element; 175 | this.isSvg = true; 176 | this.pixelRatio = 1; 177 | // set initial size from width & height attributes 178 | var width = element.getAttribute('width'); 179 | var height = element.getAttribute('height'); 180 | this.setSizeSvg( width, height ); 181 | }; 182 | 183 | Illustration.prototype.setSizeSvg = function( width, height ) { 184 | this.width = width; 185 | this.height = height; 186 | var viewWidth = width / this.zoom; 187 | var viewHeight = height / this.zoom; 188 | var viewX = this.centered ? -viewWidth/2 : 0; 189 | var viewY = this.centered ? -viewHeight/2 : 0; 190 | this.element.setAttribute( 'viewBox', viewX + ' ' + viewY + ' ' + 191 | viewWidth + ' ' + viewHeight ); 192 | if ( this.resize ) { 193 | // remove size attributes, let size be determined by viewbox 194 | this.element.removeAttribute('width'); 195 | this.element.removeAttribute('height'); 196 | } else { 197 | this.element.setAttribute( 'width', width ); 198 | this.element.setAttribute( 'height', height ); 199 | } 200 | }; 201 | 202 | Illustration.prototype.renderGraphSvg = function( item ) { 203 | item = item || this; 204 | empty( this.element ); 205 | this.onPrerender( this.element ); 206 | Anchor.prototype.renderGraphSvg.call( item, this.element ); 207 | }; 208 | 209 | function empty( element ) { 210 | while ( element.firstChild ) { 211 | element.removeChild( element.firstChild ); 212 | } 213 | } 214 | 215 | // ----- drag ----- // 216 | 217 | Illustration.prototype.setDragRotate = function( item ) { 218 | if ( !item ) { 219 | return; 220 | } else if ( item === true ) { 221 | /* eslint consistent-this: "off" */ 222 | item = this; 223 | } 224 | this.dragRotate = item; 225 | 226 | this.bindDrag( this.element ); 227 | }; 228 | 229 | Illustration.prototype.dragStart = function( /* event, pointer */) { 230 | this.dragStartRX = this.dragRotate.rotate.x; 231 | this.dragStartRY = this.dragRotate.rotate.y; 232 | Dragger.prototype.dragStart.apply( this, arguments ); 233 | }; 234 | 235 | Illustration.prototype.dragMove = function( event, pointer ) { 236 | var moveX = pointer.pageX - this.dragStartX; 237 | var moveY = pointer.pageY - this.dragStartY; 238 | var displaySize = Math.min( this.width, this.height ); 239 | var moveRY = moveX/displaySize * TAU; 240 | var moveRX = moveY/displaySize * TAU; 241 | this.dragRotate.rotate.x = this.dragStartRX - moveRX; 242 | this.dragRotate.rotate.y = this.dragStartRY - moveRY; 243 | Dragger.prototype.dragMove.apply( this, arguments ); 244 | }; 245 | 246 | return Illustration; 247 | 248 | } ) ); 249 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Index 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory( 10 | require('./boilerplate'), 11 | require('./canvas-renderer'), 12 | require('./svg-renderer'), 13 | require('./vector'), 14 | require('./anchor'), 15 | require('./dragger'), 16 | require('./illustration'), 17 | require('./path-command'), 18 | require('./shape'), 19 | require('./group'), 20 | require('./rect'), 21 | require('./rounded-rect'), 22 | require('./ellipse'), 23 | require('./polygon'), 24 | require('./hemisphere'), 25 | require('./cylinder'), 26 | require('./cone'), 27 | require('./box') 28 | ); 29 | } else if ( typeof define == 'function' && define.amd ) { 30 | /* globals define */ // AMD 31 | define( 'zdog', [], root.Zdog ); 32 | } 33 | /* eslint-disable max-params */ 34 | } )( this, function factory( Zdog, CanvasRenderer, SvgRenderer, Vector, Anchor, 35 | Dragger, Illustration, PathCommand, Shape, Group, Rect, RoundedRect, 36 | Ellipse, Polygon, Hemisphere, Cylinder, Cone, Box ) { 37 | /* eslint-enable max-params */ 38 | 39 | Zdog.CanvasRenderer = CanvasRenderer; 40 | Zdog.SvgRenderer = SvgRenderer; 41 | Zdog.Vector = Vector; 42 | Zdog.Anchor = Anchor; 43 | Zdog.Dragger = Dragger; 44 | Zdog.Illustration = Illustration; 45 | Zdog.PathCommand = PathCommand; 46 | Zdog.Shape = Shape; 47 | Zdog.Group = Group; 48 | Zdog.Rect = Rect; 49 | Zdog.RoundedRect = RoundedRect; 50 | Zdog.Ellipse = Ellipse; 51 | Zdog.Polygon = Polygon; 52 | Zdog.Hemisphere = Hemisphere; 53 | Zdog.Cylinder = Cylinder; 54 | Zdog.Cone = Cone; 55 | Zdog.Box = Box; 56 | 57 | return Zdog; 58 | } ); 59 | -------------------------------------------------------------------------------- /js/path-command.js: -------------------------------------------------------------------------------- 1 | /** 2 | * PathCommand 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory( require('./vector') ); 10 | } else { 11 | // browser global 12 | var Zdog = root.Zdog; 13 | Zdog.PathCommand = factory( Zdog.Vector ); 14 | } 15 | }( this, function factory( Vector ) { 16 | 17 | function PathCommand( method, points, previousPoint ) { 18 | this.method = method; 19 | this.points = points.map( mapVectorPoint ); 20 | this.renderPoints = points.map( mapNewVector ); 21 | this.previousPoint = previousPoint; 22 | this.endRenderPoint = this.renderPoints[ this.renderPoints.length - 1 ]; 23 | // arc actions come with previous point & corner point 24 | // but require bezier control points 25 | if ( method == 'arc' ) { 26 | this.controlPoints = [ new Vector(), new Vector() ]; 27 | } 28 | } 29 | 30 | function mapVectorPoint( point ) { 31 | if ( point instanceof Vector ) { 32 | return point; 33 | } else { 34 | return new Vector( point ); 35 | } 36 | } 37 | 38 | function mapNewVector( point ) { 39 | return new Vector( point ); 40 | } 41 | 42 | PathCommand.prototype.reset = function() { 43 | // reset renderPoints back to orignal points position 44 | var points = this.points; 45 | this.renderPoints.forEach( function( renderPoint, i ) { 46 | var point = points[i]; 47 | renderPoint.set( point ); 48 | } ); 49 | }; 50 | 51 | PathCommand.prototype.transform = function( translation, rotation, scale ) { 52 | this.renderPoints.forEach( function( renderPoint ) { 53 | renderPoint.transform( translation, rotation, scale ); 54 | } ); 55 | }; 56 | 57 | PathCommand.prototype.render = function( ctx, elem, renderer ) { 58 | return this[ this.method ]( ctx, elem, renderer ); 59 | }; 60 | 61 | PathCommand.prototype.move = function( ctx, elem, renderer ) { 62 | return renderer.move( ctx, elem, this.renderPoints[0] ); 63 | }; 64 | 65 | PathCommand.prototype.line = function( ctx, elem, renderer ) { 66 | return renderer.line( ctx, elem, this.renderPoints[0] ); 67 | }; 68 | 69 | PathCommand.prototype.bezier = function( ctx, elem, renderer ) { 70 | var cp0 = this.renderPoints[0]; 71 | var cp1 = this.renderPoints[1]; 72 | var end = this.renderPoints[2]; 73 | return renderer.bezier( ctx, elem, cp0, cp1, end ); 74 | }; 75 | 76 | var arcHandleLength = 9/16; 77 | 78 | PathCommand.prototype.arc = function( ctx, elem, renderer ) { 79 | var prev = this.previousPoint; 80 | var corner = this.renderPoints[0]; 81 | var end = this.renderPoints[1]; 82 | var cp0 = this.controlPoints[0]; 83 | var cp1 = this.controlPoints[1]; 84 | cp0.set( prev ).lerp( corner, arcHandleLength ); 85 | cp1.set( end ).lerp( corner, arcHandleLength ); 86 | return renderer.bezier( ctx, elem, cp0, cp1, end ); 87 | }; 88 | 89 | return PathCommand; 90 | 91 | } ) ); 92 | -------------------------------------------------------------------------------- /js/polygon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Shape 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory( require('./boilerplate'), require('./shape') ); 10 | } else { 11 | // browser global 12 | var Zdog = root.Zdog; 13 | Zdog.Polygon = factory( Zdog, Zdog.Shape ); 14 | } 15 | }( this, function factory( utils, Shape ) { 16 | 17 | var Polygon = Shape.subclass({ 18 | sides: 3, 19 | radius: 0.5, 20 | }); 21 | 22 | var TAU = utils.TAU; 23 | 24 | Polygon.prototype.setPath = function() { 25 | this.path = []; 26 | for ( var i = 0; i < this.sides; i++ ) { 27 | var theta = i / this.sides * TAU - TAU/4; 28 | var x = Math.cos( theta ) * this.radius; 29 | var y = Math.sin( theta ) * this.radius; 30 | this.path.push({ x: x, y: y }); 31 | } 32 | }; 33 | 34 | return Polygon; 35 | 36 | } ) ); 37 | -------------------------------------------------------------------------------- /js/rect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rect 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory( require('./shape') ); 10 | } else { 11 | // browser global 12 | var Zdog = root.Zdog; 13 | Zdog.Rect = factory( Zdog.Shape ); 14 | } 15 | }( this, function factory( Shape ) { 16 | 17 | var Rect = Shape.subclass({ 18 | width: 1, 19 | height: 1, 20 | }); 21 | 22 | Rect.prototype.setPath = function() { 23 | var x = this.width / 2; 24 | var y = this.height / 2; 25 | /* eslint key-spacing: "off" */ 26 | this.path = [ 27 | { x: -x, y: -y }, 28 | { x: x, y: -y }, 29 | { x: x, y: y }, 30 | { x: -x, y: y }, 31 | ]; 32 | }; 33 | 34 | return Rect; 35 | 36 | } ) ); 37 | -------------------------------------------------------------------------------- /js/rounded-rect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * RoundedRect 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory( require('./shape') ); 10 | } else { 11 | // browser global 12 | var Zdog = root.Zdog; 13 | Zdog.RoundedRect = factory( Zdog.Shape ); 14 | } 15 | }( this, function factory( Shape ) { 16 | 17 | var RoundedRect = Shape.subclass({ 18 | width: 1, 19 | height: 1, 20 | cornerRadius: 0.25, 21 | closed: false, 22 | }); 23 | 24 | RoundedRect.prototype.setPath = function() { 25 | /* eslint 26 | id-length: [ "error", { "min": 2, "exceptions": [ "x", "y" ] }], 27 | key-spacing: "off" */ 28 | var xA = this.width / 2; 29 | var yA = this.height / 2; 30 | var shortSide = Math.min( xA, yA ); 31 | var cornerRadius = Math.min( this.cornerRadius, shortSide ); 32 | var xB = xA - cornerRadius; 33 | var yB = yA - cornerRadius; 34 | var path = [ 35 | // top right corner 36 | { x: xB, y: -yA }, 37 | { arc: [ 38 | { x: xA, y: -yA }, 39 | { x: xA, y: -yB }, 40 | ] }, 41 | ]; 42 | // bottom right corner 43 | if ( yB ) { 44 | path.push({ x: xA, y: yB }); 45 | } 46 | path.push({ arc: [ 47 | { x: xA, y: yA }, 48 | { x: xB, y: yA }, 49 | ] }); 50 | // bottom left corner 51 | if ( xB ) { 52 | path.push({ x: -xB, y: yA }); 53 | } 54 | path.push({ arc: [ 55 | { x: -xA, y: yA }, 56 | { x: -xA, y: yB }, 57 | ] }); 58 | // top left corner 59 | if ( yB ) { 60 | path.push({ x: -xA, y: -yB }); 61 | } 62 | path.push({ arc: [ 63 | { x: -xA, y: -yA }, 64 | { x: -xB, y: -yA }, 65 | ] }); 66 | 67 | // back to top right corner 68 | if ( xB ) { 69 | path.push({ x: xB, y: -yA }); 70 | } 71 | 72 | this.path = path; 73 | }; 74 | 75 | return RoundedRect; 76 | 77 | } ) ); 78 | -------------------------------------------------------------------------------- /js/shape.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Shape 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory( require('./boilerplate'), require('./vector'), 10 | require('./path-command'), require('./anchor') ); 11 | } else { 12 | // browser global 13 | var Zdog = root.Zdog; 14 | Zdog.Shape = factory( Zdog, Zdog.Vector, Zdog.PathCommand, Zdog.Anchor ); 15 | } 16 | }( this, function factory( utils, Vector, PathCommand, Anchor ) { 17 | 18 | var Shape = Anchor.subclass({ 19 | stroke: 1, 20 | fill: false, 21 | color: '#333', 22 | closed: true, 23 | visible: true, 24 | path: [ {} ], 25 | front: { z: 1 }, 26 | backface: true, 27 | }); 28 | 29 | Shape.prototype.create = function( options ) { 30 | Anchor.prototype.create.call( this, options ); 31 | this.updatePath(); 32 | // front 33 | this.front = new Vector( options.front || this.front ); 34 | this.renderFront = new Vector( this.front ); 35 | this.renderNormal = new Vector(); 36 | }; 37 | 38 | var actionNames = [ 39 | 'move', 40 | 'line', 41 | 'bezier', 42 | 'arc', 43 | ]; 44 | 45 | Shape.prototype.updatePath = function() { 46 | this.setPath(); 47 | this.updatePathCommands(); 48 | }; 49 | 50 | // place holder for Ellipse, Rect, etc. 51 | Shape.prototype.setPath = function() {}; 52 | 53 | // parse path into PathCommands 54 | Shape.prototype.updatePathCommands = function() { 55 | var previousPoint; 56 | this.pathCommands = this.path.map( function( pathPart, i ) { 57 | // pathPart can be just vector coordinates -> { x, y, z } 58 | // or path instruction -> { arc: [ {x0,y0,z0}, {x1,y1,z1} ] } 59 | var keys = Object.keys( pathPart ); 60 | var method = keys[0]; 61 | var points = pathPart[ method ]; 62 | // default to line if no instruction 63 | var isInstruction = keys.length == 1 && actionNames.indexOf( method ) != -1; 64 | if ( !isInstruction ) { 65 | method = 'line'; 66 | points = pathPart; 67 | } 68 | // munge single-point methods like line & move without arrays 69 | var isLineOrMove = method == 'line' || method == 'move'; 70 | var isPointsArray = Array.isArray( points ); 71 | if ( isLineOrMove && !isPointsArray ) { 72 | points = [ points ]; 73 | } 74 | 75 | // first action is always move 76 | method = i === 0 ? 'move' : method; 77 | // arcs require previous last point 78 | var command = new PathCommand( method, points, previousPoint ); 79 | // update previousLastPoint 80 | previousPoint = command.endRenderPoint; 81 | return command; 82 | } ); 83 | }; 84 | 85 | // ----- update ----- // 86 | 87 | Shape.prototype.reset = function() { 88 | this.renderOrigin.set( this.origin ); 89 | this.renderFront.set( this.front ); 90 | // reset command render points 91 | this.pathCommands.forEach( function( command ) { 92 | command.reset(); 93 | } ); 94 | }; 95 | 96 | Shape.prototype.transform = function( translation, rotation, scale ) { 97 | // calculate render points backface visibility & cone/hemisphere shapes 98 | this.renderOrigin.transform( translation, rotation, scale ); 99 | this.renderFront.transform( translation, rotation, scale ); 100 | this.renderNormal.set( this.renderOrigin ).subtract( this.renderFront ); 101 | // transform points 102 | this.pathCommands.forEach( function( command ) { 103 | command.transform( translation, rotation, scale ); 104 | } ); 105 | // transform children 106 | this.children.forEach( function( child ) { 107 | child.transform( translation, rotation, scale ); 108 | } ); 109 | }; 110 | 111 | Shape.prototype.updateSortValue = function() { 112 | // sort by average z of all points 113 | // def not geometrically correct, but works for me 114 | var pointCount = this.pathCommands.length; 115 | var firstPoint = this.pathCommands[0].endRenderPoint; 116 | var lastPoint = this.pathCommands[ pointCount - 1 ].endRenderPoint; 117 | // ignore the final point if self closing shape 118 | var isSelfClosing = pointCount > 2 && firstPoint.isSame( lastPoint ); 119 | if ( isSelfClosing ) { 120 | pointCount -= 1; 121 | } 122 | 123 | var sortValueTotal = 0; 124 | for ( var i = 0; i < pointCount; i++ ) { 125 | sortValueTotal += this.pathCommands[i].endRenderPoint.z; 126 | } 127 | this.sortValue = sortValueTotal/pointCount; 128 | }; 129 | 130 | // ----- render ----- // 131 | 132 | Shape.prototype.render = function( ctx, renderer ) { 133 | var length = this.pathCommands.length; 134 | if ( !this.visible || !length ) { 135 | return; 136 | } 137 | // do not render if hiding backface 138 | this.isFacingBack = this.renderNormal.z > 0; 139 | if ( !this.backface && this.isFacingBack ) { 140 | return; 141 | } 142 | if ( !renderer ) { 143 | throw new Error( 'Zdog renderer required. Set to ' + renderer ); 144 | } 145 | // render dot or path 146 | var isDot = length == 1; 147 | if ( renderer.isCanvas && isDot ) { 148 | this.renderCanvasDot( ctx, renderer ); 149 | } else { 150 | this.renderPath( ctx, renderer ); 151 | } 152 | }; 153 | 154 | var TAU = utils.TAU; 155 | // Safari does not render lines with no size, have to render circle instead 156 | Shape.prototype.renderCanvasDot = function( ctx ) { 157 | var lineWidth = this.getLineWidth(); 158 | if ( !lineWidth ) { 159 | return; 160 | } 161 | ctx.fillStyle = this.getRenderColor(); 162 | var point = this.pathCommands[0].endRenderPoint; 163 | ctx.beginPath(); 164 | var radius = lineWidth/2; 165 | ctx.arc( point.x, point.y, radius, 0, TAU ); 166 | ctx.fill(); 167 | }; 168 | 169 | Shape.prototype.getLineWidth = function() { 170 | if ( !this.stroke ) { 171 | return 0; 172 | } 173 | if ( this.stroke == true ) { 174 | return 1; 175 | } 176 | return this.stroke; 177 | }; 178 | 179 | Shape.prototype.getRenderColor = function() { 180 | // use backface color if applicable 181 | var isBackfaceColor = typeof this.backface == 'string' && this.isFacingBack; 182 | var color = isBackfaceColor ? this.backface : this.color; 183 | return color; 184 | }; 185 | 186 | Shape.prototype.renderPath = function( ctx, renderer ) { 187 | var elem = this.getRenderElement( ctx, renderer ); 188 | var isTwoPoints = this.pathCommands.length == 2 && 189 | this.pathCommands[1].method == 'line'; 190 | var isClosed = !isTwoPoints && this.closed; 191 | var color = this.getRenderColor(); 192 | 193 | renderer.renderPath( ctx, elem, this.pathCommands, isClosed ); 194 | renderer.stroke( ctx, elem, this.stroke, color, this.getLineWidth() ); 195 | renderer.fill( ctx, elem, this.fill, color ); 196 | renderer.end( ctx, elem ); 197 | }; 198 | 199 | var svgURI = 'http://www.w3.org/2000/svg'; 200 | 201 | Shape.prototype.getRenderElement = function( ctx, renderer ) { 202 | if ( !renderer.isSvg ) { 203 | return; 204 | } 205 | if ( !this.svgElement ) { 206 | // create svgElement 207 | this.svgElement = document.createElementNS( svgURI, 'path' ); 208 | this.svgElement.setAttribute( 'stroke-linecap', 'round' ); 209 | this.svgElement.setAttribute( 'stroke-linejoin', 'round' ); 210 | } 211 | return this.svgElement; 212 | }; 213 | 214 | return Shape; 215 | 216 | } ) ); 217 | -------------------------------------------------------------------------------- /js/svg-renderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SvgRenderer 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory(); 10 | } else { 11 | // browser global 12 | root.Zdog.SvgRenderer = factory(); 13 | } 14 | }( this, function factory() { 15 | 16 | var SvgRenderer = { isSvg: true }; 17 | 18 | // round path coordinates to 3 decimals 19 | var round = SvgRenderer.round = function( num ) { 20 | return Math.round( num * 1000 ) / 1000; 21 | }; 22 | 23 | function getPointString( point ) { 24 | return round( point.x ) + ',' + round( point.y ) + ' '; 25 | } 26 | 27 | SvgRenderer.begin = function() {}; 28 | 29 | SvgRenderer.move = function( svg, elem, point ) { 30 | return 'M' + getPointString( point ); 31 | }; 32 | 33 | SvgRenderer.line = function( svg, elem, point ) { 34 | return 'L' + getPointString( point ); 35 | }; 36 | 37 | SvgRenderer.bezier = function( svg, elem, cp0, cp1, end ) { 38 | return 'C' + getPointString( cp0 ) + getPointString( cp1 ) + 39 | getPointString( end ); 40 | }; 41 | 42 | SvgRenderer.closePath = function( /* elem */) { 43 | return 'Z'; 44 | }; 45 | 46 | SvgRenderer.setPath = function( svg, elem, pathValue ) { 47 | elem.setAttribute( 'd', pathValue ); 48 | }; 49 | 50 | SvgRenderer.renderPath = function( svg, elem, pathCommands, isClosed ) { 51 | var pathValue = ''; 52 | pathCommands.forEach( function( command ) { 53 | pathValue += command.render( svg, elem, SvgRenderer ); 54 | } ); 55 | if ( isClosed ) { 56 | pathValue += this.closePath( svg, elem ); 57 | } 58 | this.setPath( svg, elem, pathValue ); 59 | }; 60 | 61 | SvgRenderer.stroke = function( svg, elem, isStroke, color, lineWidth ) { 62 | if ( !isStroke ) { 63 | return; 64 | } 65 | elem.setAttribute( 'stroke', color ); 66 | elem.setAttribute( 'stroke-width', lineWidth ); 67 | }; 68 | 69 | SvgRenderer.fill = function( svg, elem, isFill, color ) { 70 | var fillColor = isFill ? color : 'none'; 71 | elem.setAttribute( 'fill', fillColor ); 72 | }; 73 | 74 | SvgRenderer.end = function( svg, elem ) { 75 | svg.appendChild( elem ); 76 | }; 77 | 78 | return SvgRenderer; 79 | 80 | } ) ); 81 | -------------------------------------------------------------------------------- /js/vector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vector 3 | */ 4 | 5 | ( function( root, factory ) { 6 | // module definition 7 | if ( typeof module == 'object' && module.exports ) { 8 | // CommonJS 9 | module.exports = factory( require('./boilerplate') ); 10 | } else { 11 | // browser global 12 | var Zdog = root.Zdog; 13 | Zdog.Vector = factory( Zdog ); 14 | } 15 | 16 | }( this, function factory( utils ) { 17 | 18 | function Vector( position ) { 19 | this.set( position ); 20 | } 21 | 22 | var TAU = utils.TAU; 23 | 24 | // 'pos' = 'position' 25 | Vector.prototype.set = function( pos ) { 26 | this.x = pos && pos.x || 0; 27 | this.y = pos && pos.y || 0; 28 | this.z = pos && pos.z || 0; 29 | return this; 30 | }; 31 | 32 | // set coordinates without sanitizing 33 | // vec.write({ y: 2 }) only sets y coord 34 | Vector.prototype.write = function( pos ) { 35 | if ( !pos ) { 36 | return this; 37 | } 38 | this.x = pos.x != undefined ? pos.x : this.x; 39 | this.y = pos.y != undefined ? pos.y : this.y; 40 | this.z = pos.z != undefined ? pos.z : this.z; 41 | return this; 42 | }; 43 | 44 | Vector.prototype.rotate = function( rotation ) { 45 | if ( !rotation ) { 46 | return; 47 | } 48 | this.rotateZ( rotation.z ); 49 | this.rotateY( rotation.y ); 50 | this.rotateX( rotation.x ); 51 | return this; 52 | }; 53 | 54 | Vector.prototype.rotateZ = function( angle ) { 55 | rotateProperty( this, angle, 'x', 'y' ); 56 | }; 57 | 58 | Vector.prototype.rotateX = function( angle ) { 59 | rotateProperty( this, angle, 'y', 'z' ); 60 | }; 61 | 62 | Vector.prototype.rotateY = function( angle ) { 63 | rotateProperty( this, angle, 'x', 'z' ); 64 | }; 65 | 66 | function rotateProperty( vec, angle, propA, propB ) { 67 | if ( !angle || angle % TAU === 0 ) { 68 | return; 69 | } 70 | var cos = Math.cos( angle ); 71 | var sin = Math.sin( angle ); 72 | var a = vec[ propA ]; 73 | var b = vec[ propB ]; 74 | vec[ propA ] = a * cos - b * sin; 75 | vec[ propB ] = b * cos + a * sin; 76 | } 77 | 78 | Vector.prototype.isSame = function( pos ) { 79 | if ( !pos ) { 80 | return false; 81 | } 82 | return this.x === pos.x && this.y === pos.y && this.z === pos.z; 83 | }; 84 | 85 | Vector.prototype.add = function( pos ) { 86 | if ( !pos ) { 87 | return this; 88 | } 89 | this.x += pos.x || 0; 90 | this.y += pos.y || 0; 91 | this.z += pos.z || 0; 92 | return this; 93 | }; 94 | 95 | Vector.prototype.subtract = function( pos ) { 96 | if ( !pos ) { 97 | return this; 98 | } 99 | this.x -= pos.x || 0; 100 | this.y -= pos.y || 0; 101 | this.z -= pos.z || 0; 102 | return this; 103 | }; 104 | 105 | Vector.prototype.multiply = function( pos ) { 106 | if ( pos == undefined ) { 107 | return this; 108 | } 109 | // multiple all values by same number 110 | if ( typeof pos == 'number' ) { 111 | this.x *= pos; 112 | this.y *= pos; 113 | this.z *= pos; 114 | } else { 115 | // multiply object 116 | this.x *= pos.x != undefined ? pos.x : 1; 117 | this.y *= pos.y != undefined ? pos.y : 1; 118 | this.z *= pos.z != undefined ? pos.z : 1; 119 | } 120 | return this; 121 | }; 122 | 123 | Vector.prototype.transform = function( translation, rotation, scale ) { 124 | this.multiply( scale ); 125 | this.rotate( rotation ); 126 | this.add( translation ); 127 | return this; 128 | }; 129 | 130 | Vector.prototype.lerp = function( pos, alpha ) { 131 | this.x = utils.lerp( this.x, pos.x || 0, alpha ); 132 | this.y = utils.lerp( this.y, pos.y || 0, alpha ); 133 | this.z = utils.lerp( this.z, pos.z || 0, alpha ); 134 | return this; 135 | }; 136 | 137 | Vector.prototype.magnitude = function() { 138 | var sum = this.x * this.x + this.y * this.y + this.z * this.z; 139 | return getMagnitudeSqrt( sum ); 140 | }; 141 | 142 | function getMagnitudeSqrt( sum ) { 143 | // PERF: check if sum ~= 1 and skip sqrt 144 | if ( Math.abs( sum - 1 ) < 0.00000001 ) { 145 | return 1; 146 | } 147 | return Math.sqrt( sum ); 148 | } 149 | 150 | Vector.prototype.magnitude2d = function() { 151 | var sum = this.x * this.x + this.y * this.y; 152 | return getMagnitudeSqrt( sum ); 153 | }; 154 | 155 | Vector.prototype.copy = function() { 156 | return new Vector( this ); 157 | }; 158 | 159 | return Vector; 160 | 161 | } ) ); 162 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zdog", 3 | "version": "1.1.3", 4 | "description": "Round, flat, designer-friendly pseudo-3D engine", 5 | "main": "js/index.js", 6 | "unpkg": "dist/zdog.dist.min.js", 7 | "files": [ 8 | "dist/*.*", 9 | "js/*.*", 10 | "!js/.*", 11 | "!dist/.*" 12 | ], 13 | "devDependencies": { 14 | "eslint": "^8.7.0", 15 | "eslint-plugin-metafizzy": "^1.0.0", 16 | "uglify-js": "^3.6.3" 17 | }, 18 | "scripts": { 19 | "bundle": "node tasks/bundle", 20 | "dist": "npm run bundle && npm run uglify", 21 | "lint": "npx eslint .", 22 | "lintFix": "npx eslint . --fix", 23 | "preversion": "npm run lint", 24 | "version": "node tasks/version && npm run dist && git add -A dist js", 25 | "test": "npm run lint", 26 | "uglify": "npx uglifyjs dist/zdog.dist.js -o dist/zdog.dist.min.js --mangle --comments /^!/" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/metafizzy/zdog.git" 31 | }, 32 | "keywords": [ 33 | "3D", 34 | "canvas", 35 | "svg" 36 | ], 37 | "author": "David DeSandro", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/metafizzy/zdog/issues" 41 | }, 42 | "homepage": "https://zzz.dog" 43 | } 44 | -------------------------------------------------------------------------------- /tasks/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | module.exports = { 4 | plugins: [ 'metafizzy' ], 5 | extends: 'plugin:metafizzy/node', 6 | env: { 7 | browser: false, 8 | commonjs: true, 9 | }, 10 | rules: { 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /tasks/bundle.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const execSync = require('child_process').execSync; 3 | 4 | // get file paths from index.js 5 | const indexPath = 'js/index.js'; 6 | let indexSrc = fs.readFileSync( `./${indexPath}`, 'utf8' ); 7 | let cjsBlockRegex = /module\.exports = factory\([\w ,'.\-()/\n]+;/i; 8 | let cjsBlockMatch = indexSrc.match( cjsBlockRegex ); 9 | let paths = cjsBlockMatch[0].match( /require\('([.\-/\w]+)'\)/gi ); 10 | 11 | paths = paths.map( function( path ) { 12 | return path.replace( "require('.", 'js' ).replace( "')", '.js' ); 13 | } ); 14 | paths.push( indexPath ); 15 | 16 | execSync(`cat ${paths.join(' ')} > dist/zdog.dist.js`); 17 | 18 | console.log('bundled dist/zdog.dist.js'); 19 | -------------------------------------------------------------------------------- /tasks/version.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const version = require('../package.json').version; 3 | 4 | const boilerplatePath = 'js/boilerplate.js'; 5 | 6 | let boilerplateSrc = fs.readFileSync( boilerplatePath, 'utf8' ); 7 | 8 | boilerplateSrc = boilerplateSrc.replace( /\n \* Zdog v\d+\.\d+\.\d+/, 9 | `\n * Zdog v${version}` ); 10 | 11 | fs.writeFileSync( boilerplatePath, boilerplateSrc, 'utf8' ); 12 | 13 | console.log(`updated ${boilerplatePath} to ${version}`); 14 | --------------------------------------------------------------------------------