function render(options = {}) {
const {
size = 600,
stroke = 10,
strokeColor = 'hsl(30,40%,30%)',
fillColor = 'hsl(30,40%,98%)',
duration = 3000,
ease = easeInOut(3),
} = options;
const boxSize = 50 - stroke / 2 / (2**.5) - .5, boxOffset = 100;
const PI = Math.PI, PIQ = PI / 2;
const ctx = DOM.context2d(size, size, 1);
const illo = new Zdog.Illustration({
element: ctx.canvas,
zoom: 2,
rotate: { x: -PI/5.1 , y:-PI/4 },
});
illo.pixelRatio = 1;
ctx.canvas.width = illo.width = size;
ctx.canvas.height = illo.height = size;
ctx.canvas.style.maxWidth = '100%';
ctx.canvas.style.height = 'auto';
function createCube([x, y, z]) {
const origin = new Zdog.Anchor({
translate: {x: x*boxOffset, y: y*boxOffset, z: z*boxOffset}});
new Zdog.Box({
addTo: new Zdog.Group({addTo: origin, updateSort: true}),
width: boxSize, height: boxSize, depth: boxSize,
stroke, fill: false, color: strokeColor,
});
new Zdog.Box({
addTo: new Zdog.Group({addTo: origin, updateSort: true}),
width: boxSize, height: boxSize, depth: boxSize,
color: fillColor,
});
return origin;
}
// Turns a list of translation offsets into a set of cubes.
function createCubeSet(offsets) {
const root = new Zdog.Anchor();
const boxes = offsets.map(createCube);
for(let b of boxes) root.addChild(b);
return {root, boxes};
}
// Our two box sets, each having a "root" element, a list of "boxes", and a rotation vector.
const sets = [
{
rotate: [0, -1, 0],
// 7 Boxes, 2 on opposite ends of each axis, one at center.
...createCubeSet([-1, 1].map(s => [0, 1, 2].map(o => [0, 1, 2].map(i => i === o ? s : 0))).flat().concat([[0,0,0]])),
},
{
rotate: [-1, 0, 0],
// 8 Boxes, each on a corner.
...createCubeSet([-.5, .5].map(x => [-.5, .5].map(y => [-.5, .5].map(z => [x, y, z]))).flat(2)),
},
];
// Container to which we'll attach "invisible" sets during animation.
const inactive = new Zdog.Anchor();
// Zdog clears the canvas on every draw. Instead of setting a rect in the background,
// we simply override our context's clearRect to instead perform a fill.
ctx.clearRect = function(x, y, w, h) {
this.fillStyle = fillColor;
this.fillRect(x, y, w, h);
};
// Main render callback.
return t => {
// Remove all sets from the scene.
for(let s of sets) inactive.addChild(s.root);
// Fetch the current cube set.
const {root, rotate} = sets[t * sets.length | 0];
// Reattach the current set to the scene.
illo.addChild(root);
// Rotate the cube set anchor.
const [x, y, z] = rotate, a = ease((t * sets.length) % 1) * PIQ;
root.rotate = { x: x * a, y: y * a, z: z * a };
illo.updateRenderGraph();
return illo.element;
}
}