Extras

Z-fighting

Z-fighting is when two shapes pop-over one another as they appear to fight for position. In polygonal 3D engines, z-fighting happens when two polygons are coplanar. In Zdog, z-fighting happens when any shapes occupy the same space.

Z-fighting in Zdog is the natural result of its pseudo-3D engine. Zdog’s 3D shapes are actually 2D projections, rendered without any accounting for collisions with other shapes.

Rather than fighting against this effect, the best course is to accept it. Z-fighting is one of Zdog’s charms. Embrace it.

That said, there are some techniques to address z-fighting.

A simple solution is to use the same color for multiple shapes.

For shapes of similar size, move the shapes away from one another so their stroke volume do not overlap.

Use a Group for groups of shapes that collectively cover the same area as a larger shape. In this demo, the gold dots are in a Group, whereas the purple dots are individual shapes.

Use a Shape with visible: false in a Group to balance out its z-index. Shape z-index is calculated as the average of all the shape’s path points. Group z-index is calcuated as the average of all the grouped shapes' z-indexes. So you can affect the z-index of a Group by adding or moving invisible shapes.

In the demo below, the gold dot has a counter-balanced invisible shape in its group.

let group = new Zdog.Group({...});
// dot
new Zdog.Shape({
  addTo: group,
  stroke: 20,
  translate: { x: 45, y: -45 },
  color: '#EA0',
});
// invisible Shape to counter-balance group z-index
new Zdog.Shape({
  addTo: group,
  visible: false,
  translate: { x: -45, y: 45 },
});

Known bugs

Hemisphere, Cylinder, & Cone scale bug

Due to Zdog’s underlying math, Hemisphere, Cylinder, and Cone shapes cannot be scaled with a 1- or 2-dimensional Vector.

let anchor = new Zdog.Anchor({
  // 1-dimensional scale
  // will break Hemisphere, Cone, & Cylinder
  scale: { y: 2 },
});

new Zdog.Hemisphere({ addTo: anchor, /*...*/ });
new Zdog.Cylinder({ addTo: anchor, /*...*/ });
new Zdog.Cone({ addTo: anchor, /*...*/ });

However, uniform Vector options will still work.

let anchor = new Zdog.Anchor({
  // uniform scale, will be okay
  scale: 2,
});

new Zdog.Hemisphere({ addTo: anchor, /*...*/ });
new Zdog.Cylinder({ addTo: anchor, /*...*/ });
new Zdog.Cone({ addTo: anchor, /*...*/ });

One pixel gaps

Gaps can appear in between flat polygons of solids.

new Zdog.Box({
  addTo: illo,
  width: 120,
  height: 100,
  depth: 80,
  rotate: { x: -Zdog.TAU/8, y: Zdog.TAU/8 },
  stroke: false,
  color: '#EA0',
  rearFace: '#636',
  leftFace: '#636',
  bottomFace: '#636',
});

You can cover over this gaps by adding a 1-pixel-wide stroke to the polygons.

// with an Illustration without zoom set

new Zdog.Box({
  // 1px stroke to cover edge-to-edge gaps
  stroke: 1,
  // ...
});
// with an Illustration with zoom set
let illo = new Illustration({
  zoom: 4,
  // ...
});

new Zdog.Box({
  addTo: illo,
  stroke: 1 / illo.zoom, // 1px stroke
  // ...
});

Rendering without Illustration

You can render Zdog models without using Illustration, in the case you are directly using a <canvas> or <svg> and prefer to handle managing the element yourself.

The demos below work by using a generic Anchor as the scene object. The scene’s graph is then updated with updateGraph and rotated by dragging with a Dragger.

Rendering with canvas without Illustration

Render the scene’s graph on a <canvas> with renderGraphCanvas. Additional code is required to prepare the canvas for rendering. View this demo on CodePen.

// ----- setup ----- //

// get canvas element and its context
let canvas = document.querySelector('.zdog-canvas');
let ctx = canvas.getContext('2d');
// get canvas size
let canvasWidth = canvas.width;
let canvasHeight = canvas.height;
// illustration variables
const TAU = Zdog.TAU;
const zoom = 4;
let isSpinning = true;

// create an scene Anchor to hold all items
let scene = new Zdog.Anchor();

// ----- model ----- //

// add shapes to scene
new Zdog.Shape({
  addTo: scene,
  //...
});

new Zdog.Ellipse({
  addTo: scene,
  //...
});

// ----- animate ----- //

function animate() {
  // make changes to model, like rotating scene
  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 ----- //

let dragStartRX, dragStartRY;
let 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 );
  },
});

Edit this demo on CodePen

Rendering with SVG without Illustration

Render the scene’s graph on a <svg> with renderGraphSvg. Additional code is required to prepare the SVG for rendering. View this demo on CodePen.

// ----- setup ----- //

// get svg element
let svg = document.querySelector('svg');
// rendering sizes
const zoom = 4;
let sceneWidth = 16;
let sceneHeight = 16;
let viewWidth = sceneWidth * zoom;
let viewHeight = sceneHeight * zoom;
let svgWidth = svg.getAttribute('width');
let svgHeight = svg.getAttribute('height');
// set viewBox using zoom
svg.setAttribute( 'viewBox', `${-viewWidth/2}  ${-viewHeight/2} ` +
  `${viewWidth} ${viewHeight}` );
// rendering variable
const TAU = Zdog.TAU;
let isSpinning = true;

// create an scene Anchor to hold all items
let scene = new Zdog.Anchor();

// ----- model ----- //

// add shapes to scene
new Zdog.Shape({
  addTo: scene,
  //...
});

new Zdog.Ellipse({
  addTo: scene,
  //...
});

// ----- 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 ----- //

// click drag to rotate
let dragStartRX, dragStartRY;
let 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 );
  },
});

Edit this demo on CodePen

Canvas or SVG

Should you use <canvas> or <svg>? Visually, canvas and SVG output the same exact image with Zdog. In practice, they each have their benefits.

Canvas is better for lots of shapes and smaller image sizes. Canvas renders on a flat image, so it doesn't have to manage a DOM. But its rendered with pixels, so it can be slower with larger sizes — particularly with high-resolution screens.

SVG is better for fewer shapes and larger image sizes. SVG is a vector format, so its resolution independent, perfect for large image sizes. However, all shapes have to be rendered and sorted with individual DOM elements, which can negatively impact performance and get your fan spinning.

Feature requests

Help me select new features by adding your 👍 reaction to GitHub issues for features you would like to see added. Hotly requested features include:

More Zdog resources

Other people’s stuff:

My stuff: