Showing preview only (204K chars total). Download the full file or copy to clipboard to get everything.
Repository: metafizzy/zdog
Branch: master
Commit: dde8684be686
Files: 67
Total size: 188.3 KB
Directory structure:
gitextract_1ppqe373/
├── .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
├── 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.json
└── tasks/
├── .eslintrc.js
├── bundle.js
└── version.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.js
================================================
/* eslint-env node */
module.exports = {
plugins: [ 'metafizzy' ],
extends: 'plugin:metafizzy/browser',
env: {
browser: true,
commonjs: true,
},
parserOptions: {
ecmaVersion: 5,
},
rules: {
'no-var': 'off',
'max-params': [ 'error', {
max: 5,
} ],
},
};
================================================
FILE: .github/contributing.md
================================================
## Feature requests
**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/).
## Reduced test cases required
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).
**CodePen**
+ [Hello world canvas](https://codepen.io/desandro/pen/YbrLaO)
+ [Hello world SVG](https://codepen.io/desandro/pen/Bewxme)
+ [resize fullscreen](https://codepen.io/desandro/pen/dEJxaV)
+ [Strutter](https://codepen.io/desandro/pen/xNPaoP)
**Test cases**
+ A reduced test case clearly demonstrates the bug or issue.
+ It contains the bare minimum HTML, CSS, and JavaScript required to demonstrate the bug.
+ A link to your production site is **not** a reduced test case.
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.
## Pull requests
Contributions are welcome!
+ **For typos and one-line fixes,** send those right in.
+ **For larger features,** open an issue before starting any significant work. Let's discuss to see how your feature fits within Zdog's vision.
+ **Follow the code style.** Spaces in brackets, semicolons, trailing commas.
+ **Do not edit `dist/` files.** Make your edits to source files in `js/` and `css/`.
+ **Do not run `make` to update `dist/` files.** I'll take care of this when I create a new release.
================================================
FILE: .github/issue_template.md
================================================
<!-- Thanks for submitting an issue! If you have a bug or problem issue, please include a **reduced test case**. Create one in CodePen or other demo site. See contributing guidelines. -->
**Test case:** https://codepen.io/desandro/pen/YbrLaO
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules/
bower_components/
================================================
FILE: README.md
================================================
# Zdog
_Round, flat, designer-friendly pseudo-3D engine_
View complete documentation and live demos at [zzz.dog](https://zzz.dog).
## Install
### Download
+ [zdog.dist.min.js](https://unpkg.com/zdog@1/dist/zdog.dist.min.js) minified, or
+ [zdog.dist.js](https://unpkg.com/zdog@1/dist/zdog.dist.js) un-minified
### CDN
Link directly to Zdog JS on [unpkg](https://unpkg.com).
``` html
<script src="https://unpkg.com/zdog@1/dist/zdog.dist.min.js"></script>
```
### Package managers
npm: `npm install zdog`
Bower: `bower install zdog`
## Hello world demo
Create 3D models with Zdog by adding shapes. See [Getting started](https://zzz.dog/getting-started) for a walk-through of this demo.
``` js
let isSpinning = true;
let illo = new Zdog.Illustration({
element: '.zdog-canvas',
zoom: 4,
dragRotate: true,
// stop spinning when drag starts
onDragStart: function() {
isSpinning = false;
},
});
// circle
new Zdog.Ellipse({
addTo: illo,
diameter: 20,
translate: { z: 10 },
stroke: 5,
color: '#636',
});
// square
new Zdog.Rect({
addTo: illo,
width: 20,
height: 20,
translate: { z: -10 },
stroke: 3,
color: '#E62',
fill: true,
});
function animate() {
illo.rotate.y += isSpinning ? 0.03 : 0;
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
```
## About Zdog
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.
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.
Zdog uses the same principle. It renders all shapes using the 2D drawing APIs in either `<canvas>` or `<svg>`. 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).
Zdog is pronounced "Zee-dog" in American parlance or "Zed-dog" in British.
### Beta!
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).
### More Zdog resources
Other people's stuff:
+ [Zfont](https://jaames.github.io/zfont/) - Text plugin for Zdog
+ [vue-zdog](https://github.com/AlexandreBonaventure/vue-zdog) - Vue wrapper for Zdog
+ [zDogPy](https://github.com/gferreira/zdogpy) - Python port of Zdog for DrawBot
+ [Made with Zdog CodePen Collection](https://codepen.io/collection/DzdGMe/)
+ [Made with Zdog on Twitter](https://twitter.com/i/moments/1135000612356206592)
My stuff:
+ [Zdog demos on CodePen](https://github.com/metafizzy/zdog-demos), source code at [zdog-demos](https://github.com/metafizzy/zdog-demos)
+ [zdog-docs](https://github.com/metafizzy/zdog-docs) - Docs site source code
---
Licensed MIT. Made by Metafizzy 🌈🐻
================================================
FILE: bower.json
================================================
{
"name": "zdog",
"description": "Round, flat, designer-friendly pseudo-3D engine",
"main": "js/index.js",
"authors": [
"David DeSandro"
],
"license": "MIT",
"keywords": [
"3D",
"canvas",
"svg"
],
"homepage": "https://zzz.dog",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests",
"package.json",
"demo",
"demos"
]
}
================================================
FILE: demos/.eslintrc.js
================================================
/* eslint-env node */
module.exports = {
extends: '../.eslintrc.js',
globals: {
Zdog: 'readonly',
},
rules: {
'key-spacing': 'off',
'max-lines': 'off',
},
};
================================================
FILE: demos/box-cross/box-cross.js
================================================
// ----- setup ----- //
var sceneSize = 9;
var isSpinning = true;
var TAU = Zdog.TAU;
// colors
var yellow = '#ED0';
var gold = '#EA0';
var orange = '#E62';
var garnet = '#C25';
var eggplant = '#636';
var initRotate = { x: ( 35/360 ) * TAU, y: TAU/8 };
var illo = new Zdog.Illustration({
element: '.illo',
rotate: initRotate,
resize: 'fullscreen',
dragRotate: true,
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.floor( Math.min( width, height ) / sceneSize );
},
});
// ----- model ----- //
var model = new Zdog.Anchor({
addTo: illo,
});
function addBox( options ) {
var boxOptions = {
addTo: model,
stroke: false,
topFace: yellow,
rearFace: gold,
leftFace: orange,
rightFace: orange,
frontFace: garnet,
bottomFace: eggplant,
};
Zdog.extend( boxOptions, options );
new Zdog.Box( boxOptions );
}
// top
addBox({
bottomFace: false,
translate: { y: -1 },
});
// bottom
addBox({
topFace: false,
translate: { y: 1 },
});
// front
addBox({
rearFace: false,
translate: { z: 1 },
});
// back
addBox({
frontFace: false,
translate: { z: -1 },
});
// left
addBox({
rightFace: false,
translate: { x: -1 },
});
// right
addBox({
leftFace: false,
translate: { x: 1 },
});
var dot = new Zdog.Shape({
addTo: model,
translate: { y: -2 },
stroke: 1,
color: gold,
});
dot.copy({
translate: { y: 2 },
color: gold,
});
dot.copy({
translate: { x: -2 },
color: yellow,
});
dot.copy({
translate: { x: 2 },
color: garnet,
});
dot.copy({
translate: { z: -2 },
color: orange,
});
dot.copy({
translate: { z: 2 },
color: eggplant,
});
// ----- animate ----- //
var ticker = 0;
var cycleCount = 150;
function animate() {
spin();
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
function spin() {
if ( !isSpinning ) {
return;
}
var progress = ticker/cycleCount;
var turn = Math.floor( progress % 4 );
var theta = Zdog.easeInOut( progress % 1, 3 ) * TAU;
if ( turn == 0 || turn == 2 ) {
model.rotate.y = theta;
} else if ( turn == 1 ) {
model.rotate.x = theta;
} else if ( turn == 3 ) {
model.rotate.z = theta;
}
ticker++;
}
animate();
================================================
FILE: demos/box-cross/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>box cross</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
}
.illo {
display: block;
width: 100%;
height: 100%;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<canvas class="illo"></canvas>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/box.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="box-cross.js"></script>
</body>
</html>
================================================
FILE: demos/dot-cube/dot-cube.js
================================================
// ----- setup ----- //
var sceneSize = 24;
var TAU = Zdog.TAU;
var illo = new Zdog.Illustration({
element: '.illo',
rotate: { x: TAU * -35/360, y: TAU * 1/8 },
dragRotate: true,
resize: 'fullscreen',
onResize: function( width, height ) {
this.zoom = Math.floor( Math.min( width, height ) / sceneSize );
},
});
// ----- model ----- //
var cube = new Zdog.Anchor({
addTo: illo,
scale: 4,
});
var oneUnit = new Zdog.Vector({ x: 1, y: 1 });
var side = new Zdog.Anchor({
addTo: cube,
translate: { z: 1 },
});
var dot = new Zdog.Shape({
addTo: side,
translate: oneUnit.copy(),
stroke: 1,
color: 'white',
});
dot.copy({ translate: { x: -1, y: 1 } });
dot.copy({ translate: { x: 1, y: -1 } });
dot.copy({ translate: { x: -1, y: -1 } });
// more dots
dot.copy({ translate: { x: 1 } });
dot.copy({ translate: { x: -1 } });
dot.copy({ translate: { y: -1 } });
dot.copy({ translate: { y: 1 } });
side.copyGraph({
translate: { z: -1 },
});
var midDot = dot.copy({
addTo: cube,
});
midDot.copy({ translate: { x: -1, y: 1 } });
midDot.copy({ translate: { x: 1, y: -1 } });
midDot.copy({ translate: { x: -1, y: -1 } });
// ----- animate ----- //
var keyframes = [
{ x: 0, y: 0, z: 0 },
{ x: 0, y: 0, z: TAU/4 },
{ x: -TAU/4, y: 0, z: TAU/4 },
{ x: -TAU/4, y: 0, z: TAU/2 },
];
var ticker = 0;
var cycleCount = 75;
var turnLimit = keyframes.length - 1;
function animate() {
var progress = ticker/cycleCount;
var tween = Zdog.easeInOut( progress % 1, 4 );
var turn = Math.floor( progress % turnLimit );
var keyA = keyframes[ turn ];
var keyB = keyframes[ turn + 1 ];
cube.rotate.x = Zdog.lerp( keyA.x, keyB.x, tween );
cube.rotate.y = Zdog.lerp( keyA.y, keyB.y, tween );
cube.rotate.z = Zdog.lerp( keyA.z, keyB.z, tween );
ticker++;
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/dot-cube/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>dot cube</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
background: black;
color: white;
}
.illo {
display: block;
width: 100%;
height: 100%;
cursor: move;
}
</style>
</head>
<body>
<canvas class="illo"></canvas>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/group.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="dot-cube.js"></script>
</body>
</html>
================================================
FILE: demos/fullscreen/fullscreen.js
================================================
// ----- setup ----- //
var isSpinning = true;
var gold = '#EA0';
var orange = '#E62';
var garnet = '#C25';
var eggplant = '#636';
var illo = new Zdog.Illustration({
element: '.illo',
zoom: 4,
resize: 'fullscreen',
dragRotate: true,
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.min( width, height ) / 50;
},
});
// ----- model ----- //
new Zdog.Rect({
width: 20,
height: 20,
addTo: illo,
translate: { z: -10 },
stroke: 2,
color: garnet,
});
new Zdog.Ellipse({
diameter: 16,
addTo: illo,
translate: { z: 10 },
stroke: 4,
color: eggplant,
});
new Zdog.Shape({
path: [
{ x: 0, z: 1 },
{ x: -1, z: -1 },
{ x: 1, z: -1 },
],
scale: { x: 5, z: 5 },
addTo: illo,
stroke: 2,
fill: true,
color: gold,
});
new Zdog.Shape({
translate: { x: 10, y: -5 },
addTo: illo,
stroke: 7,
color: orange,
});
// ----- animate ----- //
function animate() {
illo.rotate.y += isSpinning ? 0.03 : 0;
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/fullscreen/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>fullscreen</title>
<style>
body {
margin: 0;
padding: 0;
}
.illo {
display: block;
width: 100%;
height: 100%;
cursor: move;
background: #FDB;
}
</style>
</head>
<body>
<canvas class="illo"></canvas>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/svg-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/group.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="fullscreen.js"></script>
</body>
</html>
================================================
FILE: demos/hello-world-canvas/hello-world-canvas.js
================================================
// ----- variables ----- //
var isSpinning = true;
// ----- model ----- //
var illo = new Zdog.Illustration({
element: '.illo',
zoom: 5,
dragRotate: true,
onDragStart: function() {
isSpinning = false;
},
});
// circle
new Zdog.Ellipse({
addTo: illo,
diameter: 20,
translate: { z: 10 },
stroke: 5,
color: '#636',
});
// square
new Zdog.Rect({
addTo: illo,
width: 20,
height: 20,
translate: { z: -10 },
stroke: 3,
color: '#E62',
fill: true,
});
// ----- animate ----- //
function animate() {
illo.rotate.y += isSpinning ? 0.03 : 0;
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/hello-world-canvas/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Hello world canvas</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
text-align: center;
}
.illo {
display: block;
margin: 20px auto;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<div class="container">
<h1>Hello world canvas</h1>
<canvas class="illo" width="300" height="300"></canvas>
</div>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="hello-world-canvas.js"></script>
</body>
</html>
================================================
FILE: demos/hello-world-svg/hello-world-svg.js
================================================
// ----- variables ----- //
var isSpinning = true;
// ----- model ----- //
var illo = new Zdog.Illustration({
element: '.illo',
zoom: 5,
dragRotate: true,
onDragStart: function() {
isSpinning = false;
},
});
// circle
new Zdog.Ellipse({
addTo: illo,
diameter: 20,
translate: { z: 10 },
stroke: 5,
color: '#636',
});
// square
new Zdog.Rect({
addTo: illo,
width: 20,
height: 20,
translate: { z: -10 },
stroke: 3,
color: '#E62',
fill: true,
});
// ----- animate ----- //
function animate() {
illo.rotate.y += isSpinning ? 0.03 : 0;
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/hello-world-svg/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Hello world SVG</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
text-align: center;
}
.illo {
display: block;
margin: 20px auto;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<div class="container">
<h1>Hello world SVG</h1>
<svg class="illo" width="300" height="300"></svg>
</div>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/svg-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="hello-world-svg.js"></script>
</body>
</html>
================================================
FILE: demos/hemisphere-cone-ball/hemisphere-cone-ball.js
================================================
// ----- setup ----- //
var sceneSize = 48;
var isSpinning = true;
var TAU = Zdog.TAU;
var illo = new Zdog.Illustration({
element: '.illo',
dragRotate: true,
resize: 'fullscreen',
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.floor( Math.min( width, height ) / sceneSize );
},
});
// colors
var yellow = '#ED0';
var gold = '#EA0';
var orange = '#E62';
var garnet = '#C25';
var eggplant = '#636';
// ----- model ----- //
var hemi = new Zdog.Hemisphere({
addTo: illo,
diameter: 13,
translate: { y: -16 },
rotate: { x: -TAU/4 },
color: garnet,
backface: eggplant,
stroke: false,
});
var cone = new Zdog.Cone({
addTo: illo,
diameter: 13,
length: 6.5,
translate: { y: 16 },
rotate: { x: TAU/4 },
color: garnet,
backface: eggplant,
stroke: false,
});
var colorWheel = [ eggplant, garnet, orange, gold, yellow ];
[ true, false ].forEach( function( isHemi ) {
var shape = isHemi ? hemi : cone;
for ( var i = 0; i < 5; i++ ) {
var rotor1 = new Zdog.Anchor({
addTo: illo,
rotate: { y: TAU/5 * i },
});
var rotor2 = new Zdog.Anchor({
addTo: rotor1,
rotate: { x: TAU/6 },
});
shape.copy({
addTo: rotor2,
color: colorWheel[i],
backface: colorWheel[ ( i + 7 ) % 5 ],
});
}
} );
// ----- animate ----- //
var keyframes = [
{ x: TAU * 0, y: TAU * 0 },
{ x: TAU/2, y: TAU/2 },
{ x: TAU * 1, y: TAU * 1 },
];
var ticker = 0;
var cycleCount = 180;
var turnLimit = keyframes.length - 1;
function animate() {
spin();
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
function spin() {
if ( !isSpinning ) {
return;
}
var progress = ticker/cycleCount;
var tween = Zdog.easeInOut( progress % 1, 3 );
var turn = Math.floor( progress % turnLimit );
var keyA = keyframes[ turn ];
var keyB = keyframes[ turn + 1 ];
var thetaX = Zdog.lerp( keyA.x, keyB.x, tween );
illo.rotate.x = Math.cos( thetaX ) * TAU/12;
illo.rotate.y = Zdog.lerp( keyA.y, keyB.y, tween );
ticker++;
}
animate();
================================================
FILE: demos/hemisphere-cone-ball/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>hemisphere-cone-ball</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
}
.illo {
display: block;
width: 100%;
height: 100%;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<canvas class="illo"></canvas>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/group.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/hemisphere.js"></script>
<script src="../../js/cone.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="hemisphere-cone-ball.js"></script>
</body>
</html>
================================================
FILE: demos/houses/houses.js
================================================
// ----- setup ----- //
var sceneSize = 56;
var isSpinning = true;
var TAU = Zdog.TAU;
var offWhite = '#FED';
var yellow = '#ED0';
var gold = '#EA0';
var orange = '#E62';
var garnet = '#C25';
var eggplant = '#636';
// enable fill, disable stroke for all defaults
[ Zdog.Rect, Zdog.Shape, Zdog.Ellipse ].forEach( function( Item ) {
Item.defaults.fill = true;
Item.defaults.stroke = false;
} );
var initRotate = { y: TAU/8 };
var turnRatio = 1 / Math.sin( TAU/8 );
var illo = new Zdog.Illustration({
element: '.illo',
rotate: initRotate,
// stretch looks circular at 1/8 turn
scale: { x: turnRatio, z: turnRatio },
dragRotate: true,
resize: 'fullscreen',
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.floor( Math.min( width, height ) / sceneSize );
},
});
// ----- model ----- //
var house = new Zdog.Anchor({
addTo: illo,
translate: { x: -2, y: 2, z: 8 },
});
var frontGroup = new Zdog.Group({
addTo: house,
translate: { z: 5 },
});
// front wall
new Zdog.Rect({
addTo: frontGroup,
width: 14,
height: 14,
color: garnet,
});
var frontWindow = new Zdog.Rect({
addTo: frontGroup,
width: 2,
height: 4,
translate: { x: -4, y: -3 },
color: eggplant,
});
frontWindow.copy({
translate: { y: -3 },
});
frontWindow.copy({
translate: { x: 4, y: -3 },
});
frontWindow.copy({
translate: { x: -4, y: 3 },
});
// door
new Zdog.Shape({
addTo: frontGroup,
path: [
{ x: -2, y: 3 },
{ x: -2, y: -1 },
{ arc: [
{ x: -2, y: -3 },
{ x: 0, y: -3 },
] },
{ arc: [
{ x: 2, y: -3 },
{ x: 2, y: -1 },
] },
{ x: 2, y: 3 },
],
translate: { x: 2, y: 4 },
color: eggplant,
});
// backWall
var backGroup = frontGroup.copyGraph({
translate: { z: -5 },
rotate: { y: TAU/2 },
});
backGroup.children.forEach( function( child, i ) {
// orange windows, yellow wall
child.color = i ? orange : yellow;
} );
var rightGroup = new Zdog.Group({
addTo: house,
translate: { x: 7 },
rotate: { y: -TAU/4 },
});
// right wall
new Zdog.Shape({
addTo: rightGroup,
path: [
{ x: -5, y: 7 },
{ x: -5, y: -7 },
{ x: 0, y: -12 },
{ x: 5, y: -7 },
{ x: 5, y: 7 },
],
width: 10,
height: 14,
color: offWhite,
});
var sideWindow = frontWindow.copy({
addTo: rightGroup,
translate: { x: -2, y: -3 },
color: gold,
});
sideWindow.copy({
translate: { x: 2, y: -3 },
});
sideWindow.copy({
translate: { x: 2, y: 3 },
});
sideWindow.copy({
translate: { x: -2, y: 3 },
});
// porthole
new Zdog.Ellipse({
addTo: rightGroup,
width: 2,
height: 2,
translate: { y: -8 },
color: gold,
});
var leftGroup = rightGroup.copyGraph({
translate: { x: -7 },
rotate: { y: TAU/4 },
});
leftGroup.children.forEach( function( child, i ) {
// eggplant windows, yellow wall
child.color = i ? eggplant : orange;
} );
// front roof
var frontRoof = new Zdog.Shape({
addTo: house,
path: [
{ x: -7, y: -7, z: 5 },
{ x: -7, y: -12, z: 0 },
{ x: 7, y: -12, z: 0 },
{ x: 7, y: -7, z: 5 },
],
color: eggplant,
});
frontRoof.copy({
scale: { z: -1 },
color: garnet,
});
// floor
new Zdog.Rect({
addTo: house,
width: 14,
height: 10,
translate: { y: 7 },
rotate: { x: TAU/4 },
color: eggplant,
});
house.copyGraph({
translate: house.translate.copy().multiply( -1 ),
});
// ----- animate ----- //
var ticker = 0;
var cycleCount = 240;
function animate() {
if ( isSpinning ) {
var progress = ticker/cycleCount;
var tween = Zdog.easeInOut( progress % 1, 3 );
illo.rotate.y = tween * TAU + initRotate.y;
ticker++;
}
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/houses/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>houses</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
background: white;
font-family: sans-serif;
text-align: center;
}
.illo {
display: block;
width: 100%;
height: 100%;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<div class="container">
<canvas class="illo"></canvas>
</div>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/svg-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/group.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="houses.js"></script>
</body>
</html>
================================================
FILE: demos/kid-kit/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Kid Kit</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
text-align: center;
}
.illo {
display: block;
margin: 20px auto;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<div class="container">
<h1>Kid Kit</h1>
<canvas class="illo" width="300" height="300"></canvas>
</div>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="kid-kit.js"></script>
</body>
</html>
================================================
FILE: demos/kid-kit/kid-kit.js
================================================
// ----- setup ----- //
var offWhite = '#FED';
var gold = '#EA0';
var garnet = '#C25';
var eggplant = '#636';
var illo = new Zdog.Illustration({
element: '.illo',
zoom: 4,
dragRotate: true,
});
// ----- model ----- //
// body center
new Zdog.Shape({
path: [
{ x: -3, y: 10 },
{ x: 0, y: 14 },
{ x: 3, y: 10 },
],
addTo: illo,
color: offWhite,
stroke: 13,
});
// head circle
new Zdog.Shape({
addTo: illo,
translate: { y: -12 },
color: gold,
stroke: 32,
});
// nose
var nose = new Zdog.Anchor({
addTo: illo,
translate: { y: -7, z: 17 },
});
new Zdog.Shape({
path: [
{ x: -1 },
{ x: 1 },
],
addTo: nose,
color: eggplant,
stroke: 3,
});
new Zdog.Shape({
path: [
{ y: 0 },
{ y: 1 },
],
addTo: nose,
color: eggplant,
stroke: 3,
});
// snout
new Zdog.Shape({
path: [
{ x: -2, y: -5, z: 11 },
{ x: 2, y: -5, z: 11 },
{ x: 2, y: -3, z: 7 },
{ x: -2, y: -3, z: 7 },
],
addTo: illo,
color: gold,
stroke: 12,
});
// eyes
var eye = new Zdog.Shape({
path: [
{ y: -12 },
{ y: -9 },
],
addTo: illo,
translate: { x: -8, z: 11 },
color: eggplant,
stroke: 4,
});
eye.copy({
translate: { x: 8, z: 11 },
});
// ears
var frontEarZ = 4;
var topEarY = -30;
var earColor = gold;
var earAnchor = new Zdog.Anchor({
addTo: illo,
translate: { y: topEarY, z: frontEarZ },
});
var earA = { x: 14, y: 12, z: -4 };
var earB = { x: 14, y: 0, z: 0 };
var earC = { x: 7, y: 11, z: -14 };
var earD = { x: 10, y: 0, z: 0 };
var earE = { x: 3, y: 5, z: 0 };
// outer ear
new Zdog.Shape({
path: [ earA, earB, earC ],
addTo: earAnchor,
color: earColor,
fill: true,
stroke: 4,
});
new Zdog.Shape({
path: [ earB, earC, earD ],
addTo: earAnchor,
color: earColor,
fill: true,
stroke: 4,
});
new Zdog.Shape({
path: [ earC, earD, earE ],
addTo: earAnchor,
color: earColor,
fill: true,
stroke: 4,
});
// inner ear
var innerEarXShift = 4;
new Zdog.Shape({
path: [
{ x: earA.x - innerEarXShift, y: earA.y - 3 },
{ x: earD.x, y: earD.y + 5 },
{ x: earE.x + innerEarXShift, y: earE.y + 2 },
],
addTo: earAnchor,
color: offWhite,
fill: true,
stroke: 3,
});
earAnchor.copyGraph({
scale: { x: -1 },
});
// var whiskerX0 = 10*xSide;
// var whiskerX1 = 17*xSide;
// var whiskerY0 = -6+yShift;
// var whiskerY1 = -2+yShift;
// whiskers
var whisker = new Zdog.Shape({
path: [
{ x: 10, y: -6 },
{ x: 10, y: -2 },
{ x: 17, y: -2 },
],
addTo: illo,
translate: { z: 6 },
fill: true,
color: gold,
stroke: 3,
});
whisker.copy({
translate: { y: -6, z: 6 },
});
whisker.copy({
scale: { x: -1 },
});
whisker.copy({
scale: { x: -1 },
translate: { y: -6, z: 6 },
});
// arms
var armAnchor = new Zdog.Anchor({
addTo: illo,
});
// shoulder
new Zdog.Shape({
path: [
{ x: 11, y: 6, z: -2 },
{ x: 12, y: 9, z: -2.5 },
],
addTo: armAnchor,
closed: true,
color: eggplant,
stroke: 8,
});
// forearm
new Zdog.Shape({
path: [
{ x: 12, y: 12, z: -2.5 },
{ x: 12, y: 15, z: -2 },
],
addTo: armAnchor,
color: gold,
stroke: 8,
});
// hand
new Zdog.Shape({
path: [ { x: 11, y: 18, z: -1 } ],
addTo: armAnchor,
color: eggplant,
stroke: 10,
});
armAnchor.copyGraph({
scale: { x: -1 },
});
// legs
var leg = new Zdog.Shape({
path: [
{ y: 20 },
{ y: 27 },
],
addTo: illo,
translate: { x: -6 },
color: eggplant,
stroke: 8,
});
leg.copy({
translate: { x: 6 },
});
var cloakX0 = 8;
var cloakX1 = 5;
var cloakY0 = 4;
var cloakY1 = 6;
var cloakY2 = 13;
var cloakY3 = 21;
var cloakZ0 = 0;
var cloakZ1 = 6;
var cloakZ2 = 8;
var cloakSide = new Zdog.Anchor({
addTo: illo,
});
// top straps
var topCloakStrap = new Zdog.Shape({
path: [
{ x: cloakX0, y: cloakY0, z: cloakZ0 },
{ x: cloakX0, y: cloakY1, z: cloakZ1 },
{ x: cloakX1, y: cloakY1, z: cloakZ1 },
],
addTo: cloakSide,
fill: true,
color: garnet,
stroke: 4,
});
topCloakStrap.copy({
scale: { x: -1 },
});
var vNeckY = ( cloakY1 + cloakY2 )/2;
var vNeckZ = ( cloakZ2 + cloakZ1 )/2;
new Zdog.Shape({
path: [
{ x: -cloakX0, y: cloakY1, z: cloakZ1 },
{ x: -cloakX1, y: cloakY1, z: cloakZ1 },
{ x: 0, y: vNeckY, z: vNeckZ },
{ x: cloakX1, y: cloakY1, z: cloakZ1 },
{ x: cloakX0, y: cloakY1, z: cloakZ1 },
{ x: cloakX0, y: cloakY2, z: cloakZ2 },
{ x: -cloakX0, y: cloakY2, z: cloakZ2 },
],
addTo: cloakSide,
fill: true,
color: garnet,
stroke: 4,
});
new Zdog.Shape({
path: [
{ x: -cloakX0, y: cloakY2 },
{ x: cloakX0, y: cloakY2 },
{ x: cloakX0, y: cloakY3 },
{ x: -cloakX0, y: cloakY3 },
],
addTo: cloakSide,
translate: { z: cloakZ2 },
fill: true,
color: garnet,
stroke: 4,
});
cloakSide.copyGraph({
scale: { z: -1 },
});
// ----- animate ----- //
function animate() {
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/kirby-parasol/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>kirby parasol</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
background: #425;
color: white;
}
.illo {
display: block;
width: 100%;
height: 100%;
cursor: move;
}
</style>
</head>
<body>
<canvas class="illo"></canvas>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/group.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="kirby-parasol.js"></script>
</body>
</html>
================================================
FILE: demos/kirby-parasol/kirby-parasol.js
================================================
// ----- setup ----- //
var sceneSize = 80;
var isSpinning = true;
var TAU = Zdog.TAU;
// colors
var pink = '#F8B';
var blush = '#F5A';
var black = '#333';
var shoe = '#D03';
var red = '#E10';
var yellow = '#FD0';
var illo = new Zdog.Illustration({
element: '.illo',
dragRotate: true,
resize: 'fullscreen',
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.floor( Math.min( width, height ) / sceneSize );
},
});
// ----- model ----- //
var body = new Zdog.Shape({
stroke: 22,
translate: { y: 11 },
rotate: { x: 0.3, z: 0.1 },
addTo: illo,
color: pink,
});
var face = new Zdog.Anchor({
translate: { z: 10.5 },
addTo: body,
});
[ -1, 1 ].forEach( function( xSide ) {
var eyeGroup = new Zdog.Group({
addTo: face,
translate: { x: 2.4 * xSide, y: -2 },
rotate: { x: -0.1 },
});
// eye
new Zdog.Ellipse({
width: 1.4,
height: 5.5,
addTo: eyeGroup,
stroke: 1,
color: black,
fill: true,
});
// eye highlight
new Zdog.Ellipse({
width: 1,
height: 2,
addTo: eyeGroup,
translate: { y: -1.5, z: 0.5 },
stroke: 0.5,
color: '#FFF',
fill: true,
});
// cheek holder
var cheekHolder = new Zdog.Anchor({
addTo: body,
rotate: { y: 0.6 * xSide },
});
new Zdog.Ellipse({
width: 2.5,
height: 1,
translate: { y: 1, z: 10.5 },
addTo: cheekHolder,
color: blush,
stroke: 1,
});
} );
// mouth
new Zdog.Shape({
path: [
{ x: 0, y: 0 },
{ bezier: [
{ x: 1.1, y: 0 },
{ x: 1.1, y: 0.2 },
{ x: 1.1, y: 0.5 },
] },
{ bezier: [
{ x: 1.1, y: 1.1 },
{ x: 0.2, y: 1.8 },
{ x: 0, y: 1.8 },
] },
{ bezier: [
{ x: -0.2, y: 1.8 },
{ x: -1.1, y: 1.1 },
{ x: -1.1, y: 0.5 },
] },
{ bezier: [
{ x: -1.1, y: 0.2 },
{ x: -1.1, y: 0 },
{ x: 0, y: 0 },
] },
],
addTo: face,
translate: { y: 2, z: -0.5 },
stroke: 1,
color: shoe,
fill: true,
});
var rightArm = new Zdog.Shape({
path: [
{ y: 0 },
{ y: -7 },
],
addTo: body,
translate: { x: -6, y: -4, z: 0 },
color: pink,
stroke: 7,
});
// left arm
rightArm.copy({
path: [
{ x: 0 },
{ x: 6 },
],
translate: { x: 6, y: -2, z: 0 },
});
// right foot
var rightFoot = new Zdog.Shape({
path: [
{ x: 0, y: -2 },
{ arc: [
{ x: 2, y: -2 },
{ x: 2, y: 0 },
] },
{ arc: [
{ x: 2, y: 5 },
{ x: 0, y: 5 },
] },
{ arc: [
{ x: -2, y: 5 },
{ x: -2, y: 0 },
] },
{ arc: [
{ x: -2, y: -2 },
{ x: 0, y: -2 },
] },
],
addTo: body,
translate: { x: -1, y: 9, z: -9 },
rotate: { z: 0.2 },
stroke: 6,
color: shoe,
fill: true,
closed: false,
});
rightFoot.copy({
translate: { x: 9.5, y: 6, z: -6 },
rotate: { z: -1.1, y: 0.8 },
});
// ----- umbrella ----- //
// umbrella rod
var umbrella = new Zdog.Shape({
path: [
{ y: 0 },
{ y: 22 },
],
addTo: rightArm,
translate: { y: -33, z: 2 },
rotate: { y: 0.5 },
color: yellow,
stroke: 1,
});
// star
var starPath = ( function() {
var path = [];
var starRadiusA = 3;
var starRadiusB = 1.7;
for ( var i = 0; i < 10; i++ ) {
var radius = i % 2 ? starRadiusA : starRadiusB;
var angle = TAU * i/10 + TAU/4;
var point = {
x: Math.cos( angle ) * radius,
y: Math.sin( angle ) * radius,
};
path.push( point );
}
return path;
} )();
// star shape
var star = new Zdog.Shape({
path: starPath,
addTo: umbrella,
translate: { y: -4.5 },
stroke: 2,
color: yellow,
fill: true,
});
// umbrella handle
new Zdog.Shape({
path: [
{ z: 0, y: 0 },
{ z: 0, y: 1 },
{ arc: [
{ z: 0, y: 4 },
{ z: 3, y: 4 },
] },
{ arc: [
{ z: 6, y: 4 },
{ z: 6, y: 1 },
] },
],
addTo: umbrella,
translate: { y: 23 },
stroke: 2,
color: '#37F',
closed: false,
});
// umbrella shield panels
( function() {
var umbPanelX = 14 * Math.sin( TAU/24 );
var umbPanelZ = 14 * Math.cos( TAU/24 );
for ( var i = 0; i < 12; i++ ) {
var colorSide = Math.floor( i/2 ) % 2;
new Zdog.Shape({
path: [
{ x: 0, y: 0, z: 0 },
{ arc: [
{ x: -umbPanelX, y: 0, z: umbPanelZ },
{ x: -umbPanelX, y: 14, z: umbPanelZ },
] },
{ x: umbPanelX, y: 14, z: umbPanelZ },
{ arc: [
{ x: umbPanelX, y: 0, z: umbPanelZ },
{ x: 0, y: 0, z: 0 },
] },
],
addTo: umbrella,
rotate: { y: TAU/12 * i },
stroke: 1,
color: colorSide ? red : 'white',
fill: true,
});
}
} )();
// floater stars
( function() {
for ( var i = 0; i < 6; i++ ) {
var starHolder = new Zdog.Anchor({
addTo: umbrella,
translate: { y: 10 },
rotate: { y: TAU/6 * i + TAU/24 },
});
star.copy({
addTo: starHolder,
translate: { z: 28 },
});
}
} )();
// ----- animate ----- //
function animate() {
illo.rotate.y += isSpinning ? -0.03 : 0;
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/no-illo-canvas/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>No Illo canvas</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
text-align: center;
}
.zdog-canvas {
display: block;
margin: 20px auto;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<div class="container">
<h1>No Illo canvas</h1>
<canvas class="zdog-canvas" width="300" height="300"></canvas>
</div>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/dragger.js"></script>
<script src="no-illo-canvas.js"></script>
</body>
</html>
================================================
FILE: demos/no-illo-canvas/no-illo-canvas.js
================================================
// ----- setup ----- //
// get canvas element and its context
var canvas = document.querySelector('.zdog-canvas');
var ctx = canvas.getContext('2d');
// get canvas size
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
// illustration variables
var zoom = 5;
var isSpinning = true;
var TAU = Zdog.TAU;
// ----- model ----- //
var scene = new Zdog.Anchor();
// circle
new Zdog.Ellipse({
addTo: scene,
diameter: 20,
translate: { z: 10 },
stroke: 5,
color: '#636',
});
// square
new Zdog.Rect({
addTo: scene,
width: 20,
height: 20,
translate: { z: -10 },
stroke: 3,
color: '#E62',
fill: true,
});
// ----- animate ----- //
function animate() {
scene.rotate.y += isSpinning ? 0.03 : 0;
scene.updateGraph();
render();
requestAnimationFrame( animate );
}
function render() {
// clear canvas
ctx.clearRect( 0, 0, canvasWidth, canvasHeight );
ctx.save();
// center canvas & zoom
ctx.translate( canvasWidth/2, canvasHeight/2 );
ctx.scale( zoom, zoom );
// set lineJoin and lineCap to round
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
// render scene graph
scene.renderGraphCanvas( ctx );
ctx.restore();
}
animate();
// ----- drag ----- //
var dragStartRX, dragStartRY;
var minSize = Math.min( canvasWidth, canvasHeight );
// add drag-rotatation with Dragger
new Zdog.Dragger({
startElement: canvas,
onDragStart: function() {
isSpinning = false;
dragStartRX = scene.rotate.x;
dragStartRY = scene.rotate.y;
},
onDragMove: function( pointer, moveX, moveY ) {
scene.rotate.x = dragStartRX - ( moveY/minSize * TAU );
scene.rotate.y = dragStartRY - ( moveX/minSize * TAU );
},
});
================================================
FILE: demos/no-illo-svg/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>No Illo SVG</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
text-align: center;
}
.zdog-svg {
display: block;
margin: 20px auto;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<div class="container">
<h1>No Illo SVG</h1>
<svg class="zdog-svg" width="300" height="300"></svg>
</div>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/svg-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/dragger.js"></script>
<script src="no-illo-svg.js"></script>
</body>
</html>
================================================
FILE: demos/no-illo-svg/no-illo-svg.js
================================================
// ----- setup ----- //
// svg element
var svg = document.querySelector('svg');
// set size
var zoom = 5;
var svgWidth = svg.getAttribute('width');
var svgHeight = svg.getAttribute('height');
// set viewBox for zoom & centering
var viewWidth = svgWidth/zoom;
var viewHeight = svgHeight/zoom;
svg.setAttribute( 'viewBox', -viewWidth/2 + ' ' + -viewHeight/2 + ' ' +
viewWidth + ' ' + viewHeight );
// rendering variable
var isSpinning = true;
var TAU = Zdog.TAU;
var scene = new Zdog.Anchor();
// ----- model ----- //
// circle
new Zdog.Ellipse({
addTo: scene,
diameter: 20,
translate: { z: 10 },
stroke: 5,
color: '#636',
});
// square
new Zdog.Rect({
addTo: scene,
width: 20,
height: 20,
translate: { z: -10 },
stroke: 3,
color: '#E62',
fill: true,
});
// ----- animate ----- //
function animate() {
scene.rotate.y += isSpinning ? 0.03 : 0;
scene.updateGraph();
render();
requestAnimationFrame( animate );
}
function render() {
empty( svg );
scene.renderGraphSvg( svg );
}
animate();
function empty( element ) {
while ( element.firstChild ) {
element.removeChild( element.firstChild );
}
}
// ----- drag ----- //
var dragStartRX, dragStartRY;
var minSize = Math.min( svgWidth, svgHeight );
// add drag-rotatation with Dragger
new Zdog.Dragger({
startElement: svg,
onDragStart: function() {
isSpinning = false;
dragStartRX = scene.rotate.x;
dragStartRY = scene.rotate.y;
},
onDragMove: function( pointer, moveX, moveY ) {
scene.rotate.x = dragStartRX - ( moveY/minSize * TAU );
scene.rotate.y = dragStartRY - ( moveX/minSize * TAU );
},
});
================================================
FILE: demos/path-commands/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Path commands</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
text-align: center;
}
.illo {
display: block;
margin: 20px auto;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<div class="container">
<h1>Path commands</h1>
<canvas class="illo" width="300" height="300"></canvas>
</div>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="path-commands.js"></script>
</body>
</html>
================================================
FILE: demos/path-commands/path-commands.js
================================================
// ----- variables ----- //
var eggplant = '#636';
// ----- model ----- //
var illo = new Zdog.Illustration({
element: '.illo',
zoom: 5,
dragRotate: true,
});
// lines
new Zdog.Shape({
addTo: illo,
path: [
{ x: -6, y: -6 },
{ x: 6, y: -6 },
{ x: -6, y: 6 },
{ x: 6, y: 6 },
],
translate: { x: -12, y: -12 },
closed: false,
color: eggplant,
stroke: 2,
});
// move
new Zdog.Shape({
addTo: illo,
path: [
{ x: -6, y: -6 },
{ x: 6, y: -6 },
{ move: { x: -6, y: 6 } },
{ x: 6, y: 6 },
],
translate: { x: 12, y: -12 },
closed: false,
color: eggplant,
stroke: 2,
});
// arc
new Zdog.Shape({
addTo: illo,
path: [
{ x: -6, y: -6 }, // start
{ arc: [
{ x: 2, y: -6 }, // corner
{ x: 2, y: 2 }, // end point
] },
{ arc: [ // start next arc from last end point
{ x: 2, y: 6 }, // corner
{ x: 6, y: 6 }, // end point
] },
],
translate: { x: -12, y: 12 },
closed: false,
color: eggplant,
stroke: 2,
});
// bezier
new Zdog.Shape({
addTo: illo,
path: [
{ x: -6, y: -6 }, // start
{ bezier: [
{ x: 2, y: -6 }, // start control point
{ x: 2, y: 6 }, // end control point
{ x: 6, y: 6 }, // end control point
] },
],
translate: { x: 12, y: 12 },
closed: false,
color: eggplant,
stroke: 2,
});
// ----- animate ----- //
function animate() {
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/resize/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>resize</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
background: white;
font-family: sans-serif;
text-align: center;
}
.illo {
display: block;
cursor: move;
background: #FDB;
border-radius: 8px;
margin: 20px 2%;
width: 46%;
}
</style>
</head>
<body>
<canvas class="illo" width="200" height="200"></canvas>
<svg class="illo" width="200" height="200"></svg>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/svg-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/group.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="resize.js"></script>
</body>
</html>
================================================
FILE: demos/resize/resize.js
================================================
// ----- setup ----- //
var zoom = 4;
var isSpinning = true;
var gold = '#EA0';
var orange = '#E62';
var garnet = '#C25';
var eggplant = '#636';
var model = new Zdog.Anchor();
var canvasIllo = new Zdog.Illustration({
element: 'canvas',
zoom: zoom,
resize: true,
dragRotate: model,
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.min( width, height ) / 50;
},
});
var svgIllo = new Zdog.Illustration({
element: 'svg',
zoom: zoom,
resize: true,
dragRotate: model,
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.min( width, height ) / 50;
},
});
// HACK set initial zoom for SVG
svgIllo.setSize( svgIllo.width, svgIllo.height );
// ----- model ----- //
new Zdog.Rect({
width: 20,
height: 20,
addTo: model,
translate: { z: -10 },
stroke: 2,
color: garnet,
});
new Zdog.Ellipse({
diameter: 16,
addTo: model,
translate: { z: 10 },
stroke: 4,
color: eggplant,
});
new Zdog.Shape({
path: [
{ x: 0, z: 1 },
{ x: -1, z: -1 },
{ x: 1, z: -1 },
],
scale: { x: 5, z: 5 },
addTo: model,
stroke: 2,
fill: true,
color: gold,
});
new Zdog.Shape({
translate: { x: 10, y: -5 },
addTo: model,
stroke: 7,
color: orange,
});
model.copyGraph({
addTo: svgIllo,
});
// ----- animate ----- //
function animate() {
model.rotate.y += isSpinning ? 0.03 : 0;
model.updateGraph();
svgIllo.renderGraph( model );
canvasIllo.renderGraph( model );
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/shade-and-shades/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>shade & shades</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
}
.illo {
display: block;
width: 100%;
height: 100%;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<canvas class="illo"></canvas>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="shade-and-shades.js"></script>
</body>
</html>
================================================
FILE: demos/shade-and-shades/shade-and-shades.js
================================================
// ----- setup ----- //
var sceneSize = 96;
var orange = '#E62';
var eggplant = '#636';
// shape defaults
Zdog.Shape.defaults.closed = false;
[ Zdog.Shape, Zdog.Ellipse ].forEach( function( ShapeClass ) {
ShapeClass.defaults.stroke = 3;
ShapeClass.defaults.color = orange;
} );
var isSpinning = true;
var TAU = Zdog.TAU;
var initialRotate = { y: -TAU/8 };
var illo = new Zdog.Illustration({
element: '.illo',
rotate: initialRotate,
dragRotate: true,
resize: 'fullscreen',
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.floor( Math.min( width, height ) / sceneSize );
},
});
// ----- model ----- //
// cap top
[ 0, 1, 2, 3, 4 ].forEach( function( i ) {
new Zdog.Shape({
path: [
{ x: -20, y: 4 },
{ x: -20, y: 0 },
{ arc: [
{ x: -20, y: -20 },
{ x: 0, y: -20 },
] },
],
rotate: { y: TAU/6 * i - TAU/12 },
addTo: illo,
});
} );
// cap back
new Zdog.Ellipse({
addTo: illo,
diameter: 40,
quarters: 2,
translate: { y: 4 },
rotate: { x: TAU/4, z: -TAU/4 },
});
// cap back to brim bottom connect
var brimConnector = new Zdog.Shape({
path: [
{ x: -20, z: 0 },
{ arc: [
{ x: -20, z: 6 },
{ x: -16, z: 12 },
] },
],
addTo: illo,
translate: { y: 4 },
});
brimConnector.copy({
scale: { x: -1 },
});
// brim back arch
new Zdog.Ellipse({
addTo: illo,
diameter: 32,
quarters: 2,
translate: { y: 4, z: 12 },
rotate: { z: -TAU/4 },
});
var brimTip = new Zdog.Vector({ x: 0, y: -12, z: 34 });
var brimEdge = brimTip.copy();
brimEdge.x = -14;
// brim top line
new Zdog.Shape({
addTo: illo,
path: [
{ x: 0, y: -12, z: 12 },
brimTip,
],
});
var brimBridge = new Zdog.Shape({
addTo: illo,
path: [
{ x: -16, y: 4, z: 12 },
{ x: -16, y: 4, z: 18 },
{ bezier: [
{ x: -16, y: 4, z: 30 },
brimEdge,
brimTip,
] },
],
});
brimBridge.copy({
scale: { x: -1 },
});
// glasses front top
var glassFront = new Zdog.Shape({
addTo: illo,
path: [
{ x: -16 },
{ x: 16 },
],
translate: { y: 8, z: 12 },
color: eggplant,
});
// glass lens
var glassLens = new Zdog.Shape({
addTo: glassFront,
path: [
{ x: -1, y: -1 },
{ x: 1, y: -1 },
{ x: 1, y: 0 },
{ arc: [
{ x: 1, y: 1 },
{ x: 0, y: 1 },
] },
{ arc: [
{ x: -1, y: 1 },
{ x: -1, y: 0 },
] },
],
closed: true,
scale: 5,
translate: { x: -8, y: 5 },
color: eggplant,
fill: true,
});
glassLens.copy({
translate: { x: 8, y: 5 },
});
// glasses arm
var glassesArm = new Zdog.Shape({
addTo: illo,
path: [
{ x: 12, y: 0 },
{ x: -1, y: 0 },
{ arc: [
{ x: -12, y: 0 },
{ x: -12, y: 8 },
] },
],
rotate: { y: TAU/4 },
translate: { x: -16, y: 8 },
color: eggplant,
// only see one arm at time
backface: false,
});
glassesArm.copy({
scale: { x: -1 },
rotate: { y: -TAU/4 },
translate: { x: 16, y: 8 },
});
// ----- animate ----- //
var ticker = 0;
var cycleCount = 150;
function animate() {
if ( isSpinning ) {
var progress = ticker/cycleCount;
var tween = Zdog.easeInOut( progress % 1, 4 );
illo.rotate.y = tween * TAU + initialRotate.y;
ticker++;
}
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/shapes/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>shapes</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
}
.illo {
display: block;
width: 100%;
height: 100%;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<canvas class="illo"></canvas>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/svg-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/rounded-rect.js"></script>
<script src="../../js/polygon.js"></script>
<script src="../../js/group.js"></script>
<script src="../../js/hemisphere.js"></script>
<script src="../../js/cylinder.js"></script>
<script src="../../js/cone.js"></script>
<script src="../../js/box.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="shapes.js"></script>
</body>
</html>
================================================
FILE: demos/shapes/shapes.js
================================================
// ----- setup ----- //
var sceneSize = 24;
var isSpinning = true;
var TAU = Zdog.TAU;
var offWhite = '#FED';
var gold = '#EA0';
var orange = '#E62';
var garnet = '#C25';
var eggplant = '#636';
var illo = new Zdog.Illustration({
element: '.illo',
dragRotate: true,
resize: 'fullscreen',
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.floor( Math.min( width, height ) / sceneSize );
},
});
// ----- model ----- //
new Zdog.Rect({
addTo: illo,
width: 4,
height: 4,
translate: { x: -4, y: -4, z: 4 },
stroke: 1,
color: orange,
});
new Zdog.RoundedRect({
addTo: illo,
width: 4,
height: 4,
cornerRadius: 1,
translate: { x: -4, y: 4, z: -4 },
stroke: 1,
color: eggplant,
});
new Zdog.Ellipse({
addTo: illo,
diameter: 4,
translate: { x: 4, y: 4, z: 4 },
stroke: 1,
color: garnet,
});
new Zdog.Polygon({
addTo: illo,
sides: 3,
radius: 2.5,
translate: { x: 4, y: -4, z: -4 },
stroke: 1,
color: orange,
});
new Zdog.Shape({
addTo: illo,
path: [
{ x: -1 },
{ x: 1 },
{ move: { y: -1 } },
{ y: 1 },
{ move: { z: -1 } },
{ z: 1 },
],
scale: 1.25,
stroke: 1,
color: offWhite,
});
new Zdog.Hemisphere({
addTo: illo,
diameter: 5,
translate: { x: -4, y: -4, z: -4 },
color: garnet,
backface: gold,
stroke: false,
});
new Zdog.Cylinder({
addTo: illo,
diameter: 5,
length: 4,
translate: { x: -4, y: 4, z: 4 },
color: gold,
backface: offWhite,
stroke: false,
});
new Zdog.Cone({
addTo: illo,
diameter: 5,
length: 4,
translate: { x: 4, y: -4, z: 4 },
color: eggplant,
backface: garnet,
stroke: false,
});
new Zdog.Box({
addTo: illo,
width: 5,
height: 5,
depth: 5,
translate: { x: 4, y: 4, z: -4 },
color: orange,
topFace: gold,
leftFace: garnet,
rightFace: garnet,
bottomFace: eggplant,
stroke: false,
});
// ----- animate ----- //
var ticker = 0;
var cycleCount = 360;
function animate() {
if ( isSpinning ) {
var progress = ticker/cycleCount;
var theta = Zdog.easeInOut( progress % 1, 3 ) * TAU;
illo.rotate.y = theta * 2;
illo.rotate.x = Math.sin( theta ) * 0.5;
ticker++;
}
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/solids/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>solids</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
}
.illo {
display: block;
width: 100%;
height: 100%;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<canvas class="illo"></canvas>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/svg-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/polygon.js"></script>
<script src="../../js/group.js"></script>
<script src="../../js/hemisphere.js"></script>
<script src="../../js/cylinder.js"></script>
<script src="../../js/cone.js"></script>
<script src="../../js/box.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="solids.js"></script>
</body>
</html>
================================================
FILE: demos/solids/solids.js
================================================
// ----- setup ----- //
var illoElem = document.querySelector('.illo');
var sceneSize = 96;
var TAU = Zdog.TAU;
var ROOT3 = Math.sqrt( 3 );
var ROOT5 = Math.sqrt( 5 );
var PHI = ( 1 + ROOT5 )/2;
var isSpinning = true;
var viewRotation = new Zdog.Vector();
var displaySize;
// colors
var eggplant = '#636';
var garnet = '#C25';
var orange = '#E62';
var gold = '#EA0';
var yellow = '#ED0';
var illo = new Zdog.Illustration({
element: illoElem,
scale: 8,
resize: 'fullscreen',
onResize: function( width, height ) {
displaySize = Math.min( width, height );
this.zoom = Math.floor( displaySize/sceneSize );
},
});
var solids = [];
// ----- hourglass ----- //
( function() {
var hourglass = new Zdog.Anchor({
addTo: illo,
translate: { x: 0, y: -4 },
});
solids.push( hourglass );
var hemi = new Zdog.Hemisphere({
diameter: 2,
translate: { z: -1 },
addTo: hourglass,
color: garnet,
backface: orange,
stroke: false,
});
hemi.copy({
translate: { z: 1 },
rotate: { y: TAU/2 },
color: eggplant,
backface: gold,
});
} )();
// ----- sphere ----- //
( function() {
var sphere = new Zdog.Anchor({
addTo: illo,
translate: { x: -4, y: -4 },
});
solids.push( sphere );
var hemi = new Zdog.Hemisphere({
diameter: 2,
addTo: sphere,
color: orange,
backface: eggplant,
stroke: false,
});
hemi.copy({
rotate: { y: TAU/2 },
color: eggplant,
backface: orange,
});
} )();
// ----- cylinder ----- //
var cylinder = new Zdog.Cylinder({
diameter: 2,
length: 2,
addTo: illo,
translate: { x: 4, y: -4 },
// rotate: { x: TAU/4 },
color: gold,
backface: garnet,
stroke: false,
});
solids.push( cylinder );
// ----- cone ----- //
var cone = new Zdog.Anchor({
addTo: illo,
translate: { x: -4, y: 0 },
});
solids.push( cone );
new Zdog.Cone({
diameter: 2,
length: 2,
addTo: cone,
translate: { z: 1 },
rotate: { y: TAU/2 },
color: garnet,
backface: gold,
stroke: false,
});
// ----- tetrahedron ----- //
( function() {
var tetrahedron = new Zdog.Anchor({
addTo: illo,
translate: { x: 0, y: 0 },
scale: 2.5,
});
var radius = 0.5;
var inradius = Math.cos( TAU/6 ) * radius;
var height = radius + inradius;
solids.push( tetrahedron );
var triangle = new Zdog.Polygon({
sides: 3,
radius: radius,
addTo: tetrahedron,
translate: { y: height/2 },
fill: true,
stroke: false,
color: eggplant,
// backface: false,
});
for ( var i = 0; i < 3; i++ ) {
var rotor1 = new Zdog.Anchor({
addTo: tetrahedron,
rotate: { y: TAU/3 * -i },
});
var rotor2 = new Zdog.Anchor({
addTo: rotor1,
translate: { z: inradius, y: height/2 },
rotate: { x: Math.acos( 1/3 ) * -1 + TAU/4 },
});
triangle.copy({
addTo: rotor2,
translate: { y: -inradius },
color: [ gold, garnet, orange ][i],
});
}
triangle.rotate.set({ x: -TAU/4, z: -TAU/2 });
} )();
// ----- octahedron ----- //
( function() {
var octahedron = new Zdog.Anchor({
addTo: illo,
translate: { x: -4, y: 4 },
scale: 1.75,
});
solids.push( octahedron );
var colorWheel = [ eggplant, garnet, orange, gold, yellow ];
// radius of triangle with side length = 1
var radius = ROOT3/2 * 2/3;
var height = radius * 3/2;
var tilt = Math.asin( 0.5/height );
[ -1, 1 ].forEach( function( ySide ) {
for ( var i = 0; i < 4; i++ ) {
var rotor = new Zdog.Anchor({
addTo: octahedron,
rotate: { y: TAU/4 * ( i + 1.5 ) * -1 },
});
var anchor = new Zdog.Anchor({
addTo: rotor,
translate: { z: 0.5 },
rotate: { x: tilt * ySide },
// scale: { y: -ySide },
});
new Zdog.Polygon({
sides: 3,
radius: radius,
addTo: anchor,
translate: { y: -radius/2 * ySide },
scale: { y: ySide },
stroke: false,
fill: true,
color: colorWheel[ i + 0.5 + 0.5 * ySide ],
backface: false,
});
}
} );
} )();
// ----- cube ----- //
var cube = new Zdog.Box({
addTo: illo,
width: 2,
height: 2,
depth: 2,
translate: { x: 4, y: 0 },
topFace: yellow,
frontFace: gold,
leftFace: orange,
rightFace: orange,
rearFace: garnet,
bottomFace: eggplant,
stroke: false,
});
solids.push( cube );
// ----- dodecahedron ----- //
( function() {
var dodecahedron = new Zdog.Anchor({
addTo: illo,
translate: { x: 0, y: 4 },
scale: 0.75,
});
solids.push( dodecahedron );
// https://en.wikipedia.org/wiki/Regular_dodecahedron#Dimensions
var midradius = ( PHI * PHI )/2;
// top & bottom faces
var face = new Zdog.Polygon({
sides: 5,
radius: 1,
addTo: dodecahedron,
translate: { y: -midradius },
rotate: { x: TAU/4 },
fill: true,
stroke: false,
color: yellow,
// backface: false,
});
face.copy({
translate: { y: midradius },
rotate: { x: -TAU/4 },
color: eggplant,
});
[ -1, 1 ].forEach( function( ySide ) {
var colorWheel = {
'-1': [ eggplant, garnet, gold, orange, garnet ],
1: [ yellow, gold, garnet, orange, gold ],
}[ ySide ];
for ( var i = 0; i < 5; i++ ) {
var rotor1 = new Zdog.Anchor({
addTo: dodecahedron,
rotate: { y: TAU/5 * i },
});
var rotor2 = new Zdog.Anchor({
addTo: rotor1,
rotate: { x: TAU/4 * ySide - Math.atan( 2 ) },
});
face.copy({
addTo: rotor2,
translate: { z: midradius },
rotate: { z: TAU/2 },
color: colorWheel[i],
});
}
} );
} )();
// ----- isocahedron ----- //
( function() {
var isocahedron = new Zdog.Anchor({
addTo: illo,
translate: { x: 4, y: 4 },
scale: 1.2,
});
solids.push( isocahedron );
// geometry
// radius of triangle with side length = 1
var faceRadius = ROOT3/2 * 2/3;
var faceHeight = faceRadius * 3/2;
var capApothem = 0.5 / Math.tan( TAU/10 );
var capRadius = 0.5 / Math.sin( TAU/10 );
var capTilt = Math.asin( capApothem/faceHeight );
var capSagitta = capRadius - capApothem;
var sideTilt = Math.asin( capSagitta/faceHeight );
var sideHeight = Math.sqrt( faceHeight * faceHeight - capSagitta * capSagitta );
// var colorWheel = [ eggplant, garnet, orange, gold, yellow ];
[ -1, 1 ].forEach( function( ySide ) {
var capColors = {
'-1': [ garnet, gold, yellow, gold, orange ],
1: [ gold, garnet, eggplant, garnet, orange ],
}[ ySide ];
var sideColors = {
'-1': [ garnet, gold, yellow, orange, garnet ],
1: [ gold, garnet, eggplant, orange, orange ],
}[ ySide ];
for ( var i = 0; i < 5; i++ ) {
var rotor = new Zdog.Anchor({
addTo: isocahedron,
rotate: { y: TAU/5 * -i },
translate: { y: sideHeight/2 * ySide },
});
var capRotateX = -capTilt;
var isYPos = ySide > 0;
capRotateX += isYPos ? TAU/2 : 0;
var capAnchor = new Zdog.Anchor({
addTo: rotor,
translate: { z: capApothem * ySide },
rotate: { x: capRotateX },
});
// cap face
var face = new Zdog.Polygon({
sides: 3,
radius: faceRadius,
addTo: capAnchor,
translate: { y: -faceRadius/2 },
stroke: false,
fill: true,
color: capColors[i],
// backface: false,
});
var sideRotateX = -sideTilt;
sideRotateX += isYPos ? 0 : TAU/2;
var sideAnchor = capAnchor.copy({
rotate: { x: sideRotateX },
});
face.copy({
addTo: sideAnchor,
translate: { y: -faceRadius/2 },
rotate: { y: TAU/2 },
color: sideColors[i],
});
}
} );
} )();
// ----- animate ----- //
var keyframes = [
{ x: 0, y: 0 },
{ x: 0, y: TAU },
{ x: TAU, y: TAU },
];
var ticker = 0;
var cycleCount = 180;
var turnLimit = keyframes.length - 1;
function animate() {
update();
illo.renderGraph();
requestAnimationFrame( animate );
}
animate();
function update() {
if ( isSpinning ) {
var progress = ticker/cycleCount;
var tween = Zdog.easeInOut( progress % 1, 4 );
var turn = Math.floor( progress % turnLimit );
var keyA = keyframes[ turn ];
var keyB = keyframes[ turn + 1 ];
viewRotation.x = Zdog.lerp( keyA.x, keyB.x, tween );
viewRotation.y = Zdog.lerp( keyA.y, keyB.y, tween );
ticker++;
}
solids.forEach( function( solid ) {
solid.rotate.set( viewRotation );
} );
illo.updateGraph();
}
// ----- inputs ----- //
var dragStartRX, dragStartRY;
new Zdog.Dragger({
startElement: illoElem,
onDragStart: function() {
isSpinning = false;
dragStartRX = viewRotation.x;
dragStartRY = viewRotation.y;
},
onDragMove: function( pointer, moveX, moveY ) {
viewRotation.x = dragStartRX - ( moveY/displaySize * TAU );
viewRotation.y = dragStartRY - ( moveX/displaySize * TAU );
},
});
================================================
FILE: demos/strutter/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>strutter</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
}
.illo {
display: block;
width: 100%;
height: 100%;
background: #FDB;
cursor: move;
}
</style>
</head>
<body>
<canvas class="illo"></canvas>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/rounded-rect.js"></script>
<script src="../../js/group.js"></script>
<script src="../../js/hemisphere.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="strutter.js"></script>
</body>
</html>
================================================
FILE: demos/strutter/strutter.js
================================================
// ----- setup ----- //
var sceneSize = 48;
var isSpinning = true;
var TAU = Zdog.TAU;
// colors
var gold = '#EA0';
var orange = '#C25';
var eggplant = '#636';
var midnight = '#424';
var illo = new Zdog.Illustration({
element: '.illo',
rotate: { y: -TAU/8 },
translate: { y: 4 },
dragRotate: true,
resize: 'fullscreen',
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.floor( Math.min( width, height ) / sceneSize );
},
});
// ----- model ----- //
var hipX = 3;
new Zdog.Shape({
addTo: illo,
path: [ { x: -1 }, { x: 1 } ],
scale: hipX,
color: eggplant,
stroke: 4,
});
var rightLeg = new Zdog.Shape({
addTo: illo,
path: [ { y: 0 }, { y: 12 } ],
translate: { x: -hipX },
rotate: { x: TAU/4 },
color: eggplant,
stroke: 4,
});
// foot
new Zdog.RoundedRect({
addTo: rightLeg,
width: 2,
height: 4,
cornerRadius: 1,
translate: { y: 14, z: 2 },
rotate: { x: TAU/4 },
color: orange,
fill: true,
stroke: 4,
});
var plantAngle = -TAU/32 * 3;
var leftLeg = rightLeg.copyGraph({
translate: { x: hipX },
rotate: { x: plantAngle },
color: midnight,
});
leftLeg.children[0].rotate.set({ x: TAU/4 - plantAngle });
// chest
new Zdog.Shape({
addTo: illo,
path: [ { x: -1 }, { x: 1 } ],
scale: 1.5,
translate: { y: -5.5, z: -3 },
color: orange,
stroke: 9,
fill: true,
});
var armSize = 6;
[ true, false ].forEach( function( isRight ) {
var xSide = isRight ? -1 : 1;
var upperArm = new Zdog.Shape({
addTo: illo,
path: [ { x: 0 }, { x: armSize } ],
scale: { x: xSide },
translate: { x: 4.5 * xSide, y: -8, z: -4 },
rotate: isRight ? { y: TAU/8, z: -TAU/16 } : { y: TAU/8 },
color: eggplant,
stroke: 4,
});
var forearm = new Zdog.Shape({
addTo: upperArm,
path: [ { x: 0 }, { x: armSize - 2 } ],
translate: { x: armSize },
rotate: isRight ? { z: TAU/16 * 3, y: TAU/4 } :
{ z: -TAU/4, x: -TAU/32 * 2, y: TAU/8 },
color: orange,
stroke: 4,
});
// hand
new Zdog.Shape({
addTo: forearm,
translate: { x: armSize, z: 1 },
stroke: 6,
color: gold,
});
} );
var head = new Zdog.Anchor({
addTo: illo,
translate: { y: -12, z: -10 },
rotate: { x: TAU/8 },
});
// face
new Zdog.Hemisphere({
addTo: head,
diameter: 12,
color: gold,
backface: orange,
rotate: { x: -TAU/4 },
stroke: false,
});
var eye = new Zdog.Ellipse({
addTo: head,
diameter: 2,
quarters: 2,
translate: { x: -2, y: 1.5, z: 5 },
rotate: { z: -TAU/4 },
color: eggplant,
stroke: 0.5,
backface: false,
});
eye.copy({
translate: { x: 2, y: 1.5, z: 5 },
rotate: { z: -TAU/4 },
});
// smile
new Zdog.Ellipse({
addTo: head,
diameter: 3,
quarters: 2,
translate: { y: 3, z: 4.5 },
rotate: { z: TAU/4 },
closed: true,
color: '#FED',
stroke: 0.5,
fill: true,
backface: false,
});
new Zdog.Hemisphere({
addTo: head,
diameter: 12,
color: orange,
backface: gold,
rotate: { x: TAU/4 },
stroke: false,
});
var brim = new Zdog.Anchor({
addTo: head,
scale: 5.5,
translate: { y: -0.5, z: 6 },
});
new Zdog.Shape({
addTo: brim,
path: [
{ x: 0, z: 0 },
{ arc: [
{ x: -1, z: 0 },
{ x: -1, z: -1 },
] },
{ x: -1, z: 0 },
],
color: eggplant,
fill: true,
});
new Zdog.Shape({
addTo: brim,
path: [
{ x: -1, z: 0 },
{ arc: [
{ x: -1, z: 1 },
{ x: 0, z: 1 },
] },
{ x: 0, z: 0 },
],
color: eggplant,
fill: true,
});
brim.copyGraph({
scale: brim.scale.copy().multiply({ x: -1 }),
});
// ----- animate ----- //
var ticker = 0;
var cycleCount = 150;
function animate() {
if ( isSpinning ) {
var progress = ticker/cycleCount;
illo.rotate.y = Zdog.easeInOut( progress % 1, 4 ) * TAU - TAU/8;
ticker++;
}
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
animate();
================================================
FILE: demos/zdog-logo/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>zdog logo</title>
<style>
html { height: 100%; }
body {
min-height: 100%;
margin: 0;
}
.illo {
display: block;
width: 100%;
height: 100%;
cursor: move;
background: #FDB;
}
</style>
</head>
<body>
<canvas class="illo"></canvas>
<script src="../../js/boilerplate.js"></script>
<script src="../../js/canvas-renderer.js"></script>
<script src="../../js/vector.js"></script>
<script src="../../js/anchor.js"></script>
<script src="../../js/path-command.js"></script>
<script src="../../js/shape.js"></script>
<script src="../../js/ellipse.js"></script>
<script src="../../js/rect.js"></script>
<script src="../../js/group.js"></script>
<script src="../../js/dragger.js"></script>
<script src="../../js/illustration.js"></script>
<script src="zdog-logo.js"></script>
</body>
</html>
================================================
FILE: demos/zdog-logo/zdog-logo.js
================================================
// ----- setup ----- //
var sceneSize = 100;
var isSpinning = true;
var TAU = Zdog.TAU;
var initRotate = { x: 20/360 * TAU, y: -50/360 * TAU };
var orange = '#E62';
var gold = '#EA0';
var eggplant = '#636';
var depth = 20;
var lineWidth = 8;
var illo = new Zdog.Illustration({
element: '.illo',
rotate: initRotate,
dragRotate: true,
resize: 'fullscreen',
onDragStart: function() {
isSpinning = false;
},
onResize: function( width, height ) {
this.zoom = Math.floor( Math.min( width, height ) * 2/sceneSize ) / 2;
},
});
// ----- model ----- //
var bigGroup = new Zdog.Group({
addTo: illo,
});
var backGroup = new Zdog.Group({
addTo: bigGroup,
updateSort: true,
});
// top
var topSide = new Zdog.Rect({
addTo: backGroup,
width: 40,
height: depth,
translate: { y: -20 },
rotate: { x: TAU/4 },
fill: true,
stroke: lineWidth,
color: orange,
});
topSide.copy({
translate: { y: 20 },
rotate: { x: -TAU/4 },
});
var endCap = new Zdog.Rect({
addTo: backGroup,
width: depth,
height: 8,
translate: { x: -20, y: -16 },
rotate: { y: TAU/4 },
fill: true,
color: orange,
stroke: lineWidth,
backface: false,
});
endCap.copy({
translate: { x: 20, y: 16 },
rotate: { y: -TAU/4 },
});
var cornerCap = endCap.copy({
height: 10,
translate: { x: -20, y: 15 },
});
cornerCap.copy({
translate: { x: 20, y: -15 },
rotate: { y: -TAU/4 },
});
var underside = new Zdog.Rect({
addTo: backGroup,
width: 30,
height: depth,
translate: { x: -5, y: -12 },
rotate: { x: -TAU/4 },
stroke: lineWidth,
fill: true,
color: orange,
});
underside.copy({
translate: { x: 5, y: 12 },
rotate: { x: TAU/4 },
});
var slopeW = 30;
var slopeH = 22;
var slopeAngle = Math.atan( slopeH/slopeW );
var slope = new Zdog.Rect({
addTo: backGroup,
width: Math.sqrt( slopeH * slopeH + slopeW * slopeW ),
height: depth,
translate: { x: -5, y: -1 },
rotate: { x: TAU/4, y: slopeAngle },
stroke: lineWidth,
fill: true,
color: orange,
backface: false,
});
slope.copy({
translate: { x: 5, y: 1 },
rotate: { x: -TAU/4, y: -slopeAngle },
});
// tail
new Zdog.Ellipse({
addTo: backGroup,
diameter: 32,
quarters: 1,
closed: false,
translate: { x: 22, y: -4 },
rotate: { z: TAU/4 },
color: orange,
stroke: lineWidth,
});
// tongue
var tongueAnchor = new Zdog.Anchor({
addTo: backGroup,
translate: { x: -6, y: -7 },
rotate: { y: TAU/4 },
});
var tongueH = 12;
var tongueS = 5;
var tongueTip = tongueH + tongueS;
new Zdog.Shape({
addTo: tongueAnchor,
path: [
{ x: -tongueS, y: 0 },
{ x: tongueS, y: 0 },
{ x: tongueS, y: tongueH },
{ arc: [
{ x: tongueS, y: tongueTip },
{ x: 0, y: tongueTip },
] },
{ arc: [
{ x: -tongueS, y: tongueTip },
{ x: -tongueS, y: tongueH },
] },
],
rotate: { x: TAU/4 - Math.atan( 16/22 ) },
fill: true,
stroke: 4,
color: eggplant,
});
var foreGroup = new Zdog.Group({
addTo: bigGroup,
updateSort: true,
});
var zFace = new Zdog.Shape({
addTo: foreGroup,
path: [
{ x: -20, y: -20 },
{ x: 20, y: -20 },
{ x: 20, y: -10 },
{ x: -10, y: 12 },
{ x: 20, y: 12 },
{ x: 20, y: 20 },
{ x: -20, y: 20 },
{ x: -20, y: 10 },
{ x: 10, y: -12 },
{ x: -20, y: -12 },
],
translate: { z: depth/2 },
fill: true,
color: gold,
stroke: lineWidth,
backface: false,
});
zFace.copy({
scale: { x: -1 },
translate: { z: -depth/2 },
rotate: { y: TAU/2 },
});
// nose
var semiCircle = new Zdog.Ellipse({
addTo: backGroup,
quarters: 2,
scale: 8,
translate: { x: -26, y: -20 },
rotate: { y: TAU/4, z: TAU/4 },
fill: true,
stroke: 5,
color: eggplant,
closed: true,
// backface: false,
});
// ears
// group & extra shape are hacks
var earGroup = new Zdog.Group({
addTo: illo,
});
var ear = semiCircle.copy({
addTo: earGroup,
quarters: 2,
scale: 24,
rotate: { z: -TAU/16, x: TAU/16 },
translate: { x: 10, y: -14, z: depth },
});
new Zdog.Shape({
visible: false,
addTo: ear,
translate: { z: 0.5, x: -0.5 },
});
earGroup.copyGraph({
scale: { z: -1 },
});
// ----- animate ----- //
var keyframes = [
{ y: 0 + initRotate.y, z: 0 },
{ y: TAU + initRotate.y, z: 0 },
{ y: TAU + initRotate.y, z: TAU },
];
var ticker = 0;
var cycleCount = 180;
var turnLimit = keyframes.length - 1;
function animate() {
spin();
illo.updateRenderGraph();
requestAnimationFrame( animate );
}
function spin() {
if ( !isSpinning ) {
return;
}
var progress = ticker/cycleCount;
var tween = Zdog.easeInOut( progress % 1, 4 );
var turn = Math.floor( progress % turnLimit );
var keyA = keyframes[ turn ];
var keyB = keyframes[ turn + 1 ];
illo.rotate.y = Zdog.lerp( keyA.y, keyB.y, tween );
illo.rotate.z = Zdog.lerp( keyA.z, keyB.z, tween );
ticker++;
}
animate();
================================================
FILE: dist/zdog.dist.js
================================================
/*!
* Zdog v1.1.3
* Round, flat, designer-friendly pseudo-3D engine
* Licensed MIT
* https://zzz.dog
* Copyright 2020 Metafizzy
*/
/**
* Boilerplate & utils
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog = factory();
}
}( this, function factory() {
var Zdog = {};
Zdog.TAU = Math.PI * 2;
Zdog.extend = function( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
};
Zdog.lerp = function( a, b, alpha ) {
return ( b - a ) * alpha + a;
};
Zdog.modulo = function( num, div ) {
return ( ( num % div ) + div ) % div;
};
var powerMultipliers = {
2: function( a ) {
return a * a;
},
3: function( a ) {
return a * a * a;
},
4: function( a ) {
return a * a * a * a;
},
5: function( a ) {
return a * a * a * a * a;
},
};
Zdog.easeInOut = function( alpha, power ) {
if ( power == 1 ) {
return alpha;
}
alpha = Math.max( 0, Math.min( 1, alpha ) );
var isFirstHalf = alpha < 0.5;
var slope = isFirstHalf ? alpha : 1 - alpha;
slope /= 0.5;
// make easing steeper with more multiples
var powerMultiplier = powerMultipliers[ power ] || powerMultipliers[2];
var curve = powerMultiplier( slope );
curve /= 2;
return isFirstHalf ? curve : 1 - curve;
};
return Zdog;
} ) );
/**
* CanvasRenderer
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog.CanvasRenderer = factory();
}
}( this, function factory() {
var CanvasRenderer = { isCanvas: true };
CanvasRenderer.begin = function( ctx ) {
ctx.beginPath();
};
CanvasRenderer.move = function( ctx, elem, point ) {
ctx.moveTo( point.x, point.y );
};
CanvasRenderer.line = function( ctx, elem, point ) {
ctx.lineTo( point.x, point.y );
};
CanvasRenderer.bezier = function( ctx, elem, cp0, cp1, end ) {
ctx.bezierCurveTo( cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y );
};
CanvasRenderer.closePath = function( ctx ) {
ctx.closePath();
};
CanvasRenderer.setPath = function() {};
CanvasRenderer.renderPath = function( ctx, elem, pathCommands, isClosed ) {
this.begin( ctx, elem );
pathCommands.forEach( function( command ) {
command.render( ctx, elem, CanvasRenderer );
} );
if ( isClosed ) {
this.closePath( ctx, elem );
}
};
CanvasRenderer.stroke = function( ctx, elem, isStroke, color, lineWidth ) {
if ( !isStroke ) {
return;
}
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.stroke();
};
CanvasRenderer.fill = function( ctx, elem, isFill, color ) {
if ( !isFill ) {
return;
}
ctx.fillStyle = color;
ctx.fill();
};
CanvasRenderer.end = function() {};
return CanvasRenderer;
} ) );
/**
* SvgRenderer
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog.SvgRenderer = factory();
}
}( this, function factory() {
var SvgRenderer = { isSvg: true };
// round path coordinates to 3 decimals
var round = SvgRenderer.round = function( num ) {
return Math.round( num * 1000 ) / 1000;
};
function getPointString( point ) {
return round( point.x ) + ',' + round( point.y ) + ' ';
}
SvgRenderer.begin = function() {};
SvgRenderer.move = function( svg, elem, point ) {
return 'M' + getPointString( point );
};
SvgRenderer.line = function( svg, elem, point ) {
return 'L' + getPointString( point );
};
SvgRenderer.bezier = function( svg, elem, cp0, cp1, end ) {
return 'C' + getPointString( cp0 ) + getPointString( cp1 ) +
getPointString( end );
};
SvgRenderer.closePath = function( /* elem */) {
return 'Z';
};
SvgRenderer.setPath = function( svg, elem, pathValue ) {
elem.setAttribute( 'd', pathValue );
};
SvgRenderer.renderPath = function( svg, elem, pathCommands, isClosed ) {
var pathValue = '';
pathCommands.forEach( function( command ) {
pathValue += command.render( svg, elem, SvgRenderer );
} );
if ( isClosed ) {
pathValue += this.closePath( svg, elem );
}
this.setPath( svg, elem, pathValue );
};
SvgRenderer.stroke = function( svg, elem, isStroke, color, lineWidth ) {
if ( !isStroke ) {
return;
}
elem.setAttribute( 'stroke', color );
elem.setAttribute( 'stroke-width', lineWidth );
};
SvgRenderer.fill = function( svg, elem, isFill, color ) {
var fillColor = isFill ? color : 'none';
elem.setAttribute( 'fill', fillColor );
};
SvgRenderer.end = function( svg, elem ) {
svg.appendChild( elem );
};
return SvgRenderer;
} ) );
/**
* Vector
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Vector = factory( Zdog );
}
}( this, function factory( utils ) {
function Vector( position ) {
this.set( position );
}
var TAU = utils.TAU;
// 'pos' = 'position'
Vector.prototype.set = function( pos ) {
this.x = pos && pos.x || 0;
this.y = pos && pos.y || 0;
this.z = pos && pos.z || 0;
return this;
};
// set coordinates without sanitizing
// vec.write({ y: 2 }) only sets y coord
Vector.prototype.write = function( pos ) {
if ( !pos ) {
return this;
}
this.x = pos.x != undefined ? pos.x : this.x;
this.y = pos.y != undefined ? pos.y : this.y;
this.z = pos.z != undefined ? pos.z : this.z;
return this;
};
Vector.prototype.rotate = function( rotation ) {
if ( !rotation ) {
return;
}
this.rotateZ( rotation.z );
this.rotateY( rotation.y );
this.rotateX( rotation.x );
return this;
};
Vector.prototype.rotateZ = function( angle ) {
rotateProperty( this, angle, 'x', 'y' );
};
Vector.prototype.rotateX = function( angle ) {
rotateProperty( this, angle, 'y', 'z' );
};
Vector.prototype.rotateY = function( angle ) {
rotateProperty( this, angle, 'x', 'z' );
};
function rotateProperty( vec, angle, propA, propB ) {
if ( !angle || angle % TAU === 0 ) {
return;
}
var cos = Math.cos( angle );
var sin = Math.sin( angle );
var a = vec[ propA ];
var b = vec[ propB ];
vec[ propA ] = a * cos - b * sin;
vec[ propB ] = b * cos + a * sin;
}
Vector.prototype.isSame = function( pos ) {
if ( !pos ) {
return false;
}
return this.x === pos.x && this.y === pos.y && this.z === pos.z;
};
Vector.prototype.add = function( pos ) {
if ( !pos ) {
return this;
}
this.x += pos.x || 0;
this.y += pos.y || 0;
this.z += pos.z || 0;
return this;
};
Vector.prototype.subtract = function( pos ) {
if ( !pos ) {
return this;
}
this.x -= pos.x || 0;
this.y -= pos.y || 0;
this.z -= pos.z || 0;
return this;
};
Vector.prototype.multiply = function( pos ) {
if ( pos == undefined ) {
return this;
}
// multiple all values by same number
if ( typeof pos == 'number' ) {
this.x *= pos;
this.y *= pos;
this.z *= pos;
} else {
// multiply object
this.x *= pos.x != undefined ? pos.x : 1;
this.y *= pos.y != undefined ? pos.y : 1;
this.z *= pos.z != undefined ? pos.z : 1;
}
return this;
};
Vector.prototype.transform = function( translation, rotation, scale ) {
this.multiply( scale );
this.rotate( rotation );
this.add( translation );
return this;
};
Vector.prototype.lerp = function( pos, alpha ) {
this.x = utils.lerp( this.x, pos.x || 0, alpha );
this.y = utils.lerp( this.y, pos.y || 0, alpha );
this.z = utils.lerp( this.z, pos.z || 0, alpha );
return this;
};
Vector.prototype.magnitude = function() {
var sum = this.x * this.x + this.y * this.y + this.z * this.z;
return getMagnitudeSqrt( sum );
};
function getMagnitudeSqrt( sum ) {
// PERF: check if sum ~= 1 and skip sqrt
if ( Math.abs( sum - 1 ) < 0.00000001 ) {
return 1;
}
return Math.sqrt( sum );
}
Vector.prototype.magnitude2d = function() {
var sum = this.x * this.x + this.y * this.y;
return getMagnitudeSqrt( sum );
};
Vector.prototype.copy = function() {
return new Vector( this );
};
return Vector;
} ) );
/**
* Anchor
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./canvas-renderer'), require('./svg-renderer') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Anchor = factory( Zdog, Zdog.Vector, Zdog.CanvasRenderer,
Zdog.SvgRenderer );
}
}( this, function factory( utils, Vector, CanvasRenderer, SvgRenderer ) {
var TAU = utils.TAU;
var onePoint = { x: 1, y: 1, z: 1 };
function Anchor( options ) {
this.create( options || {} );
}
Anchor.prototype.create = function( options ) {
this.children = [];
// set defaults & options
utils.extend( this, this.constructor.defaults );
this.setOptions( options );
// transform
this.translate = new Vector( options.translate );
this.rotate = new Vector( options.rotate );
this.scale = new Vector( onePoint ).multiply( this.scale );
// origin
this.origin = new Vector();
this.renderOrigin = new Vector();
if ( this.addTo ) {
this.addTo.addChild( this );
}
};
Anchor.defaults = {};
Anchor.optionKeys = Object.keys( Anchor.defaults ).concat([
'rotate',
'translate',
'scale',
'addTo',
]);
Anchor.prototype.setOptions = function( options ) {
var optionKeys = this.constructor.optionKeys;
for ( var key in options ) {
if ( optionKeys.indexOf( key ) != -1 ) {
this[ key ] = options[ key ];
}
}
};
Anchor.prototype.addChild = function( shape ) {
if ( this.children.indexOf( shape ) != -1 ) {
return;
}
shape.remove(); // remove previous parent
shape.addTo = this; // keep parent reference
this.children.push( shape );
};
Anchor.prototype.removeChild = function( shape ) {
var index = this.children.indexOf( shape );
if ( index != -1 ) {
this.children.splice( index, 1 );
}
};
Anchor.prototype.remove = function() {
if ( this.addTo ) {
this.addTo.removeChild( this );
}
};
// ----- update ----- //
Anchor.prototype.update = function() {
// update self
this.reset();
// update children
this.children.forEach( function( child ) {
child.update();
} );
this.transform( this.translate, this.rotate, this.scale );
};
Anchor.prototype.reset = function() {
this.renderOrigin.set( this.origin );
};
Anchor.prototype.transform = function( translation, rotation, scale ) {
this.renderOrigin.transform( translation, rotation, scale );
// transform children
this.children.forEach( function( child ) {
child.transform( translation, rotation, scale );
} );
};
Anchor.prototype.updateGraph = function() {
this.update();
this.updateFlatGraph();
this.flatGraph.forEach( function( item ) {
item.updateSortValue();
} );
// z-sort
this.flatGraph.sort( Anchor.shapeSorter );
};
Anchor.shapeSorter = function( a, b ) {
return a.sortValue - b.sortValue;
};
// custom getter to check for flatGraph before using it
Object.defineProperty( Anchor.prototype, 'flatGraph', {
get: function() {
if ( !this._flatGraph ) {
this.updateFlatGraph();
}
return this._flatGraph;
},
set: function( graph ) {
this._flatGraph = graph;
},
} );
Anchor.prototype.updateFlatGraph = function() {
this.flatGraph = this.getFlatGraph();
};
// return Array of self & all child graph items
Anchor.prototype.getFlatGraph = function() {
var flatGraph = [ this ];
return this.addChildFlatGraph( flatGraph );
};
Anchor.prototype.addChildFlatGraph = function( flatGraph ) {
this.children.forEach( function( child ) {
var childFlatGraph = child.getFlatGraph();
Array.prototype.push.apply( flatGraph, childFlatGraph );
} );
return flatGraph;
};
Anchor.prototype.updateSortValue = function() {
this.sortValue = this.renderOrigin.z;
};
// ----- render ----- //
Anchor.prototype.render = function() {};
// TODO refactor out CanvasRenderer so its not a dependency within anchor.js
Anchor.prototype.renderGraphCanvas = function( ctx ) {
if ( !ctx ) {
throw new Error( 'ctx is ' + ctx + '. ' +
'Canvas context required for render. Check .renderGraphCanvas( ctx ).' );
}
this.flatGraph.forEach( function( item ) {
item.render( ctx, CanvasRenderer );
} );
};
Anchor.prototype.renderGraphSvg = function( svg ) {
if ( !svg ) {
throw new Error( 'svg is ' + svg + '. ' +
'SVG required for render. Check .renderGraphSvg( svg ).' );
}
this.flatGraph.forEach( function( item ) {
item.render( svg, SvgRenderer );
} );
};
// ----- misc ----- //
Anchor.prototype.copy = function( options ) {
// copy options
var itemOptions = {};
var optionKeys = this.constructor.optionKeys;
optionKeys.forEach( function( key ) {
itemOptions[ key ] = this[ key ];
}, this );
// add set options
utils.extend( itemOptions, options );
var ItemClass = this.constructor;
return new ItemClass( itemOptions );
};
Anchor.prototype.copyGraph = function( options ) {
var clone = this.copy( options );
this.children.forEach( function( child ) {
child.copyGraph({
addTo: clone,
});
} );
return clone;
};
Anchor.prototype.normalizeRotate = function() {
this.rotate.x = utils.modulo( this.rotate.x, TAU );
this.rotate.y = utils.modulo( this.rotate.y, TAU );
this.rotate.z = utils.modulo( this.rotate.z, TAU );
};
// ----- subclass ----- //
function getSubclass( Super ) {
return function( defaults ) {
// create constructor
function Item( options ) {
this.create( options || {} );
}
Item.prototype = Object.create( Super.prototype );
Item.prototype.constructor = Item;
Item.defaults = utils.extend( {}, Super.defaults );
utils.extend( Item.defaults, defaults );
// create optionKeys
Item.optionKeys = Super.optionKeys.slice( 0 );
// add defaults keys to optionKeys, dedupe
Object.keys( Item.defaults ).forEach( function( key ) {
if ( !Item.optionKeys.indexOf( key ) != 1 ) {
Item.optionKeys.push( key );
}
} );
Item.subclass = getSubclass( Item );
return Item;
};
}
Anchor.subclass = getSubclass( Anchor );
return Anchor;
} ) );
/**
* Dragger
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog.Dragger = factory();
}
}( this, function factory() {
// quick & dirty drag event stuff
// messes up if multiple pointers/touches
// check for browser window #85
var hasWindow = typeof window != 'undefined';
// event support, default to mouse events
var downEvent = 'mousedown';
var moveEvent = 'mousemove';
var upEvent = 'mouseup';
if ( hasWindow ) {
if ( window.PointerEvent ) {
// PointerEvent, Chrome
downEvent = 'pointerdown';
moveEvent = 'pointermove';
upEvent = 'pointerup';
} else if ( 'ontouchstart' in window ) {
// Touch Events, iOS Safari
downEvent = 'touchstart';
moveEvent = 'touchmove';
upEvent = 'touchend';
}
}
function noop() {}
function Dragger( options ) {
this.create( options || {} );
}
Dragger.prototype.create = function( options ) {
this.onDragStart = options.onDragStart || noop;
this.onDragMove = options.onDragMove || noop;
this.onDragEnd = options.onDragEnd || noop;
this.bindDrag( options.startElement );
};
Dragger.prototype.bindDrag = function( element ) {
element = this.getQueryElement( element );
if ( !element ) {
return;
}
// disable browser gestures #53
element.style.touchAction = 'none';
element.addEventListener( downEvent, this );
};
Dragger.prototype.getQueryElement = function( element ) {
if ( typeof element == 'string' ) {
// with string, query selector
element = document.querySelector( element );
}
return element;
};
Dragger.prototype.handleEvent = function( event ) {
var method = this[ 'on' + event.type ];
if ( method ) {
method.call( this, event );
}
};
Dragger.prototype.onmousedown =
Dragger.prototype.onpointerdown = function( event ) {
this.dragStart( event, event );
};
Dragger.prototype.ontouchstart = function( event ) {
this.dragStart( event, event.changedTouches[0] );
};
Dragger.prototype.dragStart = function( event, pointer ) {
event.preventDefault();
this.dragStartX = pointer.pageX;
this.dragStartY = pointer.pageY;
if ( hasWindow ) {
window.addEventListener( moveEvent, this );
window.addEventListener( upEvent, this );
}
this.onDragStart( pointer );
};
Dragger.prototype.ontouchmove = function( event ) {
// HACK, moved touch may not be first
this.dragMove( event, event.changedTouches[0] );
};
Dragger.prototype.onmousemove =
Dragger.prototype.onpointermove = function( event ) {
this.dragMove( event, event );
};
Dragger.prototype.dragMove = function( event, pointer ) {
event.preventDefault();
var moveX = pointer.pageX - this.dragStartX;
var moveY = pointer.pageY - this.dragStartY;
this.onDragMove( pointer, moveX, moveY );
};
Dragger.prototype.onmouseup =
Dragger.prototype.onpointerup =
Dragger.prototype.ontouchend =
Dragger.prototype.dragEnd = function( /* event */) {
window.removeEventListener( moveEvent, this );
window.removeEventListener( upEvent, this );
this.onDragEnd();
};
return Dragger;
} ) );
/**
* Illustration
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./anchor'),
require('./dragger') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Illustration = factory( Zdog, Zdog.Anchor, Zdog.Dragger );
}
}( this, function factory( utils, Anchor, Dragger ) {
function noop() {}
var TAU = utils.TAU;
var Illustration = Anchor.subclass({
element: undefined,
centered: true,
zoom: 1,
dragRotate: false,
resize: false,
onPrerender: noop,
onDragStart: noop,
onDragMove: noop,
onDragEnd: noop,
onResize: noop,
});
utils.extend( Illustration.prototype, Dragger.prototype );
Illustration.prototype.create = function( options ) {
Anchor.prototype.create.call( this, options );
Dragger.prototype.create.call( this, options );
this.setElement( this.element );
this.setDragRotate( this.dragRotate );
this.setResize( this.resize );
};
Illustration.prototype.setElement = function( element ) {
element = this.getQueryElement( element );
if ( !element ) {
throw new Error( 'Zdog.Illustration element required. Set to ' + element );
}
var nodeName = element.nodeName.toLowerCase();
if ( nodeName == 'canvas' ) {
this.setCanvas( element );
} else if ( nodeName == 'svg' ) {
this.setSvg( element );
}
};
Illustration.prototype.setSize = function( width, height ) {
width = Math.round( width );
height = Math.round( height );
if ( this.isCanvas ) {
this.setSizeCanvas( width, height );
} else if ( this.isSvg ) {
this.setSizeSvg( width, height );
}
};
Illustration.prototype.setResize = function( resize ) {
this.resize = resize;
// create resize event listener
if ( !this.resizeListener ) {
this.resizeListener = this.onWindowResize.bind( this );
}
// add/remove event listener
if ( resize ) {
window.addEventListener( 'resize', this.resizeListener );
this.onWindowResize();
} else {
window.removeEventListener( 'resize', this.resizeListener );
}
};
// TODO debounce this?
Illustration.prototype.onWindowResize = function() {
this.setMeasuredSize();
this.onResize( this.width, this.height );
};
Illustration.prototype.setMeasuredSize = function() {
var width, height;
var isFullscreen = this.resize == 'fullscreen';
if ( isFullscreen ) {
width = window.innerWidth;
height = window.innerHeight;
} else {
var rect = this.element.getBoundingClientRect();
width = rect.width;
height = rect.height;
}
this.setSize( width, height );
};
// ----- render ----- //
Illustration.prototype.renderGraph = function( item ) {
if ( this.isCanvas ) {
this.renderGraphCanvas( item );
} else if ( this.isSvg ) {
this.renderGraphSvg( item );
}
};
// combo method
Illustration.prototype.updateRenderGraph = function( item ) {
this.updateGraph();
this.renderGraph( item );
};
// ----- canvas ----- //
Illustration.prototype.setCanvas = function( element ) {
this.element = element;
this.isCanvas = true;
// update related properties
this.ctx = this.element.getContext('2d');
// set initial size
this.setSizeCanvas( element.width, element.height );
};
Illustration.prototype.setSizeCanvas = function( width, height ) {
this.width = width;
this.height = height;
// up-rez for hi-DPI devices
var pixelRatio = this.pixelRatio = window.devicePixelRatio || 1;
this.element.width = this.canvasWidth = width * pixelRatio;
this.element.height = this.canvasHeight = height * pixelRatio;
var needsHighPixelRatioSizing = pixelRatio > 1 && !this.resize;
if ( needsHighPixelRatioSizing ) {
this.element.style.width = width + 'px';
this.element.style.height = height + 'px';
}
};
Illustration.prototype.renderGraphCanvas = function( item ) {
item = item || this;
this.prerenderCanvas();
Anchor.prototype.renderGraphCanvas.call( item, this.ctx );
this.postrenderCanvas();
};
Illustration.prototype.prerenderCanvas = function() {
var ctx = this.ctx;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.clearRect( 0, 0, this.canvasWidth, this.canvasHeight );
ctx.save();
if ( this.centered ) {
var centerX = this.width / 2 * this.pixelRatio;
var centerY = this.height / 2 * this.pixelRatio;
ctx.translate( centerX, centerY );
}
var scale = this.pixelRatio * this.zoom;
ctx.scale( scale, scale );
this.onPrerender( ctx );
};
Illustration.prototype.postrenderCanvas = function() {
this.ctx.restore();
};
// ----- svg ----- //
Illustration.prototype.setSvg = function( element ) {
this.element = element;
this.isSvg = true;
this.pixelRatio = 1;
// set initial size from width & height attributes
var width = element.getAttribute('width');
var height = element.getAttribute('height');
this.setSizeSvg( width, height );
};
Illustration.prototype.setSizeSvg = function( width, height ) {
this.width = width;
this.height = height;
var viewWidth = width / this.zoom;
var viewHeight = height / this.zoom;
var viewX = this.centered ? -viewWidth/2 : 0;
var viewY = this.centered ? -viewHeight/2 : 0;
this.element.setAttribute( 'viewBox', viewX + ' ' + viewY + ' ' +
viewWidth + ' ' + viewHeight );
if ( this.resize ) {
// remove size attributes, let size be determined by viewbox
this.element.removeAttribute('width');
this.element.removeAttribute('height');
} else {
this.element.setAttribute( 'width', width );
this.element.setAttribute( 'height', height );
}
};
Illustration.prototype.renderGraphSvg = function( item ) {
item = item || this;
empty( this.element );
this.onPrerender( this.element );
Anchor.prototype.renderGraphSvg.call( item, this.element );
};
function empty( element ) {
while ( element.firstChild ) {
element.removeChild( element.firstChild );
}
}
// ----- drag ----- //
Illustration.prototype.setDragRotate = function( item ) {
if ( !item ) {
return;
} else if ( item === true ) {
/* eslint consistent-this: "off" */
item = this;
}
this.dragRotate = item;
this.bindDrag( this.element );
};
Illustration.prototype.dragStart = function( /* event, pointer */) {
this.dragStartRX = this.dragRotate.rotate.x;
this.dragStartRY = this.dragRotate.rotate.y;
Dragger.prototype.dragStart.apply( this, arguments );
};
Illustration.prototype.dragMove = function( event, pointer ) {
var moveX = pointer.pageX - this.dragStartX;
var moveY = pointer.pageY - this.dragStartY;
var displaySize = Math.min( this.width, this.height );
var moveRY = moveX/displaySize * TAU;
var moveRX = moveY/displaySize * TAU;
this.dragRotate.rotate.x = this.dragStartRX - moveRX;
this.dragRotate.rotate.y = this.dragStartRY - moveRY;
Dragger.prototype.dragMove.apply( this, arguments );
};
return Illustration;
} ) );
/**
* PathCommand
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./vector') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.PathCommand = factory( Zdog.Vector );
}
}( this, function factory( Vector ) {
function PathCommand( method, points, previousPoint ) {
this.method = method;
this.points = points.map( mapVectorPoint );
this.renderPoints = points.map( mapNewVector );
this.previousPoint = previousPoint;
this.endRenderPoint = this.renderPoints[ this.renderPoints.length - 1 ];
// arc actions come with previous point & corner point
// but require bezier control points
if ( method == 'arc' ) {
this.controlPoints = [ new Vector(), new Vector() ];
}
}
function mapVectorPoint( point ) {
if ( point instanceof Vector ) {
return point;
} else {
return new Vector( point );
}
}
function mapNewVector( point ) {
return new Vector( point );
}
PathCommand.prototype.reset = function() {
// reset renderPoints back to orignal points position
var points = this.points;
this.renderPoints.forEach( function( renderPoint, i ) {
var point = points[i];
renderPoint.set( point );
} );
};
PathCommand.prototype.transform = function( translation, rotation, scale ) {
this.renderPoints.forEach( function( renderPoint ) {
renderPoint.transform( translation, rotation, scale );
} );
};
PathCommand.prototype.render = function( ctx, elem, renderer ) {
return this[ this.method ]( ctx, elem, renderer );
};
PathCommand.prototype.move = function( ctx, elem, renderer ) {
return renderer.move( ctx, elem, this.renderPoints[0] );
};
PathCommand.prototype.line = function( ctx, elem, renderer ) {
return renderer.line( ctx, elem, this.renderPoints[0] );
};
PathCommand.prototype.bezier = function( ctx, elem, renderer ) {
var cp0 = this.renderPoints[0];
var cp1 = this.renderPoints[1];
var end = this.renderPoints[2];
return renderer.bezier( ctx, elem, cp0, cp1, end );
};
var arcHandleLength = 9/16;
PathCommand.prototype.arc = function( ctx, elem, renderer ) {
var prev = this.previousPoint;
var corner = this.renderPoints[0];
var end = this.renderPoints[1];
var cp0 = this.controlPoints[0];
var cp1 = this.controlPoints[1];
cp0.set( prev ).lerp( corner, arcHandleLength );
cp1.set( end ).lerp( corner, arcHandleLength );
return renderer.bezier( ctx, elem, cp0, cp1, end );
};
return PathCommand;
} ) );
/**
* Shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./path-command'), require('./anchor') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Shape = factory( Zdog, Zdog.Vector, Zdog.PathCommand, Zdog.Anchor );
}
}( this, function factory( utils, Vector, PathCommand, Anchor ) {
var Shape = Anchor.subclass({
stroke: 1,
fill: false,
color: '#333',
closed: true,
visible: true,
path: [ {} ],
front: { z: 1 },
backface: true,
});
Shape.prototype.create = function( options ) {
Anchor.prototype.create.call( this, options );
this.updatePath();
// front
this.front = new Vector( options.front || this.front );
this.renderFront = new Vector( this.front );
this.renderNormal = new Vector();
};
var actionNames = [
'move',
'line',
'bezier',
'arc',
];
Shape.prototype.updatePath = function() {
this.setPath();
this.updatePathCommands();
};
// place holder for Ellipse, Rect, etc.
Shape.prototype.setPath = function() {};
// parse path into PathCommands
Shape.prototype.updatePathCommands = function() {
var previousPoint;
this.pathCommands = this.path.map( function( pathPart, i ) {
// pathPart can be just vector coordinates -> { x, y, z }
// or path instruction -> { arc: [ {x0,y0,z0}, {x1,y1,z1} ] }
var keys = Object.keys( pathPart );
var method = keys[0];
var points = pathPart[ method ];
// default to line if no instruction
var isInstruction = keys.length == 1 && actionNames.indexOf( method ) != -1;
if ( !isInstruction ) {
method = 'line';
points = pathPart;
}
// munge single-point methods like line & move without arrays
var isLineOrMove = method == 'line' || method == 'move';
var isPointsArray = Array.isArray( points );
if ( isLineOrMove && !isPointsArray ) {
points = [ points ];
}
// first action is always move
method = i === 0 ? 'move' : method;
// arcs require previous last point
var command = new PathCommand( method, points, previousPoint );
// update previousLastPoint
previousPoint = command.endRenderPoint;
return command;
} );
};
// ----- update ----- //
Shape.prototype.reset = function() {
this.renderOrigin.set( this.origin );
this.renderFront.set( this.front );
// reset command render points
this.pathCommands.forEach( function( command ) {
command.reset();
} );
};
Shape.prototype.transform = function( translation, rotation, scale ) {
// calculate render points backface visibility & cone/hemisphere shapes
this.renderOrigin.transform( translation, rotation, scale );
this.renderFront.transform( translation, rotation, scale );
this.renderNormal.set( this.renderOrigin ).subtract( this.renderFront );
// transform points
this.pathCommands.forEach( function( command ) {
command.transform( translation, rotation, scale );
} );
// transform children
this.children.forEach( function( child ) {
child.transform( translation, rotation, scale );
} );
};
Shape.prototype.updateSortValue = function() {
// sort by average z of all points
// def not geometrically correct, but works for me
var pointCount = this.pathCommands.length;
var firstPoint = this.pathCommands[0].endRenderPoint;
var lastPoint = this.pathCommands[ pointCount - 1 ].endRenderPoint;
// ignore the final point if self closing shape
var isSelfClosing = pointCount > 2 && firstPoint.isSame( lastPoint );
if ( isSelfClosing ) {
pointCount -= 1;
}
var sortValueTotal = 0;
for ( var i = 0; i < pointCount; i++ ) {
sortValueTotal += this.pathCommands[i].endRenderPoint.z;
}
this.sortValue = sortValueTotal/pointCount;
};
// ----- render ----- //
Shape.prototype.render = function( ctx, renderer ) {
var length = this.pathCommands.length;
if ( !this.visible || !length ) {
return;
}
// do not render if hiding backface
this.isFacingBack = this.renderNormal.z > 0;
if ( !this.backface && this.isFacingBack ) {
return;
}
if ( !renderer ) {
throw new Error( 'Zdog renderer required. Set to ' + renderer );
}
// render dot or path
var isDot = length == 1;
if ( renderer.isCanvas && isDot ) {
this.renderCanvasDot( ctx, renderer );
} else {
this.renderPath( ctx, renderer );
}
};
var TAU = utils.TAU;
// Safari does not render lines with no size, have to render circle instead
Shape.prototype.renderCanvasDot = function( ctx ) {
var lineWidth = this.getLineWidth();
if ( !lineWidth ) {
return;
}
ctx.fillStyle = this.getRenderColor();
var point = this.pathCommands[0].endRenderPoint;
ctx.beginPath();
var radius = lineWidth/2;
ctx.arc( point.x, point.y, radius, 0, TAU );
ctx.fill();
};
Shape.prototype.getLineWidth = function() {
if ( !this.stroke ) {
return 0;
}
if ( this.stroke == true ) {
return 1;
}
return this.stroke;
};
Shape.prototype.getRenderColor = function() {
// use backface color if applicable
var isBackfaceColor = typeof this.backface == 'string' && this.isFacingBack;
var color = isBackfaceColor ? this.backface : this.color;
return color;
};
Shape.prototype.renderPath = function( ctx, renderer ) {
var elem = this.getRenderElement( ctx, renderer );
var isTwoPoints = this.pathCommands.length == 2 &&
this.pathCommands[1].method == 'line';
var isClosed = !isTwoPoints && this.closed;
var color = this.getRenderColor();
renderer.renderPath( ctx, elem, this.pathCommands, isClosed );
renderer.stroke( ctx, elem, this.stroke, color, this.getLineWidth() );
renderer.fill( ctx, elem, this.fill, color );
renderer.end( ctx, elem );
};
var svgURI = 'http://www.w3.org/2000/svg';
Shape.prototype.getRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.svgElement ) {
// create svgElement
this.svgElement = document.createElementNS( svgURI, 'path' );
this.svgElement.setAttribute( 'stroke-linecap', 'round' );
this.svgElement.setAttribute( 'stroke-linejoin', 'round' );
}
return this.svgElement;
};
return Shape;
} ) );
/**
* Group
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./anchor') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Group = factory( Zdog.Anchor );
}
}( this, function factory( Anchor ) {
var Group = Anchor.subclass({
updateSort: false,
visible: true,
});
// ----- update ----- //
Group.prototype.updateSortValue = function() {
var sortValueTotal = 0;
this.flatGraph.forEach( function( item ) {
item.updateSortValue();
sortValueTotal += item.sortValue;
} );
// average sort value of all points
// def not geometrically correct, but works for me
this.sortValue = sortValueTotal / this.flatGraph.length;
if ( this.updateSort ) {
this.flatGraph.sort( Anchor.shapeSorter );
}
};
// ----- render ----- //
Group.prototype.render = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
this.flatGraph.forEach( function( item ) {
item.render( ctx, renderer );
} );
};
// actual group flatGraph only used inside group
Group.prototype.updateFlatGraph = function() {
// do not include self
var flatGraph = [];
this.flatGraph = this.addChildFlatGraph( flatGraph );
};
// do not include children, group handles rendering & sorting internally
Group.prototype.getFlatGraph = function() {
return [ this ];
};
return Group;
} ) );
/**
* Rect
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Rect = factory( Zdog.Shape );
}
}( this, function factory( Shape ) {
var Rect = Shape.subclass({
width: 1,
height: 1,
});
Rect.prototype.setPath = function() {
var x = this.width / 2;
var y = this.height / 2;
/* eslint key-spacing: "off" */
this.path = [
{ x: -x, y: -y },
{ x: x, y: -y },
{ x: x, y: y },
{ x: -x, y: y },
];
};
return Rect;
} ) );
/**
* RoundedRect
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.RoundedRect = factory( Zdog.Shape );
}
}( this, function factory( Shape ) {
var RoundedRect = Shape.subclass({
width: 1,
height: 1,
cornerRadius: 0.25,
closed: false,
});
RoundedRect.prototype.setPath = function() {
/* eslint
id-length: [ "error", { "min": 2, "exceptions": [ "x", "y" ] }],
key-spacing: "off" */
var xA = this.width / 2;
var yA = this.height / 2;
var shortSide = Math.min( xA, yA );
var cornerRadius = Math.min( this.cornerRadius, shortSide );
var xB = xA - cornerRadius;
var yB = yA - cornerRadius;
var path = [
// top right corner
{ x: xB, y: -yA },
{ arc: [
{ x: xA, y: -yA },
{ x: xA, y: -yB },
] },
];
// bottom right corner
if ( yB ) {
path.push({ x: xA, y: yB });
}
path.push({ arc: [
{ x: xA, y: yA },
{ x: xB, y: yA },
] });
// bottom left corner
if ( xB ) {
path.push({ x: -xB, y: yA });
}
path.push({ arc: [
{ x: -xA, y: yA },
{ x: -xA, y: yB },
] });
// top left corner
if ( yB ) {
path.push({ x: -xA, y: -yB });
}
path.push({ arc: [
{ x: -xA, y: -yA },
{ x: -xB, y: -yA },
] });
// back to top right corner
if ( xB ) {
path.push({ x: xB, y: -yA });
}
this.path = path;
};
return RoundedRect;
} ) );
/**
* Ellipse
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Ellipse = factory( Zdog.Shape );
}
}( this, function factory( Shape ) {
var Ellipse = Shape.subclass({
diameter: 1,
width: undefined,
height: undefined,
quarters: 4,
closed: false,
});
Ellipse.prototype.setPath = function() {
var width = this.width != undefined ? this.width : this.diameter;
var height = this.height != undefined ? this.height : this.diameter;
var x = width/2;
var y = height/2;
this.path = [
{ x: 0, y: -y },
{ arc: [ // top right
{ x: x, y: -y },
{ x: x, y: 0 },
] },
];
// bottom right
if ( this.quarters > 1 ) {
this.path.push({ arc: [
{ x: x, y: y },
{ x: 0, y: y },
] });
}
// bottom left
if ( this.quarters > 2 ) {
this.path.push({ arc: [
{ x: -x, y: y },
{ x: -x, y: 0 },
] });
}
// top left
if ( this.quarters > 3 ) {
this.path.push({ arc: [
{ x: -x, y: -y },
{ x: 0, y: -y },
] });
}
};
return Ellipse;
} ) );
/**
* Shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Polygon = factory( Zdog, Zdog.Shape );
}
}( this, function factory( utils, Shape ) {
var Polygon = Shape.subclass({
sides: 3,
radius: 0.5,
});
var TAU = utils.TAU;
Polygon.prototype.setPath = function() {
this.path = [];
for ( var i = 0; i < this.sides; i++ ) {
var theta = i / this.sides * TAU - TAU/4;
var x = Math.cos( theta ) * this.radius;
var y = Math.sin( theta ) * this.radius;
this.path.push({ x: x, y: y });
}
};
return Polygon;
} ) );
/**
* Hemisphere composite shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./anchor'), require('./ellipse') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Hemisphere = factory( Zdog, Zdog.Vector, Zdog.Anchor, Zdog.Ellipse );
}
}( this, function factory( utils, Vector, Anchor, Ellipse ) {
var Hemisphere = Ellipse.subclass({
fill: true,
});
var TAU = utils.TAU;
Hemisphere.prototype.create = function( /* options */) {
// call super
Ellipse.prototype.create.apply( this, arguments );
// composite shape, create child shapes
this.apex = new Anchor({
addTo: this,
translate: { z: this.diameter / 2 },
});
// vector used for calculation
this.renderCentroid = new Vector();
};
Hemisphere.prototype.updateSortValue = function() {
// centroid of hemisphere is 3/8 between origin and apex
this.renderCentroid.set( this.renderOrigin )
.lerp( this.apex.renderOrigin, 3/8 );
this.sortValue = this.renderCentroid.z;
};
Hemisphere.prototype.render = function( ctx, renderer ) {
this.renderDome( ctx, renderer );
// call super
Ellipse.prototype.render.apply( this, arguments );
};
Hemisphere.prototype.renderDome = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
var elem = this.getDomeRenderElement( ctx, renderer );
var contourAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x );
var domeRadius = this.diameter / 2 * this.renderNormal.magnitude();
var x = this.renderOrigin.x;
var y = this.renderOrigin.y;
if ( renderer.isCanvas ) {
// canvas
var startAngle = contourAngle + TAU/4;
var endAngle = contourAngle - TAU/4;
ctx.beginPath();
ctx.arc( x, y, domeRadius, startAngle, endAngle );
} else if ( renderer.isSvg ) {
// svg
contourAngle = ( contourAngle - TAU/4 ) / TAU * 360;
this.domeSvgElement.setAttribute( 'd', 'M ' + -domeRadius + ',0 A ' +
domeRadius + ',' + domeRadius + ' 0 0 1 ' + domeRadius + ',0' );
this.domeSvgElement.setAttribute( 'transform',
'translate(' + x + ',' + y + ' ) rotate(' + contourAngle + ')' );
}
renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );
renderer.fill( ctx, elem, this.fill, this.color );
renderer.end( ctx, elem );
};
var svgURI = 'http://www.w3.org/2000/svg';
Hemisphere.prototype.getDomeRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.domeSvgElement ) {
// create svgElement
this.domeSvgElement = document.createElementNS( svgURI, 'path' );
this.domeSvgElement.setAttribute( 'stroke-linecap', 'round' );
this.domeSvgElement.setAttribute( 'stroke-linejoin', 'round' );
}
return this.domeSvgElement;
};
return Hemisphere;
} ) );
/**
* Cylinder composite shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'),
require('./path-command'), require('./shape'), require('./group'),
require('./ellipse') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Cylinder = factory( Zdog, Zdog.PathCommand, Zdog.Shape,
Zdog.Group, Zdog.Ellipse );
}
}( this, function factory( utils, PathCommand, Shape, Group, Ellipse ) {
function noop() {}
// ----- CylinderGroup ----- //
var CylinderGroup = Group.subclass({
color: '#333',
updateSort: true,
});
CylinderGroup.prototype.create = function() {
Group.prototype.create.apply( this, arguments );
this.pathCommands = [
new PathCommand( 'move', [ {} ] ),
new PathCommand( 'line', [ {} ] ),
];
};
CylinderGroup.prototype.render = function( ctx, renderer ) {
this.renderCylinderSurface( ctx, renderer );
Group.prototype.render.apply( this, arguments );
};
CylinderGroup.prototype.renderCylinderSurface = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
// render cylinder surface
var elem = this.getRenderElement( ctx, renderer );
var frontBase = this.frontBase;
var rearBase = this.rearBase;
var scale = frontBase.renderNormal.magnitude();
var strokeWidth = frontBase.diameter * scale + frontBase.getLineWidth();
// set path command render points
this.pathCommands[0].renderPoints[0].set( frontBase.renderOrigin );
this.pathCommands[1].renderPoints[0].set( rearBase.renderOrigin );
if ( renderer.isCanvas ) {
ctx.lineCap = 'butt'; // nice
}
renderer.renderPath( ctx, elem, this.pathCommands );
renderer.stroke( ctx, elem, true, this.color, strokeWidth );
renderer.end( ctx, elem );
if ( renderer.isCanvas ) {
ctx.lineCap = 'round'; // reset
}
};
var svgURI = 'http://www.w3.org/2000/svg';
CylinderGroup.prototype.getRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.svgElement ) {
// create svgElement
this.svgElement = document.createElementNS( svgURI, 'path' );
}
return this.svgElement;
};
// prevent double-creation in parent.copyGraph()
// only create in Cylinder.create()
CylinderGroup.prototype.copyGraph = noop;
// ----- CylinderEllipse ----- //
var CylinderEllipse = Ellipse.subclass();
CylinderEllipse.prototype.copyGraph = noop;
// ----- Cylinder ----- //
var Cylinder = Shape.subclass({
diameter: 1,
length: 1,
frontFace: undefined,
fill: true,
});
var TAU = utils.TAU;
Cylinder.prototype.create = function( /* options */) {
// call super
Shape.prototype.create.apply( this, arguments );
// composite shape, create child shapes
// CylinderGroup to render cylinder surface then bases
this.group = new CylinderGroup({
addTo: this,
color: this.color,
visible: this.visible,
});
var baseZ = this.length / 2;
var baseColor = this.backface || true;
// front outside base
this.frontBase = this.group.frontBase = new Ellipse({
addTo: this.group,
diameter: this.diameter,
translate: { z: baseZ },
rotate: { y: TAU/2 },
color: this.color,
stroke: this.stroke,
fill: this.fill,
backface: this.frontFace || baseColor,
visible: this.visible,
});
// back outside base
this.rearBase = this.group.rearBase = this.frontBase.copy({
translate: { z: -baseZ },
rotate: { y: 0 },
backface: baseColor,
});
};
// Cylinder shape does not render anything
Cylinder.prototype.render = function() {};
// ----- set child properties ----- //
var childProperties = [ 'stroke', 'fill', 'color', 'visible' ];
childProperties.forEach( function( property ) {
// use proxy property for custom getter & setter
var _prop = '_' + property;
Object.defineProperty( Cylinder.prototype, property, {
get: function() {
return this[ _prop ];
},
set: function( value ) {
this[ _prop ] = value;
// set property on children
if ( this.frontBase ) {
this.frontBase[ property ] = value;
this.rearBase[ property ] = value;
this.group[ property ] = value;
}
},
} );
} );
// TODO child property setter for backface, frontBaseColor, & rearBaseColor
return Cylinder;
} ) );
/**
* Cone composite shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./path-command'), require('./anchor'), require('./ellipse') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Cone = factory( Zdog, Zdog.Vector, Zdog.PathCommand,
Zdog.Anchor, Zdog.Ellipse );
}
}( this, function factory( utils, Vector, PathCommand, Anchor, Ellipse ) {
var Cone = Ellipse.subclass({
length: 1,
fill: true,
});
var TAU = utils.TAU;
Cone.prototype.create = function( /* options */) {
// call super
Ellipse.prototype.create.apply( this, arguments );
// composite shape, create child shapes
this.apex = new Anchor({
addTo: this,
translate: { z: this.length },
});
// vectors used for calculation
this.renderApex = new Vector();
this.renderCentroid = new Vector();
this.tangentA = new Vector();
this.tangentB = new Vector();
this.surfacePathCommands = [
new PathCommand( 'move', [ {} ] ), // points set in renderConeSurface
new PathCommand( 'line', [ {} ] ),
new PathCommand( 'line', [ {} ] ),
];
};
Cone.prototype.updateSortValue = function() {
// center of cone is one third of its length
this.renderCentroid.set( this.renderOrigin )
.lerp( this.apex.renderOrigin, 1/3 );
this.sortValue = this.renderCentroid.z;
};
Cone.prototype.render = function( ctx, renderer ) {
this.renderConeSurface( ctx, renderer );
Ellipse.prototype.render.apply( this, arguments );
};
Cone.prototype.renderConeSurface = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
this.renderApex.set( this.apex.renderOrigin )
.subtract( this.renderOrigin );
var scale = this.renderNormal.magnitude();
var apexDistance = this.renderApex.magnitude2d();
var normalDistance = this.renderNormal.magnitude2d();
// eccentricity
var eccenAngle = Math.acos( normalDistance/scale );
var eccen = Math.sin( eccenAngle );
var radius = this.diameter / 2 * scale;
// does apex extend beyond eclipse of face
var isApexVisible = radius * eccen < apexDistance;
if ( !isApexVisible ) {
return;
}
// update tangents
var apexAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x ) +
TAU/2;
var projectLength = apexDistance/eccen;
var projectAngle = Math.acos( radius/projectLength );
// set tangent points
var tangentA = this.tangentA;
var tangentB = this.tangentB;
tangentA.x = Math.cos( projectAngle ) * radius * eccen;
tangentA.y = Math.sin( projectAngle ) * radius;
tangentB.set( this.tangentA );
tangentB.y *= -1;
tangentA.rotateZ( apexAngle );
tangentB.rotateZ( apexAngle );
tangentA.add( this.renderOrigin );
tangentB.add( this.renderOrigin );
this.setSurfaceRenderPoint( 0, tangentA );
this.setSurfaceRenderPoint( 1, this.apex.renderOrigin );
this.setSurfaceRenderPoint( 2, tangentB );
// render
var elem = this.getSurfaceRenderElement( ctx, renderer );
renderer.renderPath( ctx, elem, this.surfacePathCommands );
renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );
renderer.fill( ctx, elem, this.fill, this.color );
renderer.end( ctx, elem );
};
var svgURI = 'http://www.w3.org/2000/svg';
Cone.prototype.getSurfaceRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.surfaceSvgElement ) {
// create svgElement
this.surfaceSvgElement = document.createElementNS( svgURI, 'path' );
this.surfaceSvgElement.setAttribute( 'stroke-linecap', 'round' );
this.surfaceSvgElement.setAttribute( 'stroke-linejoin', 'round' );
}
return this.surfaceSvgElement;
};
Cone.prototype.setSurfaceRenderPoint = function( index, point ) {
var renderPoint = this.surfacePathCommands[ index ].renderPoints[0];
renderPoint.set( point );
};
return Cone;
} ) );
/**
* Box composite shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./anchor'),
require('./shape'), require('./rect') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Box = factory( Zdog, Zdog.Anchor, Zdog.Shape, Zdog.Rect );
}
}( this, function factory( utils, Anchor, Shape, Rect ) {
// ----- BoxRect ----- //
var BoxRect = Rect.subclass();
// prevent double-creation in parent.copyGraph()
// only create in Box.create()
BoxRect.prototype.copyGraph = function() {};
// ----- Box ----- //
var TAU = utils.TAU;
var faceNames = [
'frontFace',
'rearFace',
'leftFace',
'rightFace',
'topFace',
'bottomFace',
];
var boxDefaults = utils.extend( {}, Shape.defaults );
delete boxDefaults.path;
faceNames.forEach( function( faceName ) {
boxDefaults[ faceName ] = true;
} );
utils.extend( boxDefaults, {
width: 1,
height: 1,
depth: 1,
fill: true,
} );
var Box = Anchor.subclass( boxDefaults );
/* eslint-disable no-self-assign */
Box.prototype.create = function( options ) {
Anchor.prototype.create.call( this, options );
this.updatePath();
// HACK reset fill to trigger face setter
this.fill = this.fill;
};
Box.prototype.updatePath = function() {
// reset all faces to trigger setters
faceNames.forEach( function( faceName ) {
this[ faceName ] = this[ faceName ];
}, this );
};
/* eslint-enable no-self-assign */
faceNames.forEach( function( faceName ) {
var _faceName = '_' + faceName;
Object.defineProperty( Box.prototype, faceName, {
get: function() {
return this[ _faceName ];
},
set: function( value ) {
this[ _faceName ] = value;
this.setFace( faceName, value );
},
} );
} );
Box.prototype.setFace = function( faceName, value ) {
var rectProperty = faceName + 'Rect';
var rect = this[ rectProperty ];
// remove if false
if ( !value ) {
this.removeChild( rect );
return;
}
// update & add face
var options = this.getFaceOptions( faceName );
options.color = typeof value == 'string' ? value : this.color;
if ( rect ) {
// update previous
rect.setOptions( options );
} else {
// create new
rect = this[ rectProperty ] = new BoxRect( options );
}
rect.updatePath();
this.addChild( rect );
};
Box.prototype.getFaceOptions = function( faceName ) {
return {
frontFace: {
width: this.width,
height: this.height,
translate: { z: this.depth / 2 },
},
rearFace: {
width: this.width,
height: this.height,
translate: { z: -this.depth / 2 },
rotate: { y: TAU/2 },
},
leftFace: {
width: this.depth,
height: this.height,
translate: { x: -this.width / 2 },
rotate: { y: -TAU/4 },
},
rightFace: {
width: this.depth,
height: this.height,
translate: { x: this.width / 2 },
rotate: { y: TAU/4 },
},
topFace: {
width: this.width,
height: this.depth,
translate: { y: -this.height / 2 },
rotate: { x: -TAU/4 },
},
bottomFace: {
width: this.width,
height: this.depth,
translate: { y: this.height / 2 },
rotate: { x: TAU/4 },
},
}[ faceName ];
};
// ----- set face properties ----- //
var childProperties = [ 'color', 'stroke', 'fill', 'backface', 'front',
'visible' ];
childProperties.forEach( function( property ) {
// use proxy property for custom getter & setter
var _prop = '_' + property;
Object.defineProperty( Box.prototype, property, {
get: function() {
return this[ _prop ];
},
set: function( value ) {
this[ _prop ] = value;
faceNames.forEach( function( faceName ) {
var rect = this[ faceName + 'Rect' ];
var isFaceColor = typeof this[ faceName ] == 'string';
var isColorUnderwrite = property == 'color' && isFaceColor;
if ( rect && !isColorUnderwrite ) {
rect[ property ] = value;
}
}, this );
},
} );
} );
return Box;
} ) );
/**
* Index
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
require('./boilerplate'),
require('./canvas-renderer'),
require('./svg-renderer'),
require('./vector'),
require('./anchor'),
require('./dragger'),
require('./illustration'),
require('./path-command'),
require('./shape'),
require('./group'),
require('./rect'),
require('./rounded-rect'),
require('./ellipse'),
require('./polygon'),
require('./hemisphere'),
require('./cylinder'),
require('./cone'),
require('./box')
);
} else if ( typeof define == 'function' && define.amd ) {
/* globals define */ // AMD
define( 'zdog', [], root.Zdog );
}
/* eslint-disable max-params */
} )( this, function factory( Zdog, CanvasRenderer, SvgRenderer, Vector, Anchor,
Dragger, Illustration, PathCommand, Shape, Group, Rect, RoundedRect,
Ellipse, Polygon, Hemisphere, Cylinder, Cone, Box ) {
/* eslint-enable max-params */
Zdog.CanvasRenderer = CanvasRenderer;
Zdog.SvgRenderer = SvgRenderer;
Zdog.Vector = Vector;
Zdog.Anchor = Anchor;
Zdog.Dragger = Dragger;
Zdog.Illustration = Illustration;
Zdog.PathCommand = PathCommand;
Zdog.Shape = Shape;
Zdog.Group = Group;
Zdog.Rect = Rect;
Zdog.RoundedRect = RoundedRect;
Zdog.Ellipse = Ellipse;
Zdog.Polygon = Polygon;
Zdog.Hemisphere = Hemisphere;
Zdog.Cylinder = Cylinder;
Zdog.Cone = Cone;
Zdog.Box = Box;
return Zdog;
} );
================================================
FILE: js/anchor.js
================================================
/**
* Anchor
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./canvas-renderer'), require('./svg-renderer') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Anchor = factory( Zdog, Zdog.Vector, Zdog.CanvasRenderer,
Zdog.SvgRenderer );
}
}( this, function factory( utils, Vector, CanvasRenderer, SvgRenderer ) {
var TAU = utils.TAU;
var onePoint = { x: 1, y: 1, z: 1 };
function Anchor( options ) {
this.create( options || {} );
}
Anchor.prototype.create = function( options ) {
this.children = [];
// set defaults & options
utils.extend( this, this.constructor.defaults );
this.setOptions( options );
// transform
this.translate = new Vector( options.translate );
this.rotate = new Vector( options.rotate );
this.scale = new Vector( onePoint ).multiply( this.scale );
// origin
this.origin = new Vector();
this.renderOrigin = new Vector();
if ( this.addTo ) {
this.addTo.addChild( this );
}
};
Anchor.defaults = {};
Anchor.optionKeys = Object.keys( Anchor.defaults ).concat([
'rotate',
'translate',
'scale',
'addTo',
]);
Anchor.prototype.setOptions = function( options ) {
var optionKeys = this.constructor.optionKeys;
for ( var key in options ) {
if ( optionKeys.indexOf( key ) != -1 ) {
this[ key ] = options[ key ];
}
}
};
Anchor.prototype.addChild = function( shape ) {
if ( this.children.indexOf( shape ) != -1 ) {
return;
}
shape.remove(); // remove previous parent
shape.addTo = this; // keep parent reference
this.children.push( shape );
};
Anchor.prototype.removeChild = function( shape ) {
var index = this.children.indexOf( shape );
if ( index != -1 ) {
this.children.splice( index, 1 );
}
};
Anchor.prototype.remove = function() {
if ( this.addTo ) {
this.addTo.removeChild( this );
}
};
// ----- update ----- //
Anchor.prototype.update = function() {
// update self
this.reset();
// update children
this.children.forEach( function( child ) {
child.update();
} );
this.transform( this.translate, this.rotate, this.scale );
};
Anchor.prototype.reset = function() {
this.renderOrigin.set( this.origin );
};
Anchor.prototype.transform = function( translation, rotation, scale ) {
this.renderOrigin.transform( translation, rotation, scale );
// transform children
this.children.forEach( function( child ) {
child.transform( translation, rotation, scale );
} );
};
Anchor.prototype.updateGraph = function() {
this.update();
this.updateFlatGraph();
this.flatGraph.forEach( function( item ) {
item.updateSortValue();
} );
// z-sort
this.flatGraph.sort( Anchor.shapeSorter );
};
Anchor.shapeSorter = function( a, b ) {
return a.sortValue - b.sortValue;
};
// custom getter to check for flatGraph before using it
Object.defineProperty( Anchor.prototype, 'flatGraph', {
get: function() {
if ( !this._flatGraph ) {
this.updateFlatGraph();
}
return this._flatGraph;
},
set: function( graph ) {
this._flatGraph = graph;
},
} );
Anchor.prototype.updateFlatGraph = function() {
this.flatGraph = this.getFlatGraph();
};
// return Array of self & all child graph items
Anchor.prototype.getFlatGraph = function() {
var flatGraph = [ this ];
return this.addChildFlatGraph( flatGraph );
};
Anchor.prototype.addChildFlatGraph = function( flatGraph ) {
this.children.forEach( function( child ) {
var childFlatGraph = child.getFlatGraph();
Array.prototype.push.apply( flatGraph, childFlatGraph );
} );
return flatGraph;
};
Anchor.prototype.updateSortValue = function() {
this.sortValue = this.renderOrigin.z;
};
// ----- render ----- //
Anchor.prototype.render = function() {};
// TODO refactor out CanvasRenderer so its not a dependency within anchor.js
Anchor.prototype.renderGraphCanvas = function( ctx ) {
if ( !ctx ) {
throw new Error( 'ctx is ' + ctx + '. ' +
'Canvas context required for render. Check .renderGraphCanvas( ctx ).' );
}
this.flatGraph.forEach( function( item ) {
item.render( ctx, CanvasRenderer );
} );
};
Anchor.prototype.renderGraphSvg = function( svg ) {
if ( !svg ) {
throw new Error( 'svg is ' + svg + '. ' +
'SVG required for render. Check .renderGraphSvg( svg ).' );
}
this.flatGraph.forEach( function( item ) {
item.render( svg, SvgRenderer );
} );
};
// ----- misc ----- //
Anchor.prototype.copy = function( options ) {
// copy options
var itemOptions = {};
var optionKeys = this.constructor.optionKeys;
optionKeys.forEach( function( key ) {
itemOptions[ key ] = this[ key ];
}, this );
// add set options
utils.extend( itemOptions, options );
var ItemClass = this.constructor;
return new ItemClass( itemOptions );
};
Anchor.prototype.copyGraph = function( options ) {
var clone = this.copy( options );
this.children.forEach( function( child ) {
child.copyGraph({
addTo: clone,
});
} );
return clone;
};
Anchor.prototype.normalizeRotate = function() {
this.rotate.x = utils.modulo( this.rotate.x, TAU );
this.rotate.y = utils.modulo( this.rotate.y, TAU );
this.rotate.z = utils.modulo( this.rotate.z, TAU );
};
// ----- subclass ----- //
function getSubclass( Super ) {
return function( defaults ) {
// create constructor
function Item( options ) {
this.create( options || {} );
}
Item.prototype = Object.create( Super.prototype );
Item.prototype.constructor = Item;
Item.defaults = utils.extend( {}, Super.defaults );
utils.extend( Item.defaults, defaults );
// create optionKeys
Item.optionKeys = Super.optionKeys.slice( 0 );
// add defaults keys to optionKeys, dedupe
Object.keys( Item.defaults ).forEach( function( key ) {
if ( !Item.optionKeys.indexOf( key ) != 1 ) {
Item.optionKeys.push( key );
}
} );
Item.subclass = getSubclass( Item );
return Item;
};
}
Anchor.subclass = getSubclass( Anchor );
return Anchor;
} ) );
================================================
FILE: js/boilerplate.js
================================================
/*!
* Zdog v1.1.3
* Round, flat, designer-friendly pseudo-3D engine
* Licensed MIT
* https://zzz.dog
* Copyright 2020 Metafizzy
*/
/**
* Boilerplate & utils
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog = factory();
}
}( this, function factory() {
var Zdog = {};
Zdog.TAU = Math.PI * 2;
Zdog.extend = function( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
};
Zdog.lerp = function( a, b, alpha ) {
return ( b - a ) * alpha + a;
};
Zdog.modulo = function( num, div ) {
return ( ( num % div ) + div ) % div;
};
var powerMultipliers = {
2: function( a ) {
return a * a;
},
3: function( a ) {
return a * a * a;
},
4: function( a ) {
return a * a * a * a;
},
5: function( a ) {
return a * a * a * a * a;
},
};
Zdog.easeInOut = function( alpha, power ) {
if ( power == 1 ) {
return alpha;
}
alpha = Math.max( 0, Math.min( 1, alpha ) );
var isFirstHalf = alpha < 0.5;
var slope = isFirstHalf ? alpha : 1 - alpha;
slope /= 0.5;
// make easing steeper with more multiples
var powerMultiplier = powerMultipliers[ power ] || powerMultipliers[2];
var curve = powerMultiplier( slope );
curve /= 2;
return isFirstHalf ? curve : 1 - curve;
};
return Zdog;
} ) );
================================================
FILE: js/box.js
================================================
/**
* Box composite shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./anchor'),
require('./shape'), require('./rect') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Box = factory( Zdog, Zdog.Anchor, Zdog.Shape, Zdog.Rect );
}
}( this, function factory( utils, Anchor, Shape, Rect ) {
// ----- BoxRect ----- //
var BoxRect = Rect.subclass();
// prevent double-creation in parent.copyGraph()
// only create in Box.create()
BoxRect.prototype.copyGraph = function() {};
// ----- Box ----- //
var TAU = utils.TAU;
var faceNames = [
'frontFace',
'rearFace',
'leftFace',
'rightFace',
'topFace',
'bottomFace',
];
var boxDefaults = utils.extend( {}, Shape.defaults );
delete boxDefaults.path;
faceNames.forEach( function( faceName ) {
boxDefaults[ faceName ] = true;
} );
utils.extend( boxDefaults, {
width: 1,
height: 1,
depth: 1,
fill: true,
} );
var Box = Anchor.subclass( boxDefaults );
/* eslint-disable no-self-assign */
Box.prototype.create = function( options ) {
Anchor.prototype.create.call( this, options );
this.updatePath();
// HACK reset fill to trigger face setter
this.fill = this.fill;
};
Box.prototype.updatePath = function() {
// reset all faces to trigger setters
faceNames.forEach( function( faceName ) {
this[ faceName ] = this[ faceName ];
}, this );
};
/* eslint-enable no-self-assign */
faceNames.forEach( function( faceName ) {
var _faceName = '_' + faceName;
Object.defineProperty( Box.prototype, faceName, {
get: function() {
return this[ _faceName ];
},
set: function( value ) {
this[ _faceName ] = value;
this.setFace( faceName, value );
},
} );
} );
Box.prototype.setFace = function( faceName, value ) {
var rectProperty = faceName + 'Rect';
var rect = this[ rectProperty ];
// remove if false
if ( !value ) {
this.removeChild( rect );
return;
}
// update & add face
var options = this.getFaceOptions( faceName );
options.color = typeof value == 'string' ? value : this.color;
if ( rect ) {
// update previous
rect.setOptions( options );
} else {
// create new
rect = this[ rectProperty ] = new BoxRect( options );
}
rect.updatePath();
this.addChild( rect );
};
Box.prototype.getFaceOptions = function( faceName ) {
return {
frontFace: {
width: this.width,
height: this.height,
translate: { z: this.depth / 2 },
},
rearFace: {
width: this.width,
height: this.height,
translate: { z: -this.depth / 2 },
rotate: { y: TAU/2 },
},
leftFace: {
width: this.depth,
height: this.height,
translate: { x: -this.width / 2 },
rotate: { y: -TAU/4 },
},
rightFace: {
width: this.depth,
height: this.height,
translate: { x: this.width / 2 },
rotate: { y: TAU/4 },
},
topFace: {
width: this.width,
height: this.depth,
translate: { y: -this.height / 2 },
rotate: { x: -TAU/4 },
},
bottomFace: {
width: this.width,
height: this.depth,
translate: { y: this.height / 2 },
rotate: { x: TAU/4 },
},
}[ faceName ];
};
// ----- set face properties ----- //
var childProperties = [ 'color', 'stroke', 'fill', 'backface', 'front',
'visible' ];
childProperties.forEach( function( property ) {
// use proxy property for custom getter & setter
var _prop = '_' + property;
Object.defineProperty( Box.prototype, property, {
get: function() {
return this[ _prop ];
},
set: function( value ) {
this[ _prop ] = value;
faceNames.forEach( function( faceName ) {
var rect = this[ faceName + 'Rect' ];
var isFaceColor = typeof this[ faceName ] == 'string';
var isColorUnderwrite = property == 'color' && isFaceColor;
if ( rect && !isColorUnderwrite ) {
rect[ property ] = value;
}
}, this );
},
} );
} );
return Box;
} ) );
================================================
FILE: js/canvas-renderer.js
================================================
/**
* CanvasRenderer
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog.CanvasRenderer = factory();
}
}( this, function factory() {
var CanvasRenderer = { isCanvas: true };
CanvasRenderer.begin = function( ctx ) {
ctx.beginPath();
};
CanvasRenderer.move = function( ctx, elem, point ) {
ctx.moveTo( point.x, point.y );
};
CanvasRenderer.line = function( ctx, elem, point ) {
ctx.lineTo( point.x, point.y );
};
CanvasRenderer.bezier = function( ctx, elem, cp0, cp1, end ) {
ctx.bezierCurveTo( cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y );
};
CanvasRenderer.closePath = function( ctx ) {
ctx.closePath();
};
CanvasRenderer.setPath = function() {};
CanvasRenderer.renderPath = function( ctx, elem, pathCommands, isClosed ) {
this.begin( ctx, elem );
pathCommands.forEach( function( command ) {
command.render( ctx, elem, CanvasRenderer );
} );
if ( isClosed ) {
this.closePath( ctx, elem );
}
};
CanvasRenderer.stroke = function( ctx, elem, isStroke, color, lineWidth ) {
if ( !isStroke ) {
return;
}
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.stroke();
};
CanvasRenderer.fill = function( ctx, elem, isFill, color ) {
if ( !isFill ) {
return;
}
ctx.fillStyle = color;
ctx.fill();
};
CanvasRenderer.end = function() {};
return CanvasRenderer;
} ) );
================================================
FILE: js/cone.js
================================================
/**
* Cone composite shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./path-command'), require('./anchor'), require('./ellipse') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Cone = factory( Zdog, Zdog.Vector, Zdog.PathCommand,
Zdog.Anchor, Zdog.Ellipse );
}
}( this, function factory( utils, Vector, PathCommand, Anchor, Ellipse ) {
var Cone = Ellipse.subclass({
length: 1,
fill: true,
});
var TAU = utils.TAU;
Cone.prototype.create = function( /* options */) {
// call super
Ellipse.prototype.create.apply( this, arguments );
// composite shape, create child shapes
this.apex = new Anchor({
addTo: this,
translate: { z: this.length },
});
// vectors used for calculation
this.renderApex = new Vector();
this.renderCentroid = new Vector();
this.tangentA = new Vector();
this.tangentB = new Vector();
this.surfacePathCommands = [
new PathCommand( 'move', [ {} ] ), // points set in renderConeSurface
new PathCommand( 'line', [ {} ] ),
new PathCommand( 'line', [ {} ] ),
];
};
Cone.prototype.updateSortValue = function() {
// center of cone is one third of its length
this.renderCentroid.set( this.renderOrigin )
.lerp( this.apex.renderOrigin, 1/3 );
this.sortValue = this.renderCentroid.z;
};
Cone.prototype.render = function( ctx, renderer ) {
this.renderConeSurface( ctx, renderer );
Ellipse.prototype.render.apply( this, arguments );
};
Cone.prototype.renderConeSurface = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
this.renderApex.set( this.apex.renderOrigin )
.subtract( this.renderOrigin );
var scale = this.renderNormal.magnitude();
var apexDistance = this.renderApex.magnitude2d();
var normalDistance = this.renderNormal.magnitude2d();
// eccentricity
var eccenAngle = Math.acos( normalDistance/scale );
var eccen = Math.sin( eccenAngle );
var radius = this.diameter / 2 * scale;
// does apex extend beyond eclipse of face
var isApexVisible = radius * eccen < apexDistance;
if ( !isApexVisible ) {
return;
}
// update tangents
var apexAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x ) +
TAU/2;
var projectLength = apexDistance/eccen;
var projectAngle = Math.acos( radius/projectLength );
// set tangent points
var tangentA = this.tangentA;
var tangentB = this.tangentB;
tangentA.x = Math.cos( projectAngle ) * radius * eccen;
tangentA.y = Math.sin( projectAngle ) * radius;
tangentB.set( this.tangentA );
tangentB.y *= -1;
tangentA.rotateZ( apexAngle );
tangentB.rotateZ( apexAngle );
tangentA.add( this.renderOrigin );
tangentB.add( this.renderOrigin );
this.setSurfaceRenderPoint( 0, tangentA );
this.setSurfaceRenderPoint( 1, this.apex.renderOrigin );
this.setSurfaceRenderPoint( 2, tangentB );
// render
var elem = this.getSurfaceRenderElement( ctx, renderer );
renderer.renderPath( ctx, elem, this.surfacePathCommands );
renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );
renderer.fill( ctx, elem, this.fill, this.color );
renderer.end( ctx, elem );
};
var svgURI = 'http://www.w3.org/2000/svg';
Cone.prototype.getSurfaceRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.surfaceSvgElement ) {
// create svgElement
this.surfaceSvgElement = document.createElementNS( svgURI, 'path' );
this.surfaceSvgElement.setAttribute( 'stroke-linecap', 'round' );
this.surfaceSvgElement.setAttribute( 'stroke-linejoin', 'round' );
}
return this.surfaceSvgElement;
};
Cone.prototype.setSurfaceRenderPoint = function( index, point ) {
var renderPoint = this.surfacePathCommands[ index ].renderPoints[0];
renderPoint.set( point );
};
return Cone;
} ) );
================================================
FILE: js/cylinder.js
================================================
/**
* Cylinder composite shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'),
require('./path-command'), require('./shape'), require('./group'),
require('./ellipse') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Cylinder = factory( Zdog, Zdog.PathCommand, Zdog.Shape,
Zdog.Group, Zdog.Ellipse );
}
}( this, function factory( utils, PathCommand, Shape, Group, Ellipse ) {
function noop() {}
// ----- CylinderGroup ----- //
var CylinderGroup = Group.subclass({
color: '#333',
updateSort: true,
});
CylinderGroup.prototype.create = function() {
Group.prototype.create.apply( this, arguments );
this.pathCommands = [
new PathCommand( 'move', [ {} ] ),
new PathCommand( 'line', [ {} ] ),
];
};
CylinderGroup.prototype.render = function( ctx, renderer ) {
this.renderCylinderSurface( ctx, renderer );
Group.prototype.render.apply( this, arguments );
};
CylinderGroup.prototype.renderCylinderSurface = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
// render cylinder surface
var elem = this.getRenderElement( ctx, renderer );
var frontBase = this.frontBase;
var rearBase = this.rearBase;
var scale = frontBase.renderNormal.magnitude();
var strokeWidth = frontBase.diameter * scale + frontBase.getLineWidth();
// set path command render points
this.pathCommands[0].renderPoints[0].set( frontBase.renderOrigin );
this.pathCommands[1].renderPoints[0].set( rearBase.renderOrigin );
if ( renderer.isCanvas ) {
ctx.lineCap = 'butt'; // nice
}
renderer.renderPath( ctx, elem, this.pathCommands );
renderer.stroke( ctx, elem, true, this.color, strokeWidth );
renderer.end( ctx, elem );
if ( renderer.isCanvas ) {
ctx.lineCap = 'round'; // reset
}
};
var svgURI = 'http://www.w3.org/2000/svg';
CylinderGroup.prototype.getRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.svgElement ) {
// create svgElement
this.svgElement = document.createElementNS( svgURI, 'path' );
}
return this.svgElement;
};
// prevent double-creation in parent.copyGraph()
// only create in Cylinder.create()
CylinderGroup.prototype.copyGraph = noop;
// ----- CylinderEllipse ----- //
var CylinderEllipse = Ellipse.subclass();
CylinderEllipse.prototype.copyGraph = noop;
// ----- Cylinder ----- //
var Cylinder = Shape.subclass({
diameter: 1,
length: 1,
frontFace: undefined,
fill: true,
});
var TAU = utils.TAU;
Cylinder.prototype.create = function( /* options */) {
// call super
Shape.prototype.create.apply( this, arguments );
// composite shape, create child shapes
// CylinderGroup to render cylinder surface then bases
this.group = new CylinderGroup({
addTo: this,
color: this.color,
visible: this.visible,
});
var baseZ = this.length / 2;
var baseColor = this.backface || true;
// front outside base
this.frontBase = this.group.frontBase = new Ellipse({
addTo: this.group,
diameter: this.diameter,
translate: { z: baseZ },
rotate: { y: TAU/2 },
color: this.color,
stroke: this.stroke,
fill: this.fill,
backface: this.frontFace || baseColor,
visible: this.visible,
});
// back outside base
this.rearBase = this.group.rearBase = this.frontBase.copy({
translate: { z: -baseZ },
rotate: { y: 0 },
backface: baseColor,
});
};
// Cylinder shape does not render anything
Cylinder.prototype.render = function() {};
// ----- set child properties ----- //
var childProperties = [ 'stroke', 'fill', 'color', 'visible' ];
childProperties.forEach( function( property ) {
// use proxy property for custom getter & setter
var _prop = '_' + property;
Object.defineProperty( Cylinder.prototype, property, {
get: function() {
return this[ _prop ];
},
set: function( value ) {
this[ _prop ] = value;
// set property on children
if ( this.frontBase ) {
this.frontBase[ property ] = value;
this.rearBase[ property ] = value;
this.group[ property ] = value;
}
},
} );
} );
// TODO child property setter for backface, frontBaseColor, & rearBaseColor
return Cylinder;
} ) );
================================================
FILE: js/dragger.js
================================================
/**
* Dragger
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog.Dragger = factory();
}
}( this, function factory() {
// quick & dirty drag event stuff
// messes up if multiple pointers/touches
// check for browser window #85
var hasWindow = typeof window != 'undefined';
// event support, default to mouse events
var downEvent = 'mousedown';
var moveEvent = 'mousemove';
var upEvent = 'mouseup';
if ( hasWindow ) {
if ( window.PointerEvent ) {
// PointerEvent, Chrome
downEvent = 'pointerdown';
moveEvent = 'pointermove';
upEvent = 'pointerup';
} else if ( 'ontouchstart' in window ) {
// Touch Events, iOS Safari
downEvent = 'touchstart';
moveEvent = 'touchmove';
upEvent = 'touchend';
}
}
function noop() {}
function Dragger( options ) {
this.create( options || {} );
}
Dragger.prototype.create = function( options ) {
this.onDragStart = options.onDragStart || noop;
this.onDragMove = options.onDragMove || noop;
this.onDragEnd = options.onDragEnd || noop;
this.bindDrag( options.startElement );
};
Dragger.prototype.bindDrag = function( element ) {
element = this.getQueryElement( element );
if ( !element ) {
return;
}
// disable browser gestures #53
element.style.touchAction = 'none';
element.addEventListener( downEvent, this );
};
Dragger.prototype.getQueryElement = function( element ) {
if ( typeof element == 'string' ) {
// with string, query selector
element = document.querySelector( element );
}
return element;
};
Dragger.prototype.handleEvent = function( event ) {
var method = this[ 'on' + event.type ];
if ( method ) {
method.call( this, event );
}
};
Dragger.prototype.onmousedown =
Dragger.prototype.onpointerdown = function( event ) {
this.dragStart( event, event );
};
Dragger.prototype.ontouchstart = function( event ) {
this.dragStart( event, event.changedTouches[0] );
};
Dragger.prototype.dragStart = function( event, pointer ) {
event.preventDefault();
this.dragStartX = pointer.pageX;
this.dragStartY = pointer.pageY;
if ( hasWindow ) {
window.addEventListener( moveEvent, this );
window.addEventListener( upEvent, this );
}
this.onDragStart( pointer );
};
Dragger.prototype.ontouchmove = function( event ) {
// HACK, moved touch may not be first
this.dragMove( event, event.changedTouches[0] );
};
Dragger.prototype.onmousemove =
Dragger.prototype.onpointermove = function( event ) {
this.dragMove( event, event );
};
Dragger.prototype.dragMove = function( event, pointer ) {
event.preventDefault();
var moveX = pointer.pageX - this.dragStartX;
var moveY = pointer.pageY - this.dragStartY;
this.onDragMove( pointer, moveX, moveY );
};
Dragger.prototype.onmouseup =
Dragger.prototype.onpointerup =
Dragger.prototype.ontouchend =
Dragger.prototype.dragEnd = function( /* event */) {
window.removeEventListener( moveEvent, this );
window.removeEventListener( upEvent, this );
this.onDragEnd();
};
return Dragger;
} ) );
================================================
FILE: js/ellipse.js
================================================
/**
* Ellipse
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Ellipse = factory( Zdog.Shape );
}
}( this, function factory( Shape ) {
var Ellipse = Shape.subclass({
diameter: 1,
width: undefined,
height: undefined,
quarters: 4,
closed: false,
});
Ellipse.prototype.setPath = function() {
var width = this.width != undefined ? this.width : this.diameter;
var height = this.height != undefined ? this.height : this.diameter;
var x = width/2;
var y = height/2;
this.path = [
{ x: 0, y: -y },
{ arc: [ // top right
{ x: x, y: -y },
{ x: x, y: 0 },
] },
];
// bottom right
if ( this.quarters > 1 ) {
this.path.push({ arc: [
{ x: x, y: y },
{ x: 0, y: y },
] });
}
// bottom left
if ( this.quarters > 2 ) {
this.path.push({ arc: [
{ x: -x, y: y },
{ x: -x, y: 0 },
] });
}
// top left
if ( this.quarters > 3 ) {
this.path.push({ arc: [
{ x: -x, y: -y },
{ x: 0, y: -y },
] });
}
};
return Ellipse;
} ) );
================================================
FILE: js/group.js
================================================
/**
* Group
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./anchor') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Group = factory( Zdog.Anchor );
}
}( this, function factory( Anchor ) {
var Group = Anchor.subclass({
updateSort: false,
visible: true,
});
// ----- update ----- //
Group.prototype.updateSortValue = function() {
var sortValueTotal = 0;
this.flatGraph.forEach( function( item ) {
item.updateSortValue();
sortValueTotal += item.sortValue;
} );
// average sort value of all points
// def not geometrically correct, but works for me
this.sortValue = sortValueTotal / this.flatGraph.length;
if ( this.updateSort ) {
this.flatGraph.sort( Anchor.shapeSorter );
}
};
// ----- render ----- //
Group.prototype.render = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
this.flatGraph.forEach( function( item ) {
item.render( ctx, renderer );
} );
};
// actual group flatGraph only used inside group
Group.prototype.updateFlatGraph = function() {
// do not include self
var flatGraph = [];
this.flatGraph = this.addChildFlatGraph( flatGraph );
};
// do not include children, group handles rendering & sorting internally
Group.prototype.getFlatGraph = function() {
return [ this ];
};
return Group;
} ) );
================================================
FILE: js/hemisphere.js
================================================
/**
* Hemisphere composite shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./anchor'), require('./ellipse') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Hemisphere = factory( Zdog, Zdog.Vector, Zdog.Anchor, Zdog.Ellipse );
}
}( this, function factory( utils, Vector, Anchor, Ellipse ) {
var Hemisphere = Ellipse.subclass({
fill: true,
});
var TAU = utils.TAU;
Hemisphere.prototype.create = function( /* options */) {
// call super
Ellipse.prototype.create.apply( this, arguments );
// composite shape, create child shapes
this.apex = new Anchor({
addTo: this,
translate: { z: this.diameter / 2 },
});
// vector used for calculation
this.renderCentroid = new Vector();
};
Hemisphere.prototype.updateSortValue = function() {
// centroid of hemisphere is 3/8 between origin and apex
this.renderCentroid.set( this.renderOrigin )
.lerp( this.apex.renderOrigin, 3/8 );
this.sortValue = this.renderCentroid.z;
};
Hemisphere.prototype.render = function( ctx, renderer ) {
this.renderDome( ctx, renderer );
// call super
Ellipse.prototype.render.apply( this, arguments );
};
Hemisphere.prototype.renderDome = function( ctx, renderer ) {
if ( !this.visible ) {
return;
}
var elem = this.getDomeRenderElement( ctx, renderer );
var contourAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x );
var domeRadius = this.diameter / 2 * this.renderNormal.magnitude();
var x = this.renderOrigin.x;
var y = this.renderOrigin.y;
if ( renderer.isCanvas ) {
// canvas
var startAngle = contourAngle + TAU/4;
var endAngle = contourAngle - TAU/4;
ctx.beginPath();
ctx.arc( x, y, domeRadius, startAngle, endAngle );
} else if ( renderer.isSvg ) {
// svg
contourAngle = ( contourAngle - TAU/4 ) / TAU * 360;
this.domeSvgElement.setAttribute( 'd', 'M ' + -domeRadius + ',0 A ' +
domeRadius + ',' + domeRadius + ' 0 0 1 ' + domeRadius + ',0' );
this.domeSvgElement.setAttribute( 'transform',
'translate(' + x + ',' + y + ' ) rotate(' + contourAngle + ')' );
}
renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );
renderer.fill( ctx, elem, this.fill, this.color );
renderer.end( ctx, elem );
};
var svgURI = 'http://www.w3.org/2000/svg';
Hemisphere.prototype.getDomeRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.domeSvgElement ) {
// create svgElement
this.domeSvgElement = document.createElementNS( svgURI, 'path' );
this.domeSvgElement.setAttribute( 'stroke-linecap', 'round' );
this.domeSvgElement.setAttribute( 'stroke-linejoin', 'round' );
}
return this.domeSvgElement;
};
return Hemisphere;
} ) );
================================================
FILE: js/illustration.js
================================================
/**
* Illustration
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./anchor'),
require('./dragger') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Illustration = factory( Zdog, Zdog.Anchor, Zdog.Dragger );
}
}( this, function factory( utils, Anchor, Dragger ) {
function noop() {}
var TAU = utils.TAU;
var Illustration = Anchor.subclass({
element: undefined,
centered: true,
zoom: 1,
dragRotate: false,
resize: false,
onPrerender: noop,
onDragStart: noop,
onDragMove: noop,
onDragEnd: noop,
onResize: noop,
});
utils.extend( Illustration.prototype, Dragger.prototype );
Illustration.prototype.create = function( options ) {
Anchor.prototype.create.call( this, options );
Dragger.prototype.create.call( this, options );
this.setElement( this.element );
this.setDragRotate( this.dragRotate );
this.setResize( this.resize );
};
Illustration.prototype.setElement = function( element ) {
element = this.getQueryElement( element );
if ( !element ) {
throw new Error( 'Zdog.Illustration element required. Set to ' + element );
}
var nodeName = element.nodeName.toLowerCase();
if ( nodeName == 'canvas' ) {
this.setCanvas( element );
} else if ( nodeName == 'svg' ) {
this.setSvg( element );
}
};
Illustration.prototype.setSize = function( width, height ) {
width = Math.round( width );
height = Math.round( height );
if ( this.isCanvas ) {
this.setSizeCanvas( width, height );
} else if ( this.isSvg ) {
this.setSizeSvg( width, height );
}
};
Illustration.prototype.setResize = function( resize ) {
this.resize = resize;
// create resize event listener
if ( !this.resizeListener ) {
this.resizeListener = this.onWindowResize.bind( this );
}
// add/remove event listener
if ( resize ) {
window.addEventListener( 'resize', this.resizeListener );
this.onWindowResize();
} else {
window.removeEventListener( 'resize', this.resizeListener );
}
};
// TODO debounce this?
Illustration.prototype.onWindowResize = function() {
this.setMeasuredSize();
this.onResize( this.width, this.height );
};
Illustration.prototype.setMeasuredSize = function() {
var width, height;
var isFullscreen = this.resize == 'fullscreen';
if ( isFullscreen ) {
width = window.innerWidth;
height = window.innerHeight;
} else {
var rect = this.element.getBoundingClientRect();
width = rect.width;
height = rect.height;
}
this.setSize( width, height );
};
// ----- render ----- //
Illustration.prototype.renderGraph = function( item ) {
if ( this.isCanvas ) {
this.renderGraphCanvas( item );
} else if ( this.isSvg ) {
this.renderGraphSvg( item );
}
};
// combo method
Illustration.prototype.updateRenderGraph = function( item ) {
this.updateGraph();
this.renderGraph( item );
};
// ----- canvas ----- //
Illustration.prototype.setCanvas = function( element ) {
this.element = element;
this.isCanvas = true;
// update related properties
this.ctx = this.element.getContext('2d');
// set initial size
this.setSizeCanvas( element.width, element.height );
};
Illustration.prototype.setSizeCanvas = function( width, height ) {
this.width = width;
this.height = height;
// up-rez for hi-DPI devices
var pixelRatio = this.pixelRatio = window.devicePixelRatio || 1;
this.element.width = this.canvasWidth = width * pixelRatio;
this.element.height = this.canvasHeight = height * pixelRatio;
var needsHighPixelRatioSizing = pixelRatio > 1 && !this.resize;
if ( needsHighPixelRatioSizing ) {
this.element.style.width = width + 'px';
this.element.style.height = height + 'px';
}
};
Illustration.prototype.renderGraphCanvas = function( item ) {
item = item || this;
this.prerenderCanvas();
Anchor.prototype.renderGraphCanvas.call( item, this.ctx );
this.postrenderCanvas();
};
Illustration.prototype.prerenderCanvas = function() {
var ctx = this.ctx;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.clearRect( 0, 0, this.canvasWidth, this.canvasHeight );
ctx.save();
if ( this.centered ) {
var centerX = this.width / 2 * this.pixelRatio;
var centerY = this.height / 2 * this.pixelRatio;
ctx.translate( centerX, centerY );
}
var scale = this.pixelRatio * this.zoom;
ctx.scale( scale, scale );
this.onPrerender( ctx );
};
Illustration.prototype.postrenderCanvas = function() {
this.ctx.restore();
};
// ----- svg ----- //
Illustration.prototype.setSvg = function( element ) {
this.element = element;
this.isSvg = true;
this.pixelRatio = 1;
// set initial size from width & height attributes
var width = element.getAttribute('width');
var height = element.getAttribute('height');
this.setSizeSvg( width, height );
};
Illustration.prototype.setSizeSvg = function( width, height ) {
this.width = width;
this.height = height;
var viewWidth = width / this.zoom;
var viewHeight = height / this.zoom;
var viewX = this.centered ? -viewWidth/2 : 0;
var viewY = this.centered ? -viewHeight/2 : 0;
this.element.setAttribute( 'viewBox', viewX + ' ' + viewY + ' ' +
viewWidth + ' ' + viewHeight );
if ( this.resize ) {
// remove size attributes, let size be determined by viewbox
this.element.removeAttribute('width');
this.element.removeAttribute('height');
} else {
this.element.setAttribute( 'width', width );
this.element.setAttribute( 'height', height );
}
};
Illustration.prototype.renderGraphSvg = function( item ) {
item = item || this;
empty( this.element );
this.onPrerender( this.element );
Anchor.prototype.renderGraphSvg.call( item, this.element );
};
function empty( element ) {
while ( element.firstChild ) {
element.removeChild( element.firstChild );
}
}
// ----- drag ----- //
Illustration.prototype.setDragRotate = function( item ) {
if ( !item ) {
return;
} else if ( item === true ) {
/* eslint consistent-this: "off" */
item = this;
}
this.dragRotate = item;
this.bindDrag( this.element );
};
Illustration.prototype.dragStart = function( /* event, pointer */) {
this.dragStartRX = this.dragRotate.rotate.x;
this.dragStartRY = this.dragRotate.rotate.y;
Dragger.prototype.dragStart.apply( this, arguments );
};
Illustration.prototype.dragMove = function( event, pointer ) {
var moveX = pointer.pageX - this.dragStartX;
var moveY = pointer.pageY - this.dragStartY;
var displaySize = Math.min( this.width, this.height );
var moveRY = moveX/displaySize * TAU;
var moveRX = moveY/displaySize * TAU;
this.dragRotate.rotate.x = this.dragStartRX - moveRX;
this.dragRotate.rotate.y = this.dragStartRY - moveRY;
Dragger.prototype.dragMove.apply( this, arguments );
};
return Illustration;
} ) );
================================================
FILE: js/index.js
================================================
/**
* Index
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory(
require('./boilerplate'),
require('./canvas-renderer'),
require('./svg-renderer'),
require('./vector'),
require('./anchor'),
require('./dragger'),
require('./illustration'),
require('./path-command'),
require('./shape'),
require('./group'),
require('./rect'),
require('./rounded-rect'),
require('./ellipse'),
require('./polygon'),
require('./hemisphere'),
require('./cylinder'),
require('./cone'),
require('./box')
);
} else if ( typeof define == 'function' && define.amd ) {
/* globals define */ // AMD
define( 'zdog', [], root.Zdog );
}
/* eslint-disable max-params */
} )( this, function factory( Zdog, CanvasRenderer, SvgRenderer, Vector, Anchor,
Dragger, Illustration, PathCommand, Shape, Group, Rect, RoundedRect,
Ellipse, Polygon, Hemisphere, Cylinder, Cone, Box ) {
/* eslint-enable max-params */
Zdog.CanvasRenderer = CanvasRenderer;
Zdog.SvgRenderer = SvgRenderer;
Zdog.Vector = Vector;
Zdog.Anchor = Anchor;
Zdog.Dragger = Dragger;
Zdog.Illustration = Illustration;
Zdog.PathCommand = PathCommand;
Zdog.Shape = Shape;
Zdog.Group = Group;
Zdog.Rect = Rect;
Zdog.RoundedRect = RoundedRect;
Zdog.Ellipse = Ellipse;
Zdog.Polygon = Polygon;
Zdog.Hemisphere = Hemisphere;
Zdog.Cylinder = Cylinder;
Zdog.Cone = Cone;
Zdog.Box = Box;
return Zdog;
} );
================================================
FILE: js/path-command.js
================================================
/**
* PathCommand
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./vector') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.PathCommand = factory( Zdog.Vector );
}
}( this, function factory( Vector ) {
function PathCommand( method, points, previousPoint ) {
this.method = method;
this.points = points.map( mapVectorPoint );
this.renderPoints = points.map( mapNewVector );
this.previousPoint = previousPoint;
this.endRenderPoint = this.renderPoints[ this.renderPoints.length - 1 ];
// arc actions come with previous point & corner point
// but require bezier control points
if ( method == 'arc' ) {
this.controlPoints = [ new Vector(), new Vector() ];
}
}
function mapVectorPoint( point ) {
if ( point instanceof Vector ) {
return point;
} else {
return new Vector( point );
}
}
function mapNewVector( point ) {
return new Vector( point );
}
PathCommand.prototype.reset = function() {
// reset renderPoints back to orignal points position
var points = this.points;
this.renderPoints.forEach( function( renderPoint, i ) {
var point = points[i];
renderPoint.set( point );
} );
};
PathCommand.prototype.transform = function( translation, rotation, scale ) {
this.renderPoints.forEach( function( renderPoint ) {
renderPoint.transform( translation, rotation, scale );
} );
};
PathCommand.prototype.render = function( ctx, elem, renderer ) {
return this[ this.method ]( ctx, elem, renderer );
};
PathCommand.prototype.move = function( ctx, elem, renderer ) {
return renderer.move( ctx, elem, this.renderPoints[0] );
};
PathCommand.prototype.line = function( ctx, elem, renderer ) {
return renderer.line( ctx, elem, this.renderPoints[0] );
};
PathCommand.prototype.bezier = function( ctx, elem, renderer ) {
var cp0 = this.renderPoints[0];
var cp1 = this.renderPoints[1];
var end = this.renderPoints[2];
return renderer.bezier( ctx, elem, cp0, cp1, end );
};
var arcHandleLength = 9/16;
PathCommand.prototype.arc = function( ctx, elem, renderer ) {
var prev = this.previousPoint;
var corner = this.renderPoints[0];
var end = this.renderPoints[1];
var cp0 = this.controlPoints[0];
var cp1 = this.controlPoints[1];
cp0.set( prev ).lerp( corner, arcHandleLength );
cp1.set( end ).lerp( corner, arcHandleLength );
return renderer.bezier( ctx, elem, cp0, cp1, end );
};
return PathCommand;
} ) );
================================================
FILE: js/polygon.js
================================================
/**
* Shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Polygon = factory( Zdog, Zdog.Shape );
}
}( this, function factory( utils, Shape ) {
var Polygon = Shape.subclass({
sides: 3,
radius: 0.5,
});
var TAU = utils.TAU;
Polygon.prototype.setPath = function() {
this.path = [];
for ( var i = 0; i < this.sides; i++ ) {
var theta = i / this.sides * TAU - TAU/4;
var x = Math.cos( theta ) * this.radius;
var y = Math.sin( theta ) * this.radius;
this.path.push({ x: x, y: y });
}
};
return Polygon;
} ) );
================================================
FILE: js/rect.js
================================================
/**
* Rect
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Rect = factory( Zdog.Shape );
}
}( this, function factory( Shape ) {
var Rect = Shape.subclass({
width: 1,
height: 1,
});
Rect.prototype.setPath = function() {
var x = this.width / 2;
var y = this.height / 2;
/* eslint key-spacing: "off" */
this.path = [
{ x: -x, y: -y },
{ x: x, y: -y },
{ x: x, y: y },
{ x: -x, y: y },
];
};
return Rect;
} ) );
================================================
FILE: js/rounded-rect.js
================================================
/**
* RoundedRect
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./shape') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.RoundedRect = factory( Zdog.Shape );
}
}( this, function factory( Shape ) {
var RoundedRect = Shape.subclass({
width: 1,
height: 1,
cornerRadius: 0.25,
closed: false,
});
RoundedRect.prototype.setPath = function() {
/* eslint
id-length: [ "error", { "min": 2, "exceptions": [ "x", "y" ] }],
key-spacing: "off" */
var xA = this.width / 2;
var yA = this.height / 2;
var shortSide = Math.min( xA, yA );
var cornerRadius = Math.min( this.cornerRadius, shortSide );
var xB = xA - cornerRadius;
var yB = yA - cornerRadius;
var path = [
// top right corner
{ x: xB, y: -yA },
{ arc: [
{ x: xA, y: -yA },
{ x: xA, y: -yB },
] },
];
// bottom right corner
if ( yB ) {
path.push({ x: xA, y: yB });
}
path.push({ arc: [
{ x: xA, y: yA },
{ x: xB, y: yA },
] });
// bottom left corner
if ( xB ) {
path.push({ x: -xB, y: yA });
}
path.push({ arc: [
{ x: -xA, y: yA },
{ x: -xA, y: yB },
] });
// top left corner
if ( yB ) {
path.push({ x: -xA, y: -yB });
}
path.push({ arc: [
{ x: -xA, y: -yA },
{ x: -xB, y: -yA },
] });
// back to top right corner
if ( xB ) {
path.push({ x: xB, y: -yA });
}
this.path = path;
};
return RoundedRect;
} ) );
================================================
FILE: js/shape.js
================================================
/**
* Shape
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate'), require('./vector'),
require('./path-command'), require('./anchor') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Shape = factory( Zdog, Zdog.Vector, Zdog.PathCommand, Zdog.Anchor );
}
}( this, function factory( utils, Vector, PathCommand, Anchor ) {
var Shape = Anchor.subclass({
stroke: 1,
fill: false,
color: '#333',
closed: true,
visible: true,
path: [ {} ],
front: { z: 1 },
backface: true,
});
Shape.prototype.create = function( options ) {
Anchor.prototype.create.call( this, options );
this.updatePath();
// front
this.front = new Vector( options.front || this.front );
this.renderFront = new Vector( this.front );
this.renderNormal = new Vector();
};
var actionNames = [
'move',
'line',
'bezier',
'arc',
];
Shape.prototype.updatePath = function() {
this.setPath();
this.updatePathCommands();
};
// place holder for Ellipse, Rect, etc.
Shape.prototype.setPath = function() {};
// parse path into PathCommands
Shape.prototype.updatePathCommands = function() {
var previousPoint;
this.pathCommands = this.path.map( function( pathPart, i ) {
// pathPart can be just vector coordinates -> { x, y, z }
// or path instruction -> { arc: [ {x0,y0,z0}, {x1,y1,z1} ] }
var keys = Object.keys( pathPart );
var method = keys[0];
var points = pathPart[ method ];
// default to line if no instruction
var isInstruction = keys.length == 1 && actionNames.indexOf( method ) != -1;
if ( !isInstruction ) {
method = 'line';
points = pathPart;
}
// munge single-point methods like line & move without arrays
var isLineOrMove = method == 'line' || method == 'move';
var isPointsArray = Array.isArray( points );
if ( isLineOrMove && !isPointsArray ) {
points = [ points ];
}
// first action is always move
method = i === 0 ? 'move' : method;
// arcs require previous last point
var command = new PathCommand( method, points, previousPoint );
// update previousLastPoint
previousPoint = command.endRenderPoint;
return command;
} );
};
// ----- update ----- //
Shape.prototype.reset = function() {
this.renderOrigin.set( this.origin );
this.renderFront.set( this.front );
// reset command render points
this.pathCommands.forEach( function( command ) {
command.reset();
} );
};
Shape.prototype.transform = function( translation, rotation, scale ) {
// calculate render points backface visibility & cone/hemisphere shapes
this.renderOrigin.transform( translation, rotation, scale );
this.renderFront.transform( translation, rotation, scale );
this.renderNormal.set( this.renderOrigin ).subtract( this.renderFront );
// transform points
this.pathCommands.forEach( function( command ) {
command.transform( translation, rotation, scale );
} );
// transform children
this.children.forEach( function( child ) {
child.transform( translation, rotation, scale );
} );
};
Shape.prototype.updateSortValue = function() {
// sort by average z of all points
// def not geometrically correct, but works for me
var pointCount = this.pathCommands.length;
var firstPoint = this.pathCommands[0].endRenderPoint;
var lastPoint = this.pathCommands[ pointCount - 1 ].endRenderPoint;
// ignore the final point if self closing shape
var isSelfClosing = pointCount > 2 && firstPoint.isSame( lastPoint );
if ( isSelfClosing ) {
pointCount -= 1;
}
var sortValueTotal = 0;
for ( var i = 0; i < pointCount; i++ ) {
sortValueTotal += this.pathCommands[i].endRenderPoint.z;
}
this.sortValue = sortValueTotal/pointCount;
};
// ----- render ----- //
Shape.prototype.render = function( ctx, renderer ) {
var length = this.pathCommands.length;
if ( !this.visible || !length ) {
return;
}
// do not render if hiding backface
this.isFacingBack = this.renderNormal.z > 0;
if ( !this.backface && this.isFacingBack ) {
return;
}
if ( !renderer ) {
throw new Error( 'Zdog renderer required. Set to ' + renderer );
}
// render dot or path
var isDot = length == 1;
if ( renderer.isCanvas && isDot ) {
this.renderCanvasDot( ctx, renderer );
} else {
this.renderPath( ctx, renderer );
}
};
var TAU = utils.TAU;
// Safari does not render lines with no size, have to render circle instead
Shape.prototype.renderCanvasDot = function( ctx ) {
var lineWidth = this.getLineWidth();
if ( !lineWidth ) {
return;
}
ctx.fillStyle = this.getRenderColor();
var point = this.pathCommands[0].endRenderPoint;
ctx.beginPath();
var radius = lineWidth/2;
ctx.arc( point.x, point.y, radius, 0, TAU );
ctx.fill();
};
Shape.prototype.getLineWidth = function() {
if ( !this.stroke ) {
return 0;
}
if ( this.stroke == true ) {
return 1;
}
return this.stroke;
};
Shape.prototype.getRenderColor = function() {
// use backface color if applicable
var isBackfaceColor = typeof this.backface == 'string' && this.isFacingBack;
var color = isBackfaceColor ? this.backface : this.color;
return color;
};
Shape.prototype.renderPath = function( ctx, renderer ) {
var elem = this.getRenderElement( ctx, renderer );
var isTwoPoints = this.pathCommands.length == 2 &&
this.pathCommands[1].method == 'line';
var isClosed = !isTwoPoints && this.closed;
var color = this.getRenderColor();
renderer.renderPath( ctx, elem, this.pathCommands, isClosed );
renderer.stroke( ctx, elem, this.stroke, color, this.getLineWidth() );
renderer.fill( ctx, elem, this.fill, color );
renderer.end( ctx, elem );
};
var svgURI = 'http://www.w3.org/2000/svg';
Shape.prototype.getRenderElement = function( ctx, renderer ) {
if ( !renderer.isSvg ) {
return;
}
if ( !this.svgElement ) {
// create svgElement
this.svgElement = document.createElementNS( svgURI, 'path' );
this.svgElement.setAttribute( 'stroke-linecap', 'round' );
this.svgElement.setAttribute( 'stroke-linejoin', 'round' );
}
return this.svgElement;
};
return Shape;
} ) );
================================================
FILE: js/svg-renderer.js
================================================
/**
* SvgRenderer
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory();
} else {
// browser global
root.Zdog.SvgRenderer = factory();
}
}( this, function factory() {
var SvgRenderer = { isSvg: true };
// round path coordinates to 3 decimals
var round = SvgRenderer.round = function( num ) {
return Math.round( num * 1000 ) / 1000;
};
function getPointString( point ) {
return round( point.x ) + ',' + round( point.y ) + ' ';
}
SvgRenderer.begin = function() {};
SvgRenderer.move = function( svg, elem, point ) {
return 'M' + getPointString( point );
};
SvgRenderer.line = function( svg, elem, point ) {
return 'L' + getPointString( point );
};
SvgRenderer.bezier = function( svg, elem, cp0, cp1, end ) {
return 'C' + getPointString( cp0 ) + getPointString( cp1 ) +
getPointString( end );
};
SvgRenderer.closePath = function( /* elem */) {
return 'Z';
};
SvgRenderer.setPath = function( svg, elem, pathValue ) {
elem.setAttribute( 'd', pathValue );
};
SvgRenderer.renderPath = function( svg, elem, pathCommands, isClosed ) {
var pathValue = '';
pathCommands.forEach( function( command ) {
pathValue += command.render( svg, elem, SvgRenderer );
} );
if ( isClosed ) {
pathValue += this.closePath( svg, elem );
}
this.setPath( svg, elem, pathValue );
};
SvgRenderer.stroke = function( svg, elem, isStroke, color, lineWidth ) {
if ( !isStroke ) {
return;
}
elem.setAttribute( 'stroke', color );
elem.setAttribute( 'stroke-width', lineWidth );
};
SvgRenderer.fill = function( svg, elem, isFill, color ) {
var fillColor = isFill ? color : 'none';
elem.setAttribute( 'fill', fillColor );
};
SvgRenderer.end = function( svg, elem ) {
svg.appendChild( elem );
};
return SvgRenderer;
} ) );
================================================
FILE: js/vector.js
================================================
/**
* Vector
*/
( function( root, factory ) {
// module definition
if ( typeof module == 'object' && module.exports ) {
// CommonJS
module.exports = factory( require('./boilerplate') );
} else {
// browser global
var Zdog = root.Zdog;
Zdog.Vector = factory( Zdog );
}
}( this, function factory( utils ) {
function Vector( position ) {
this.set( position );
}
var TAU = utils.TAU;
// 'pos' = 'position'
Vector.prototype.set = function( pos ) {
this.x = pos && pos.x || 0;
this.y = pos && pos.y || 0;
this.z = pos && pos.z || 0;
return this;
};
// set coordinates without sanitizing
// vec.write({ y: 2 }) only sets y coord
Vector.prototype.write = function( pos ) {
if ( !pos ) {
return this;
}
this.x = pos.x != undefined ? pos.x : this.x;
this.y = pos.y != undefined ? pos.y : this.y;
this.z = pos.z != undefined ? pos.z : this.z;
return this;
};
Vector.prototype.rotate = function( rotation ) {
if ( !rotation ) {
return;
}
this.rotateZ( rotation.z );
this.rotateY( rotation.y );
this.rotateX( rotation.x );
return this;
};
Vector.prototype.rotateZ = function( angle ) {
rotateProperty( this, angle, 'x', 'y' );
};
Vector.prototype.rotateX = function( angle ) {
rotateProperty( this, angle, 'y', 'z' );
};
Vector.prototype.rotateY = function( angle ) {
rotateProperty( this, angle, 'x', 'z' );
};
function rotateProperty( vec, angle, propA, propB ) {
if ( !angle || angle % TAU === 0 ) {
return;
}
var cos = Math.cos( angle );
var sin = Math.sin( angle );
var a = vec[ propA ];
var b = vec[ propB ];
vec[ propA ] = a * cos - b * sin;
vec[ propB ] = b * cos + a * sin;
}
Vector.prototype.isSame = function( pos ) {
if ( !pos ) {
return false;
}
return this.x === pos.x && this.y === pos.y && this.z === pos.z;
};
Vector.prototype.add = function( pos ) {
if ( !pos ) {
return this;
}
this.x += pos.x || 0;
this.y += pos.y || 0;
this.z += pos.z || 0;
return this;
};
Vector.prototype.subtract = function( pos ) {
if ( !pos ) {
return this;
}
this.x -= pos.x || 0;
this.y -= pos.y || 0;
this.z -= pos.z || 0;
return this;
};
Vector.prototype.multiply = function( pos ) {
if ( pos == undefined ) {
return this;
}
// multiple all values by same number
if ( typeof pos == 'number' ) {
this.x *= pos;
this.y *= pos;
this.z *= pos;
} else {
// multiply object
this.x *= pos.x != undefined ? pos.x : 1;
this.y *= pos.y != undefined ? pos.y : 1;
this.z *= pos.z != undefined ? pos.z : 1;
}
return this;
};
Vector.prototype.transform = function( translation, rotation, scale ) {
this.multiply( scale );
this.rotate( rotation );
this.add( translation );
return this;
};
Vector.prototype.lerp = function( pos, alpha ) {
this.x = utils.lerp( this.x, pos.x || 0, alpha );
this.y = utils.lerp( this.y, pos.y || 0, alpha );
this.z = utils.lerp( this.z, pos.z || 0, alpha );
return this;
};
Vector.prototype.magnitude = function() {
var sum = this.x * this.x + this.y * this.y + this.z * this.z;
return getMagnitudeSqrt( sum );
};
function getMagnitudeSqrt( sum ) {
// PERF: check if sum ~= 1 and skip sqrt
if ( Math.abs( sum - 1 ) < 0.00000001 ) {
return 1;
}
return Math.sqrt( sum );
}
Vector.prototype.magnitude2d = function() {
var sum = this.x * this.x + this.y * this.y;
return getMagnitudeSqrt( sum );
};
Vector.prototype.copy = function() {
return new Vector( this );
};
return Vector;
} ) );
================================================
FILE: package.json
================================================
{
"name": "zdog",
"version": "1.1.3",
"description": "Round, flat, designer-friendly pseudo-3D engine",
"main": "js/index.js",
"unpkg": "dist/zdog.dist.min.js",
"files": [
"dist/*.*",
"js/*.*",
"!js/.*",
"!dist/.*"
],
"devDependencies": {
"eslint": "^8.7.0",
"eslint-plugin-metafizzy": "^1.0.0",
"uglify-js": "^3.6.3"
},
"scripts": {
"bundle": "node tasks/bundle",
"dist": "npm run bundle && npm run uglify",
"lint": "npx eslint .",
"lintFix": "npx eslint . --fix",
"preversion": "npm run lint",
"version": "node tasks/version && npm run dist && git add -A dist js",
"test": "npm run lint",
"uglify": "npx uglifyjs dist/zdog.dist.js -o dist/zdog.dist.min.js --mangle --comments /^!/"
},
"repository": {
"type": "git",
"url": "git+https://github.com/metafizzy/zdog.git"
},
"keywords": [
"3D",
"canvas",
"svg"
],
"author": "David DeSandro",
"license": "MIT",
"bugs": {
"url": "https://github.com/metafizzy/zdog/issues"
},
"homepage": "https://zzz.dog"
}
================================================
FILE: tasks/.eslintrc.js
=======================================
gitextract_1ppqe373/
├── .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
├── 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.json
└── tasks/
├── .eslintrc.js
├── bundle.js
└── version.js
SYMBOL INDEX (54 symbols across 26 files)
FILE: demos/box-cross/box-cross.js
function addBox (line 34) | function addBox( options ) {
function animate (line 113) | function animate() {
function spin (line 119) | function spin() {
FILE: demos/dot-cube/dot-cube.js
function animate (line 72) | function animate() {
FILE: demos/fullscreen/fullscreen.js
function animate (line 63) | function animate() {
FILE: demos/hello-world-canvas/hello-world-canvas.js
function animate (line 38) | function animate() {
FILE: demos/hello-world-svg/hello-world-svg.js
function animate (line 38) | function animate() {
FILE: demos/hemisphere-cone-ball/hemisphere-cone-ball.js
function animate (line 83) | function animate() {
function spin (line 89) | function spin() {
FILE: demos/houses/houses.js
function animate (line 194) | function animate() {
FILE: demos/kid-kit/kid-kit.js
function animate (line 296) | function animate() {
FILE: demos/kirby-parasol/kirby-parasol.js
function animate (line 278) | function animate() {
FILE: demos/no-illo-canvas/no-illo-canvas.js
function animate (line 40) | function animate() {
function render (line 47) | function render() {
FILE: demos/no-illo-svg/no-illo-svg.js
function animate (line 44) | function animate() {
function render (line 51) | function render() {
function empty (line 58) | function empty( element ) {
FILE: demos/path-commands/path-commands.js
function animate (line 82) | function animate() {
FILE: demos/resize/resize.js
function animate (line 86) | function animate() {
FILE: demos/shade-and-shades/shade-and-shades.js
function animate (line 179) | function animate() {
FILE: demos/shapes/shapes.js
function animate (line 125) | function animate() {
FILE: demos/solids/solids.js
function animate (line 396) | function animate() {
function update (line 404) | function update() {
FILE: demos/strutter/strutter.js
function animate (line 209) | function animate() {
FILE: demos/zdog-logo/zdog-logo.js
function animate (line 243) | function animate() {
function spin (line 249) | function spin() {
FILE: dist/zdog.dist.js
function getPointString (line 169) | function getPointString( point ) {
function Vector (line 244) | function Vector( position ) {
function rotateProperty (line 292) | function rotateProperty( vec, angle, propA, propB ) {
function getMagnitudeSqrt (line 368) | function getMagnitudeSqrt( sum ) {
function Anchor (line 409) | function Anchor( options ) {
function getSubclass (line 604) | function getSubclass( Super ) {
function noop (line 674) | function noop() {}
function Dragger (line 676) | function Dragger( options ) {
function noop (line 779) | function noop() {}
function empty (line 970) | function empty( element ) {
function PathCommand (line 1026) | function PathCommand( method, points, previousPoint ) {
function mapVectorPoint (line 1039) | function mapVectorPoint( point ) {
function mapNewVector (line 1047) | function mapNewVector( point ) {
function noop (line 1709) | function noop() {}
FILE: js/anchor.js
function Anchor (line 22) | function Anchor( options ) {
function getSubclass (line 217) | function getSubclass( Super ) {
FILE: js/cylinder.js
function noop (line 20) | function noop() {}
FILE: js/dragger.js
function noop (line 39) | function noop() {}
function Dragger (line 41) | function Dragger( options ) {
FILE: js/illustration.js
function noop (line 18) | function noop() {}
function empty (line 209) | function empty( element ) {
FILE: js/path-command.js
function PathCommand (line 17) | function PathCommand( method, points, previousPoint ) {
function mapVectorPoint (line 30) | function mapVectorPoint( point ) {
function mapNewVector (line 38) | function mapNewVector( point ) {
FILE: js/svg-renderer.js
function getPointString (line 23) | function getPointString( point ) {
FILE: js/vector.js
function Vector (line 18) | function Vector( position ) {
function rotateProperty (line 66) | function rotateProperty( vec, angle, propA, propB ) {
function getMagnitudeSqrt (line 142) | function getMagnitudeSqrt( sum ) {
Condensed preview — 67 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (206K chars).
[
{
"path": ".eslintrc.js",
"chars": 300,
"preview": "/* eslint-env node */\n\nmodule.exports = {\n plugins: [ 'metafizzy' ],\n extends: 'plugin:metafizzy/browser',\n env: {\n "
},
{
"path": ".github/contributing.md",
"chars": 1802,
"preview": "## Feature requests\n\n**Add 👍 reaction** to issues for features you would like to see added to Zdog. Do not add +1 commen"
},
{
"path": ".github/issue_template.md",
"chars": 243,
"preview": "<!-- Thanks for submitting an issue! If you have a bug or problem issue, please include a **reduced test case**. Create "
},
{
"path": ".gitignore",
"chars": 42,
"preview": ".DS_Store\nnode_modules/\nbower_components/\n"
},
{
"path": "README.md",
"chars": 3552,
"preview": "# Zdog\n\n_Round, flat, designer-friendly pseudo-3D engine_\n\nView complete documentation and live demos at [zzz.dog](https"
},
{
"path": "bower.json",
"chars": 408,
"preview": "{\n \"name\": \"zdog\",\n \"description\": \"Round, flat, designer-friendly pseudo-3D engine\",\n \"main\": \"js/index.js\",\n \"auth"
},
{
"path": "demos/.eslintrc.js",
"chars": 181,
"preview": "/* eslint-env node */\n\nmodule.exports = {\n extends: '../.eslintrc.js',\n globals: {\n Zdog: 'readonly',\n },\n rules:"
},
{
"path": "demos/box-cross/box-cross.js",
"chars": 2250,
"preview": "// ----- setup ----- //\n\nvar sceneSize = 9;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n// colors\nvar yellow = '#ED0';\nva"
},
{
"path": "demos/box-cross/index.html",
"chars": 938,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/dot-cube/dot-cube.js",
"chars": 1880,
"preview": "// ----- setup ----- //\n\nvar sceneSize = 24;\nvar TAU = Zdog.TAU;\n\nvar illo = new Zdog.Illustration({\n element: '.illo',"
},
{
"path": "demos/dot-cube/index.html",
"chars": 917,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/fullscreen/fullscreen.js",
"chars": 1104,
"preview": "// ----- setup ----- //\n\nvar isSpinning = true;\nvar gold = '#EA0';\nvar orange = '#E62';\nvar garnet = '#C25';\nvar eggplan"
},
{
"path": "demos/fullscreen/index.html",
"chars": 1000,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/hello-world-canvas/hello-world-canvas.js",
"chars": 655,
"preview": "// ----- variables ----- //\n\nvar isSpinning = true;\n\n// ----- model ----- //\n\nvar illo = new Zdog.Illustration({\n eleme"
},
{
"path": "demos/hello-world-canvas/index.html",
"chars": 1169,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/hello-world-svg/hello-world-svg.js",
"chars": 655,
"preview": "// ----- variables ----- //\n\nvar isSpinning = true;\n\n// ----- model ----- //\n\nvar illo = new Zdog.Illustration({\n eleme"
},
{
"path": "demos/hello-world-svg/index.html",
"chars": 1151,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/hemisphere-cone-ball/hemisphere-cone-ball.js",
"chars": 2103,
"preview": "// ----- setup ----- //\n\nvar sceneSize = 48;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n\nvar illo = new Zdog.Illustratio"
},
{
"path": "demos/hemisphere-cone-ball/index.html",
"chars": 1052,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/houses/houses.js",
"chars": 3746,
"preview": "// ----- setup ----- //\n\nvar sceneSize = 56;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n\nvar offWhite = '#FED';\nvar yell"
},
{
"path": "demos/houses/index.html",
"chars": 1220,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/kid-kit/index.html",
"chars": 1051,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/kid-kit/kid-kit.js",
"chars": 4923,
"preview": "// ----- setup ----- //\n\nvar offWhite = '#FED';\nvar gold = '#EA0';\nvar garnet = '#C25';\nvar eggplant = '#636';\n\nvar illo"
},
{
"path": "demos/kirby-parasol/index.html",
"chars": 970,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/kirby-parasol/kirby-parasol.js",
"chars": 5148,
"preview": "// ----- setup ----- //\n\nvar sceneSize = 80;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n// colors\nvar pink = '#F8B';\nvar"
},
{
"path": "demos/no-illo-canvas/index.html",
"chars": 1123,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/no-illo-canvas/no-illo-canvas.js",
"chars": 1683,
"preview": "// ----- setup ----- //\n\n// get canvas element and its context\nvar canvas = document.querySelector('.zdog-canvas');\nvar "
},
{
"path": "demos/no-illo-svg/index.html",
"chars": 1099,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/no-illo-svg/no-illo-svg.js",
"chars": 1629,
"preview": "// ----- setup ----- //\n\n// svg element\nvar svg = document.querySelector('svg');\n// set size\nvar zoom = 5;\nvar svgWidth "
},
{
"path": "demos/path-commands/index.html",
"chars": 1069,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/path-commands/path-commands.js",
"chars": 1495,
"preview": "// ----- variables ----- //\n\nvar eggplant = '#636';\n\n// ----- model ----- //\n\nvar illo = new Zdog.Illustration({\n eleme"
},
{
"path": "demos/resize/index.html",
"chars": 1291,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/resize/resize.js",
"chars": 1607,
"preview": "// ----- setup ----- //\n\nvar zoom = 4;\nvar isSpinning = true;\nvar gold = '#EA0';\nvar orange = '#E62';\nvar garnet = '#C25"
},
{
"path": "demos/shade-and-shades/index.html",
"chars": 916,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/shade-and-shades/shade-and-shades.js",
"chars": 3367,
"preview": "// ----- setup ----- //\n\nvar sceneSize = 96;\nvar orange = '#E62';\nvar eggplant = '#636';\n\n// shape defaults\nZdog.Shape.d"
},
{
"path": "demos/shapes/index.html",
"chars": 1292,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/shapes/shapes.js",
"chars": 2301,
"preview": "// ----- setup ----- //\n\nvar sceneSize = 24;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\nvar offWhite = '#FED';\nvar gold "
},
{
"path": "demos/solids/index.html",
"chars": 1243,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/solids/solids.js",
"chars": 8997,
"preview": "// ----- setup ----- //\n\nvar illoElem = document.querySelector('.illo');\nvar sceneSize = 96;\nvar TAU = Zdog.TAU;\nvar ROO"
},
{
"path": "demos/strutter/index.html",
"chars": 1078,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/strutter/strutter.js",
"chars": 3911,
"preview": "// ----- setup ----- //\n\nvar sceneSize = 48;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n// colors\nvar gold = '#EA0';\nvar"
},
{
"path": "demos/zdog-logo/index.html",
"chars": 983,
"preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width\" "
},
{
"path": "demos/zdog-logo/zdog-logo.js",
"chars": 4865,
"preview": "// ----- setup ----- //\n\nvar sceneSize = 100;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\nvar initRotate = { x: 20/360 * "
},
{
"path": "dist/zdog.dist.js",
"chars": 56041,
"preview": "/*!\n * Zdog v1.1.3\n * Round, flat, designer-friendly pseudo-3D engine\n * Licensed MIT\n * https://zzz.dog\n * Copyright 20"
},
{
"path": "js/anchor.js",
"chars": 6168,
"preview": "/**\n * Anchor\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module.expor"
},
{
"path": "js/boilerplate.js",
"chars": 1423,
"preview": "/*!\n * Zdog v1.1.3\n * Round, flat, designer-friendly pseudo-3D engine\n * Licensed MIT\n * https://zzz.dog\n * Copyright 20"
},
{
"path": "js/box.js",
"chars": 4131,
"preview": "/**\n * Box composite shape\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' &&"
},
{
"path": "js/canvas-renderer.js",
"chars": 1484,
"preview": "/**\n * CanvasRenderer\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && modu"
},
{
"path": "js/cone.js",
"chars": 3970,
"preview": "/**\n * Cone composite shape\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' &"
},
{
"path": "js/cylinder.js",
"chars": 4352,
"preview": "/**\n * Cylinder composite shape\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'objec"
},
{
"path": "js/dragger.js",
"chars": 3158,
"preview": "/**\n * Dragger\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module.expo"
},
{
"path": "js/ellipse.js",
"chars": 1232,
"preview": "/**\n * Ellipse\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module.expo"
},
{
"path": "js/group.js",
"chars": 1437,
"preview": "/**\n * Group\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module.export"
},
{
"path": "js/hemisphere.js",
"chars": 2923,
"preview": "/**\n * Hemisphere composite shape\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'obj"
},
{
"path": "js/illustration.js",
"chars": 6909,
"preview": "/**\n * Illustration\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module"
},
{
"path": "js/index.js",
"chars": 1705,
"preview": "/**\n * Index\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module.export"
},
{
"path": "js/path-command.js",
"chars": 2536,
"preview": "/**\n * PathCommand\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module."
},
{
"path": "js/polygon.js",
"chars": 767,
"preview": "/**\n * Shape\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module.export"
},
{
"path": "js/rect.js",
"chars": 650,
"preview": "/**\n * Rect\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module.exports"
},
{
"path": "js/rounded-rect.js",
"chars": 1551,
"preview": "/**\n * RoundedRect\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module."
},
{
"path": "js/shape.js",
"chars": 6242,
"preview": "/**\n * Shape\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module.export"
},
{
"path": "js/svg-renderer.js",
"chars": 1872,
"preview": "/**\n * SvgRenderer\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module."
},
{
"path": "js/vector.js",
"chars": 3531,
"preview": "/**\n * Vector\n */\n\n( function( root, factory ) {\n // module definition\n if ( typeof module == 'object' && module.expor"
},
{
"path": "package.json",
"chars": 1076,
"preview": "{\n \"name\": \"zdog\",\n \"version\": \"1.1.3\",\n \"description\": \"Round, flat, designer-friendly pseudo-3D engine\",\n \"main\": "
},
{
"path": "tasks/.eslintrc.js",
"chars": 179,
"preview": "/* eslint-env node */\n\nmodule.exports = {\n plugins: [ 'metafizzy' ],\n extends: 'plugin:metafizzy/node',\n env: {\n b"
},
{
"path": "tasks/bundle.js",
"chars": 631,
"preview": "const fs = require('fs');\nconst execSync = require('child_process').execSync;\n\n// get file paths from index.js\nconst ind"
},
{
"path": "tasks/version.js",
"chars": 412,
"preview": "const fs = require('fs');\nconst version = require('../package.json').version;\n\nconst boilerplatePath = 'js/boilerplate.j"
}
]
About this extraction
This page contains the full source code of the metafizzy/zdog GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 67 files (188.3 KB), approximately 57.2k tokens, and a symbol index with 54 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.