Implement canvas traversal
This commit is contained in:
parent
1372ab3cc0
commit
6638faec61
|
@ -39,11 +39,14 @@
|
|||
<label for="gridCols">Columns</label>
|
||||
<input type="number" id="gridCols" min="1" value="100" />
|
||||
|
||||
<label for="gridMax">Max</label>
|
||||
<input type="number" id="gridMax" min="1" value="10000" />
|
||||
|
||||
<label for="gridScale">Scale</label>
|
||||
<input type="number" id="gridScale" min="1" value="15" />
|
||||
|
||||
<label for="scrollX">Scroll X</label>
|
||||
<input type="number" id="scrollX" min="0" value="0" />
|
||||
|
||||
<label for="scrollY">Scroll Y</label>
|
||||
<input type="number" id="scrollY" min="0" value="0" />
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
import {CachedPrimeMath, PrimeMath} from "./PrimeMath";
|
||||
|
||||
|
||||
export class Display {
|
||||
private readonly canvas: HTMLCanvasElement;
|
||||
|
||||
private _painter: Painter | undefined = undefined;
|
||||
public set painter(painter: Painter) {
|
||||
this._painter = painter;
|
||||
}
|
||||
|
||||
private isDrawing: boolean = false;
|
||||
|
||||
|
||||
constructor(canvas: HTMLCanvasElement) {
|
||||
this.canvas = canvas;
|
||||
}
|
||||
|
||||
|
||||
public startDrawLoop(): void {
|
||||
if (this.isDrawing) return;
|
||||
|
||||
const cb = () => {
|
||||
if (!this.isDrawing) return;
|
||||
|
||||
this._painter?.draw(this.canvas);
|
||||
window.requestAnimationFrame(cb);
|
||||
};
|
||||
|
||||
this.isDrawing = true;
|
||||
window.requestAnimationFrame(cb);
|
||||
}
|
||||
|
||||
public stopDrawLoop(): void {
|
||||
this.isDrawing = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export interface Painter {
|
||||
getPixelWidth(): number;
|
||||
|
||||
getPixelHeight(): number;
|
||||
|
||||
draw(canvas: HTMLCanvasElement): void;
|
||||
}
|
||||
|
||||
export class GridPainter implements Painter {
|
||||
private readonly primeMath: PrimeMath = CachedPrimeMath.getInstance();
|
||||
|
||||
private readonly gridScale: number = 1 / 15;
|
||||
public scale: number = 15;
|
||||
public cols: number = 100;
|
||||
public max: number = 10_001;
|
||||
|
||||
public get rows(): number {
|
||||
return Math.ceil(this.max / this.cols);
|
||||
}
|
||||
|
||||
|
||||
getPixelWidth(): number {
|
||||
return this.scale * this.cols;
|
||||
}
|
||||
|
||||
getPixelHeight(): number {
|
||||
return this.scale * this.rows;
|
||||
}
|
||||
|
||||
|
||||
draw(canvas: HTMLCanvasElement): void {
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
|
||||
this.clearCanvas(ctx);
|
||||
this.drawPrimes(ctx);
|
||||
this.drawGrid(ctx);
|
||||
}
|
||||
|
||||
private clearCanvas(ctx: CanvasRenderingContext2D): void {
|
||||
ctx.save();
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fillRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private drawGrid(ctx: CanvasRenderingContext2D): void {
|
||||
// TODO: Don't draw grid for non-filled rows
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = "#7b7b7b";
|
||||
ctx.lineWidth = this.scale * this.gridScale;
|
||||
for (let x = 0; x <= this.cols; x++) {
|
||||
ctx.moveTo(x * this.scale, 0);
|
||||
ctx.lineTo(x * this.scale, this.rows * this.scale);
|
||||
}
|
||||
for (let y = 0; y <= this.rows; y++) {
|
||||
ctx.moveTo(0, y * this.scale);
|
||||
ctx.lineTo(this.cols * this.scale, y * this.scale);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private drawPrimes(ctx: CanvasRenderingContext2D): void {
|
||||
ctx.save();
|
||||
ctx.fillStyle = "#ff0000";
|
||||
for (let i = 1; i <= this.max; i++) {
|
||||
if (this.primeMath.isPrime(i))
|
||||
ctx.fillRect(
|
||||
((i - 1) % this.cols) * this.scale,
|
||||
Math.floor((i - 1) / this.cols) * this.scale,
|
||||
this.scale,
|
||||
this.scale
|
||||
);
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
// @ts-ignore
|
||||
const {$, doAfterLoad, footer, header, nav} = window.fwdekker;
|
||||
|
||||
import {Display, GridPainter} from "./Display";
|
||||
import {GridModel} from "./Model";
|
||||
import {Painter} from "./Painter";
|
||||
|
||||
|
||||
// Set up template
|
||||
|
@ -30,17 +31,20 @@ doAfterLoad(async () => {
|
|||
window.addEventListener("resize", resize, false);
|
||||
resize();
|
||||
|
||||
const display = new Display(canvas);
|
||||
const gridPainter = new GridPainter();
|
||||
display.painter = gridPainter;
|
||||
display.startDrawLoop();
|
||||
const painter = new Painter(canvas);
|
||||
const model = new GridModel();
|
||||
painter.model = model;
|
||||
painter.startDrawLoop();
|
||||
|
||||
const gridCols = $("#gridCols");
|
||||
gridCols.addEventListener("change", () => gridPainter.cols = gridCols.value);
|
||||
|
||||
const gridMax = $("#gridMax");
|
||||
gridMax.addEventListener("change", () => gridPainter.max = gridMax.value);
|
||||
gridCols.addEventListener("change", () => model.cols = parseInt(gridCols.value));
|
||||
|
||||
const gridScale = $("#gridScale");
|
||||
gridScale.addEventListener("change", () => gridPainter.scale = gridScale.value);
|
||||
gridScale.addEventListener("change", () => painter.scale = parseInt(gridScale.value));
|
||||
|
||||
const scrollX = $("#scrollX");
|
||||
scrollX.addEventListener("change", () => model.scrollX = parseInt(scrollX.value));
|
||||
|
||||
const scrollY = $("#scrollY");
|
||||
scrollY.addEventListener("change", () => model.scrollY = parseInt(scrollY.value));
|
||||
});
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import {CachedPrimeMath, PrimeMath} from "./PrimeMath";
|
||||
|
||||
|
||||
/**
|
||||
* Represents a particular way in which prime numbers can be arranged and drawn.
|
||||
*/
|
||||
export interface Model {
|
||||
/**
|
||||
* Returns `true` if and only if the square at coordinates (`x`, `y`) is a prime number.
|
||||
*
|
||||
* @param x the horizontal coordinate
|
||||
* @param y the vertical coordinate
|
||||
* @return `true` if and only if the square at coordinates (`x`, `y`) is a prime number
|
||||
*/
|
||||
isPrime(x: number, y: number): boolean | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `Model` for a simple grid of primes.
|
||||
*
|
||||
* Supports scrolling by setting the `scrollX` and `scrollY` values.
|
||||
*/
|
||||
export class GridModel implements Model {
|
||||
private readonly primeMath: PrimeMath = CachedPrimeMath.getInstance();
|
||||
|
||||
/**
|
||||
* The vertical position of the viewport.
|
||||
*/
|
||||
public scrollX: number = 0;
|
||||
/**
|
||||
* The horizontal position of the viewport.
|
||||
*/
|
||||
public scrollY: number = 0;
|
||||
/**
|
||||
* The number of columns to draw primes in.
|
||||
*/
|
||||
public cols: number = 100;
|
||||
|
||||
|
||||
isPrime(x: number, y: number): boolean | null {
|
||||
if (this.scrollX + x >= this.cols) return null;
|
||||
|
||||
const n = (y + this.scrollY) * this.cols + this.scrollX + x + 1;
|
||||
return this.primeMath.isPrime(n);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
import {Model} from "./Model";
|
||||
|
||||
/**
|
||||
* Paints prime numbers on a grid according to some `Model`.
|
||||
*
|
||||
* A `Painter` keeps track of the HTML side and determines which part of the `Model` is currently visible to the user.
|
||||
*/
|
||||
export class Painter {
|
||||
/**
|
||||
* The canvas to draw on.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly canvas: HTMLCanvasElement;
|
||||
/**
|
||||
* The context of `canvas` to draw on.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private readonly ctx: CanvasRenderingContext2D;
|
||||
/**
|
||||
* `true` if and only if this painter is currently in a draw loop.
|
||||
*
|
||||
* @see #startDrawLoop
|
||||
* @private
|
||||
*/
|
||||
private isDrawing: boolean = false;
|
||||
|
||||
/**
|
||||
* The model that determines where primes are painted.
|
||||
*/
|
||||
public model: Model | undefined;
|
||||
/**
|
||||
* The scale to draw prime numbers at.
|
||||
*/
|
||||
public scale: number = 15;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new `Painter`.
|
||||
*
|
||||
* @param canvas the canvas to be used by this painter
|
||||
*/
|
||||
constructor(canvas: HTMLCanvasElement) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext("2d")!;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The width of this painter in terms of grid squares.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private get width(): number {
|
||||
return this.ctx.canvas.clientWidth / this.scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* The height of this painter in terms of grid squares.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private get height(): number {
|
||||
return this.ctx.canvas.clientHeight / this.scale;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts repeatedly invoking `draw` onto the `canvas`.
|
||||
*/
|
||||
public startDrawLoop(): void {
|
||||
if (this.isDrawing) return;
|
||||
|
||||
const cb = () => {
|
||||
if (!this.isDrawing) return;
|
||||
|
||||
this.draw();
|
||||
window.requestAnimationFrame(cb);
|
||||
};
|
||||
|
||||
this.isDrawing = true;
|
||||
window.requestAnimationFrame(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the draw loop started by `startDrawLoop`.
|
||||
*/
|
||||
public stopDrawLoop(): void {
|
||||
this.isDrawing = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draws on the canvas once.
|
||||
*/
|
||||
draw(): void {
|
||||
this.clearCanvas();
|
||||
this.drawPrimes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the entire canvas.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private clearCanvas(): void {
|
||||
this.ctx.save();
|
||||
this.ctx.fillStyle = "#ffffff";
|
||||
this.ctx.fillRect(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
|
||||
this.ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws prime numbers on the canvas.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private drawPrimes(): void {
|
||||
const width = this.width;
|
||||
const height = this.height;
|
||||
|
||||
this.ctx.save();
|
||||
this.ctx.fillStyle = "#0033cc"; // TODO: Get color programmatically
|
||||
for (let x = 0; x < width; x++) {
|
||||
for (let y = 0; y < height; y++) {
|
||||
if (this.model?.isPrime(x, y)) {
|
||||
this.ctx.fillRect(x * this.scale, y * this.scale, this.scale, this.scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.ctx.restore();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue