git @ Cat's Eye Technologies Spinner-Sanctum / master src / spinner-sanctum.js
master

Tree @master (Download .tar.gz)

spinner-sanctum.js @masterraw · history · blame

// SPDX-FileCopyrightText: Chris Pressey, the creator of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense


SpinnerSanctum = function() {
    this.init = function(cfg) {
        this.canvas = cfg.canvas;
        this.ctx = this.canvas.getContext('2d');
        this.size = cfg.size;

        this.numPoints = 50;
        this.gridSize = 8;

        this.backingCanvas1 = document.createElement('canvas');
        this.backingCanvas1.width = this.size;
        this.backingCanvas1.height = this.size;
        this.backingCtx1 = this.backingCanvas1.getContext('2d');

        this.backingCanvas2 = document.createElement('canvas');
        this.backingCanvas2.width = this.size;
        this.backingCanvas2.height = this.size;
        this.backingCtx2 = this.backingCanvas2.getContext('2d');

        this.reset();
    };

    this.drawOne = function(ctx) {
        const size = this.size;
        ctx.fillRect(0, 0, size, size);

        const points = [];
        for (let i = 0; i < this.numPoints; i++) {
            let point;

            const phase = i % 4;
            switch(phase) {
                case 0:
                    point = {
                        x: Math.random() * (size / 3),
                        y: size / 3 + Math.random() * (size / 3)
                    };
                    break;
                case 1:
                    point = {
                        x: size / 3 + Math.random() * (size / 3),
                        y: size * (2/3) + Math.random() * (size / 3)
                    };
                    break;
                case 2:
                    point = {
                        x: size * (2/3) + Math.random() * (size / 3),
                        y: size / 3 + Math.random() * (size / 3)
                    };
                    break;
                case 3:
                    point = {
                        x: size / 3 + Math.random() * (size / 3),
                        y: Math.random() * (size / 3)
                    };
                    break;
            }
            points.push(point);
        }

        ctx.lineWidth = 4;
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';

        ctx.beginPath();
        ctx.moveTo(points[0].x, points[0].y);

        // Draw Bezier curves between consecutive points
        for (let i = 1; i < points.length; i++) {
            const p1 = points[i - 1];
            const p2 = points[i];

            // By choosing a control point with a same column as one point and the
            // same row as the other point, we create a quarter-ellipse shape.  But
            // we must choose the correct orientation for each step in progression.
            let controlPoint;
            const phase = i % 4;
            if (phase === 0) {
                controlPoint = {
                    x: p2.x,
                    y: p1.y
                }
            } else if (phase === 1) {
                controlPoint = {
                    x: p1.x,
                    y: p2.y
                }
            } else if (phase === 2) {
                controlPoint = {
                    x: p2.x,
                    y: p1.y
                }
            } else if (phase === 3) {
                controlPoint = {
                    x: p1.x,
                    y: p2.y
                }
            }

            ctx.moveTo(p1.x, p1.y);
            // ctx.arc(p1.x, p1.y, 5, 0, 2 * Math.PI);
            // ctx.moveTo(p1.x, p1.y);
            ctx.quadraticCurveTo(controlPoint.x, controlPoint.y, p2.x, p2.y);
        }

        ctx.stroke();
    }

    this.combineDrawings = function(ctx, backingCanvas1, backingCanvas2) {
        const tileSize = this.size / this.gridSize;

        for (let row = 0; row < this.gridSize; row++) {
            for (let col = 0; col < this.gridSize; col++) {
                const parity = (row + col) % 2;
                const srcCanvas = parity === 0 ? backingCanvas1 : backingCanvas2;

                ctx.drawImage(
                    srcCanvas,
                    col * tileSize, row * tileSize, tileSize, tileSize,
                    col * tileSize, row * tileSize, tileSize, tileSize
                );
            }
        }
    }

    this.reset = function() {
        this.backingCtx1.fillStyle = '#000000';
        this.backingCtx1.strokeStyle = '#ffffff';
        this.drawOne(this.backingCtx1);

        this.backingCtx2.fillStyle = '#ffffff';
        this.backingCtx2.strokeStyle = '#000000';
        this.drawOne(this.backingCtx2);

        this.combineDrawings(this.ctx, this.backingCanvas1, this.backingCanvas2);
    }
}