161 lines
4.1 KiB
JavaScript
161 lines
4.1 KiB
JavaScript
import {
|
|
ARROWHEAD_LENGTH,
|
|
ARROWHEAD_WIDTH,
|
|
} from './config.js';
|
|
|
|
export class Display {
|
|
scalePower = 0;
|
|
viewOrigin = {x: 0, y: 0};
|
|
|
|
constructor(sim) {
|
|
const canvas = document.createElement('canvas')
|
|
this.canvas = canvas;
|
|
|
|
if (sim) {
|
|
// Create canvas that fills the window
|
|
// If the window resizes, also resize the canvas
|
|
sim.div.appendChild(canvas);
|
|
this.fullscreen();
|
|
window.addEventListener('resize', () => this.fullscreen());
|
|
}
|
|
}
|
|
|
|
toJSON() {
|
|
return {
|
|
scalePower: this.scalePower,
|
|
viewOrigin: this.viewOrigin,
|
|
};
|
|
}
|
|
|
|
fromJSON({scalePower, viewOrigin}) {
|
|
this.scalePower = scalePower;
|
|
this.viewOrigin = viewOrigin;
|
|
}
|
|
|
|
frame() {
|
|
// Clear canvas in preparation for other modules to render this frame
|
|
this.fillCanvas();
|
|
}
|
|
|
|
get scale() {
|
|
return 2 ** this.scalePower;
|
|
}
|
|
|
|
get ctx() {
|
|
const ctx = this.canvas.getContext("2d");
|
|
ctx.resetTransform();
|
|
ctx.scale(this.scale, this.scale);
|
|
ctx.translate(-this.viewOrigin.x, -this.viewOrigin.y);
|
|
return ctx;
|
|
}
|
|
|
|
get width() {
|
|
return this.canvas.width / this.scale;
|
|
}
|
|
|
|
get height() {
|
|
return this.canvas.height / this.scale;
|
|
}
|
|
|
|
screenToSim(x, y) {
|
|
return {
|
|
x: x / this.scale + this.viewOrigin.x,
|
|
y: y / this.scale + this.viewOrigin.y,
|
|
};
|
|
}
|
|
|
|
simToScreen(x, y) {
|
|
return {
|
|
x: (x - this.viewOrigin.x) * this.scale,
|
|
y: (y - this.viewOrigin.y) * this.scale,
|
|
};
|
|
}
|
|
|
|
fullscreen() {
|
|
this.canvas.width = document.documentElement.clientWidth;
|
|
this.canvas.height = document.documentElement.clientHeight;
|
|
}
|
|
|
|
fillCanvas() {
|
|
const ctx = this.ctx;
|
|
ctx.fillStyle = '#000';
|
|
ctx.fillRect(this.viewOrigin.x, this.viewOrigin.y, this.width, this.height);
|
|
}
|
|
|
|
drawArrow(startX, startY, endX, endY, {
|
|
style, width, arrowhead, arrowheadLength, fill, ifShort
|
|
}) {
|
|
const ctx = this.ctx;
|
|
ctx.strokeStyle = style;
|
|
// Keep arrows at normal scale
|
|
ctx.lineWidth = width / this.scale;
|
|
arrowhead = arrowhead ?? true;
|
|
arrowheadLength = arrowheadLength ?? ARROWHEAD_LENGTH;
|
|
const scaledArrowheadLength = arrowheadLength / this.scale;
|
|
ifShort = ifShort ?? 'tail';
|
|
const arrowDirection = Math.atan2(endY - startY, endX - startX);
|
|
const length = Math.sqrt((endX - startX) ** 2 + (endY - startY) ** 2);
|
|
let tail = true;
|
|
if (!length) {
|
|
return;
|
|
}
|
|
if (length <= scaledArrowheadLength) {
|
|
switch (ifShort) {
|
|
case 'head': {
|
|
arrowhead = true;
|
|
tail = false;
|
|
break;
|
|
}
|
|
case 'tail': {
|
|
arrowhead = false;
|
|
tail = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tail) {
|
|
const endAx = arrowhead ? endX - (scaledArrowheadLength) * Math.cos(arrowDirection) : endX;
|
|
const endAy = arrowhead ? endY - (scaledArrowheadLength) * Math.sin(arrowDirection) : endY;
|
|
ctx.beginPath();
|
|
ctx.moveTo(startX, startY);
|
|
ctx.lineTo(endAx, endAy);
|
|
ctx.stroke();
|
|
}
|
|
if (arrowhead) {
|
|
this.drawArrowHead(endX, endY, arrowDirection, {style, length: arrowheadLength, fill});
|
|
}
|
|
}
|
|
|
|
drawArrowHead(x, y, direction, {style, length, fill}) {
|
|
const arrowheadLength = length ?? ARROWHEAD_LENGTH;
|
|
const arrowheadWidth = arrowheadLength / ARROWHEAD_LENGTH * ARROWHEAD_WIDTH;
|
|
const ctx = this.ctx;
|
|
ctx.fillStyle = style;
|
|
ctx.strokeStyle = style;
|
|
// To make this simple, draw the arrowhead and then rotate and translate it as needed.
|
|
// Keep arrows at normal scale
|
|
const scaledArrowheadLength = arrowheadLength / this.scale;
|
|
const scaledArrowheadWidth = arrowheadWidth / this.scale;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y);
|
|
ctx.translate(x, y);
|
|
ctx.rotate(direction);
|
|
ctx.lineTo(-scaledArrowheadLength, -scaledArrowheadWidth / 2);
|
|
ctx.lineTo(-scaledArrowheadLength, scaledArrowheadWidth / 2);
|
|
ctx.closePath();
|
|
if (fill !== false) {
|
|
ctx.fill();
|
|
} else {
|
|
ctx.stroke();
|
|
}
|
|
ctx.resetTransform();
|
|
}
|
|
|
|
drawBox(start, end) {
|
|
const ctx = this.ctx;
|
|
ctx.strokeStyle = 'rgb(0, 255, 0)';
|
|
ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y);
|
|
}
|
|
}
|