blob: 134ad0453b669de8e982535b58205523683c8471 [file] [log] [blame]
// Returns an [x, y] point on a circle, with given origin and radius, at a given angle
// counter-clockwise from the positive horizontal axis.
function circleCoordinates(origin, radius, radians) {
return [
origin[0] + Math.cos(radians) * radius,
origin[1] + Math.sin(radians) * radius
];
}
// Animator handles calling and stopping requestAnimationFrame and keeping track of framerate.
class Animator {
framesCount = 0;
totalFramesMs = 0;
animating = false;
renderer = null;
start() {
if (this.animating === false) {
this.animating = true;
this.framesCount = 0;
const frameStartMs = performance.now();
const drawFrame = () => {
if (this.animating && this.renderer) {
requestAnimationFrame(drawFrame);
this.framesCount++;
const [x, y] = circleCoordinates([-70, -70], 50, this.framesCount/100);
this.renderer.render(x, y);
const frameTimeMs = performance.now() - frameStartMs;
this.totalFramesMs = frameTimeMs;
}
};
requestAnimationFrame(drawFrame);
}
}
stop() {
this.animating = false;
}
}
// The following three renderers draw a repeating pattern of paths.
// The approximate height and width of this repeated pattern is given by PATTERN_BOUNDS:
const PATTERN_BOUNDS = 600;
// And the spacing of the pattern (distance between repeated paths) is given by PATTERN_SPACING:
const PATTERN_SPACING = 70;
class SVGRenderer {
constructor(svgObjectElement) {
this.svgObjectElement = svgObjectElement;
this.svgElArray = [];
// Create an SVG element for every position in the pattern
for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
const clonedSVG = svgObjectElement.cloneNode(true);
this.svgElArray.push(clonedSVG);
svgObjectElement.parentElement.appendChild(clonedSVG);
}
}
}
render(x, y) {
let i = 0;
for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
this.svgElArray[i].style.transform = `translate(${x + xo}px, ${y + yo}px)`;
i++;
}
}
}
}
class Path2dRenderer {
constructor(svgData, offscreenCanvas) {
this.data = svgData.map(([pathString, fillColor]) => [new Path2D(pathString), fillColor]);
this.ctx = offscreenCanvas.getContext('2d');
}
render(x, y) {
const ctx = this.ctx;
ctx.clearRect(0, 0, 500, 500);
for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
ctx.save();
ctx.translate(x + xo, y + yo);
for (const [path, fillColor] of this.data) {
ctx.fillStyle = fillColor;
ctx.fill(path);
}
ctx.restore();
}
}
}
}
class CanvasKitRenderer {
constructor(svgData, offscreenCanvas, CanvasKit) {
this.CanvasKit = CanvasKit;
this.data = svgData.map(([pathString, fillColor]) => [
CanvasKit.Path.MakeFromSVGString(pathString),
CanvasKit.parseColorString(fillColor)
]);
this.surface = CanvasKit.MakeWebGLCanvasSurface(offscreenCanvas, null);
if (!this.surface) {
throw 'Could not make canvas surface';
}
this.canvas = this.surface.getCanvas();
this.paint = new CanvasKit.Paint();
this.paint.setAntiAlias(true);
this.paint.setStyle(CanvasKit.PaintStyle.Fill);
}
render(x, y) {
const canvas = this.canvas;
canvas.clear(this.CanvasKit.WHITE);
for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
canvas.save();
canvas.translate(x + xo, y + yo);
for (const [path, color] of this.data) {
this.paint.setColor(color);
canvas.drawPath(path, this.paint);
}
canvas.restore();
}
}
this.surface.flush();
}
}