1// Returns an [x, y] point on a circle, with given origin and radius, at a given angle
2// counter-clockwise from the positive horizontal axis.
3function circleCoordinates(origin, radius, radians) {
4    return [
5        origin[0] + Math.cos(radians) * radius,
6        origin[1] + Math.sin(radians) * radius
7    ];
8}
9
10// Animator handles calling and stopping requestAnimationFrame and keeping track of framerate.
11class Animator {
12    framesCount = 0;
13    totalFramesMs = 0;
14    animating = false;
15    renderer = null;
16
17    start() {
18        if (this.animating === false) {
19            this.animating = true;
20            this.framesCount = 0;
21            const frameStartMs = performance.now();
22
23            const drawFrame = () => {
24                if (this.animating && this.renderer) {
25                    requestAnimationFrame(drawFrame);
26                    this.framesCount++;
27
28                    const [x, y] = circleCoordinates([-70, -70], 50, this.framesCount/100);
29                    this.renderer.render(x, y);
30
31                    const frameTimeMs = performance.now() - frameStartMs;
32                    this.totalFramesMs = frameTimeMs;
33                }
34            };
35            requestAnimationFrame(drawFrame);
36        }
37    }
38
39    stop() {
40        this.animating = false;
41    }
42}
43
44// The following three renderers draw a repeating pattern of paths.
45// The approximate height and width of this repeated pattern is given by PATTERN_BOUNDS:
46const PATTERN_BOUNDS = 600;
47// And the spacing of the pattern (distance between repeated paths) is given by PATTERN_SPACING:
48const PATTERN_SPACING = 70;
49
50class SVGRenderer {
51    constructor(svgObjectElement) {
52        this.svgObjectElement = svgObjectElement;
53        this.svgElArray = [];
54        // Create an SVG element for every position in the pattern
55        for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
56            for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
57                const clonedSVG = svgObjectElement.cloneNode(true);
58                this.svgElArray.push(clonedSVG);
59                svgObjectElement.parentElement.appendChild(clonedSVG);
60            }
61        }
62    }
63
64    render(x, y) {
65        let i = 0;
66        for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
67            for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
68                this.svgElArray[i].style.transform = `translate(${x + xo}px, ${y + yo}px)`;
69                i++;
70            }
71        }
72    }
73}
74
75class Path2dRenderer {
76    constructor(svgData, offscreenCanvas) {
77        this.data = svgData.map(([pathString, fillColor]) => [new Path2D(pathString), fillColor]);
78
79        this.ctx = offscreenCanvas.getContext('2d');
80    }
81
82    render(x, y) {
83        const ctx = this.ctx;
84
85        ctx.clearRect(0, 0, 500, 500);
86
87        for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
88            for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
89                ctx.save();
90                ctx.translate(x + xo, y + yo);
91
92                for (const [path, fillColor] of this.data) {
93                    ctx.fillStyle = fillColor;
94                    ctx.fill(path);
95                }
96                ctx.restore();
97            }
98        }
99    }
100}
101
102class CanvasKitRenderer {
103    constructor(svgData, offscreenCanvas, CanvasKit) {
104        this.CanvasKit = CanvasKit;
105        this.data = svgData.map(([pathString, fillColor]) => [
106            CanvasKit.Path.MakeFromSVGString(pathString),
107            CanvasKit.parseColorString(fillColor)
108        ]);
109
110        this.surface = CanvasKit.MakeWebGLCanvasSurface(offscreenCanvas, null);
111        if (!this.surface) {
112            throw 'Could not make canvas surface';
113        }
114        this.canvas = this.surface.getCanvas();
115
116        this.paint = new CanvasKit.Paint();
117        this.paint.setAntiAlias(true);
118        this.paint.setStyle(CanvasKit.PaintStyle.Fill);
119    }
120
121    render(x, y) {
122        const canvas = this.canvas;
123
124        canvas.clear(this.CanvasKit.WHITE);
125
126        for (let xo = 0; xo < PATTERN_BOUNDS; xo += PATTERN_SPACING) {
127            for (let yo = 0; yo < PATTERN_BOUNDS; yo += PATTERN_SPACING) {
128                canvas.save();
129                canvas.translate(x + xo, y + yo);
130
131                for (const [path, color] of this.data) {
132                    this.paint.setColor(color);
133                    canvas.drawPath(path, this.paint);
134                }
135                canvas.restore();
136            }
137        }
138        this.surface.flush();
139    }
140}
141