class GalleryPlan {
constructor(width, height, scale) {
this.width = width;
this.height = height;
this.scale = scale;
this.walls = [];
this.objects = [];
this.wallAttrs = {};
this.minimalDistanceToWall = 0.1;
this.curPos = [0, 0];
}
jumpTo(x, y) {
this.curPos = [x, y];
}
wallTo(x, y, wallAttrs=null) {
this.walls.push({
x1: this.curPos[0],
y1: this.curPos[1],
x2: x,
y2: y,
attrs: {...this.wallAttrs, ...wallAttrs},
});
this.curPos = [x, y];
}
tryMove(dx, dy) {
const N = 10;
let waltThroughFired = false;
for (let i=0; i<=N; i++) {
const [sx, sy] = this.curPos;
const [tx, ty] = [sx + dx / N, sy + dy / N];
for (const {x1, y1, x2, y2, attrs} of this.walls) {
const dist = ptToSegDist(tx, ty, x1, y1, x2, y2);
const int = intersect(sx, sy, tx, ty, x1, y1, x2, y2);
if (attrs.canWalkThrough) {
if (attrs.onWalkThrough && int && !waltThroughFired) {
attrs.onWalkThrough();
waltThroughFired = true;
}
} else if (dist < this.minimalDistanceToWall || int) {
return false;
}
}
for (const {x, y, r, attrs} of this.objects) {
const dist = ((x - tx) ** 2 + (y - ty) ** 2) ** 0.5;
if (dist < r) {
return false;
}
}
this.curPos = [tx, ty];
}
}
put(dir, name, constructor, attrs, r=0) {
this.objects.push({
x: this.curPos[0],
y: this.curPos[1],
r,
dir,
name,
constructor,
attrs,
});
}
onKeyDown = e => {
const delta = 10;
if (e.key === 'a') {
this.tryMove(-delta, 0);
} else if (e.key === 'd') {
this.tryMove(+delta, 0);
} else if (e.key === 'w') {
this.tryMove(0, -delta);
} else if (e.key === 's') {
this.tryMove(0, +delta);
}
this.draw();
}
mountControls() {
window.addEventListener('keydown', this.onKeyDown);
}
unmountControls() {
window.removeEventListener('keydown', this.onKeyDown);
}
draw() {
if (!this.ctx) {
this.ctx = DOM.context2d(this.width * 1.2 * this.scale, this.height * 1.2 * this.scale);
}
const {ctx} = this;
ctx.save();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.translate(this.width * 0.1 * this.scale, this.height * 0.1 * this.scale);
ctx.scale(this.scale, this.scale);
ctx.fillStyle = 'black';
ctx.strokeStyle = 'black';
ctx.lineWidth = 1 / this.scale;
this.walls.forEach(({x1, y1, x2, y2, attrs}) => {
ctx.strokeStyle = attrs.canWalkThrough ? 'red' : 'black';
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
});
this.objects.forEach(({x, y, r, dir, name}) => {
ctx.fillStyle = 'green';
ctx.strokeStyle = 'green';
ctx.fillRect(x - 2 / this.scale, y - 2 / this.scale, 4 / this.scale, 4 / this.scale);
ctx.beginPath();
ctx.moveTo(x, y);
ctx.arc(x, y, r / this.scale, 0, Math.PI * 2);
ctx.stroke();
const a = dir / 180 * Math.PI;
const rr = 10 / this.scale;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + rr * Math.cos(a), y + rr * Math.sin(a));
ctx.stroke();
ctx.font = `${10 / this.scale}px monospace`;
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.strokeStyle = 'white';
ctx.lineWidth = 3 / this.scale;
ctx.strokeText(name, x, y - 2 / this.scale);
ctx.fillText(name, x, y - 2 / this.scale);
ctx.lineWidth = 1 / this.scale;
});
const [x, y] = this.curPos;
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(x, y, 2 / this.scale, 0, 2 * Math.PI);
ctx.fill();
ctx.restore();
return ctx.canvas;
}
}