249 lines
7.2 KiB
JavaScript
249 lines
7.2 KiB
JavaScript
/*
|
|
This component is responsible for the rendering of a RailSystem in a 2d map
|
|
view.
|
|
*/
|
|
|
|
const SCALE_VALUES = [0.01, 0.1, 0.25, 0.5, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0, 10.0, 12.0, 16.0, 20.0, 30.0, 45.0, 60.0, 80.0, 100.0];
|
|
const SCALE_INDEX_NORMAL = 7;
|
|
const HOVER_RADIUS = 10;
|
|
|
|
let mapCanvas = null;
|
|
let railSystem = null;
|
|
|
|
let mapScaleIndex = SCALE_INDEX_NORMAL;
|
|
let mapTranslation = {x: 0, y: 0};
|
|
let mapDragOrigin = null;
|
|
let mapDragTranslation = null;
|
|
let lastMousePoint = new DOMPoint(0, 0, 0, 0);
|
|
const hoveredElements = [];
|
|
|
|
export function initMap(rs) {
|
|
railSystem = rs;
|
|
console.log("Initializing map for rail system: " + rs.name);
|
|
hoveredElements.length = 0;
|
|
mapCanvas = document.getElementById("railSystemMapCanvas");
|
|
mapCanvas.removeEventListener("wheel", onMouseWheel);
|
|
mapCanvas.addEventListener("wheel", onMouseWheel);
|
|
mapCanvas.removeEventListener("mousedown", onMouseDown);
|
|
mapCanvas.addEventListener("mousedown", onMouseDown);
|
|
mapCanvas.removeEventListener("mouseup", onMouseUp);
|
|
mapCanvas.addEventListener("mouseup", onMouseUp);
|
|
mapCanvas.removeEventListener("mousemove", onMouseMove);
|
|
mapCanvas.addEventListener("mousemove", onMouseMove);
|
|
|
|
// Do an initial draw.
|
|
draw();
|
|
}
|
|
|
|
export function draw() {
|
|
if (!(mapCanvas && railSystem && railSystem.components)) {
|
|
console.warn("Attempted to draw map without canvas or railSystem.");
|
|
return;
|
|
}
|
|
const ctx = mapCanvas.getContext("2d");
|
|
const width = mapCanvas.width;
|
|
const height = mapCanvas.height;
|
|
ctx.resetTransform();
|
|
ctx.fillStyle = `rgb(240, 240, 240)`;
|
|
ctx.fillRect(0, 0, width, height);
|
|
const worldTx = getWorldTransform();
|
|
ctx.setTransform(worldTx);
|
|
|
|
for (let i = 0; i < railSystem.components.length; i++) {
|
|
drawComponent(ctx, worldTx, railSystem.components[i]);
|
|
}
|
|
|
|
// Draw debug info.
|
|
ctx.resetTransform();
|
|
ctx.fillStyle = "black";
|
|
ctx.strokeStyle = "black";
|
|
ctx.font = "10px Sans-Serif";
|
|
const lastWorldPoint = mapPointToWorld(lastMousePoint);
|
|
const lines = [
|
|
"Scale factor: " + getScaleFactor(),
|
|
`(x = ${lastWorldPoint.x.toFixed(2)}, y = ${lastWorldPoint.y.toFixed(2)}, z = ${lastWorldPoint.z.toFixed(2)})`,
|
|
`Components: ${railSystem.components.length}`,
|
|
`Hovered elements: ${hoveredElements.length}`
|
|
]
|
|
for (let i = 0; i < lines.length; i++) {
|
|
ctx.fillText(lines[i], 10, 20 + (i * 15));
|
|
}
|
|
}
|
|
|
|
function drawComponent(ctx, worldTx, component) {
|
|
const tx = DOMMatrix.fromMatrix(worldTx);
|
|
tx.translateSelf(component.position.x, component.position.z, 0);
|
|
const s = getScaleFactor();
|
|
tx.scaleSelf(1/s, 1/s, 1/s);
|
|
tx.scaleSelf(5, 5, 5);
|
|
ctx.setTransform(tx);
|
|
if (isComponentHovered(component)) {
|
|
ctx.fillStyle = `rgba(255, 255, 0, 32)`;
|
|
ctx.beginPath();
|
|
ctx.ellipse(0, 0, 1.8, 1.8, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
if (component.type === "SIGNAL") {
|
|
drawSignal(ctx, component);
|
|
} else if (component.type === "SEGMENT_BOUNDARY") {
|
|
drawSegmentBoundary(ctx, component);
|
|
}
|
|
}
|
|
|
|
function drawSignal(ctx, signal) {
|
|
roundedRect(ctx, -0.7, -1, 1.4, 2, 0.25);
|
|
ctx.fillStyle = "black";
|
|
ctx.fill();
|
|
|
|
// ctx.fillStyle = "green";
|
|
// ctx.beginPath();
|
|
// ctx.ellipse(0, 0, 0.8, 0.8, 0, 0, Math.PI * 2);
|
|
// ctx.fill();
|
|
//
|
|
// ctx.strokeStyle = "black";
|
|
// ctx.lineWidth = 0.5;
|
|
// ctx.beginPath();
|
|
// ctx.ellipse(0, 0, 1, 1, 0, 0, Math.PI * 2);
|
|
// ctx.stroke();
|
|
}
|
|
|
|
function drawSegmentBoundary(ctx, segmentBoundary) {
|
|
ctx.fillStyle = "blue";
|
|
ctx.beginPath();
|
|
ctx.ellipse(0, 0, 1, 1, 0, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
|
|
export function getScaleFactor() {
|
|
return SCALE_VALUES[mapScaleIndex];
|
|
}
|
|
|
|
function getWorldTransform() {
|
|
const canvasRect = mapCanvas.getBoundingClientRect();
|
|
const scale = getScaleFactor();
|
|
const tx = new DOMMatrix();
|
|
tx.translateSelf(canvasRect.width / 2, canvasRect.height / 2, 0);
|
|
tx.scaleSelf(scale, scale, scale);
|
|
tx.translateSelf(mapTranslation.x, mapTranslation.y, 0);
|
|
if (mapDragOrigin !== null && mapDragTranslation !== null) {
|
|
tx.translateSelf(mapDragTranslation.x, mapDragTranslation.y, 0);
|
|
}
|
|
return tx;
|
|
}
|
|
|
|
function isComponentHovered(component) {
|
|
for (let i = 0; i < hoveredElements.length; i++) {
|
|
if (hoveredElements[i].id === component.id) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Maps a point on the map coordinates to world coordinates.
|
|
* @param {DOMPoint} p
|
|
* @returns {DOMPoint}
|
|
*/
|
|
function mapPointToWorld(p) {
|
|
return getWorldTransform().invertSelf().transformPoint(p);
|
|
}
|
|
|
|
/**
|
|
* Maps a point in the world to map coordinates.
|
|
* @param {DOMPoint} p
|
|
* @returns {DOMPoint}
|
|
*/
|
|
function worldPointToMap(p) {
|
|
return getWorldTransform().transformPoint(p);
|
|
}
|
|
|
|
function roundedRect(ctx, x, y, w, h, r) {
|
|
if (w < 2 * r) r = w / 2;
|
|
if (h < 2 * r) r = h / 2;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x+r, y);
|
|
ctx.arcTo(x+w, y, x+w, y+h, r);
|
|
ctx.arcTo(x+w, y+h, x, y+h, r);
|
|
ctx.arcTo(x, y+h, x, y, r);
|
|
ctx.arcTo(x, y, x+w, y, r);
|
|
ctx.closePath();
|
|
}
|
|
|
|
/*
|
|
EVENT HANDLING
|
|
*/
|
|
|
|
/**
|
|
* @param {WheelEvent} event
|
|
*/
|
|
function onMouseWheel(event) {
|
|
const s = event.deltaY;
|
|
if (s > 0) {
|
|
mapScaleIndex = Math.max(0, mapScaleIndex - 1);
|
|
} else if (s < 0) {
|
|
mapScaleIndex = Math.min(SCALE_VALUES.length - 1, mapScaleIndex + 1);
|
|
}
|
|
draw();
|
|
event.stopPropagation();
|
|
}
|
|
|
|
/**
|
|
* @param {MouseEvent} event
|
|
*/
|
|
function onMouseDown(event) {
|
|
const p = getMousePoint(event);
|
|
mapDragOrigin = {x: p.x, y: p.y};
|
|
}
|
|
|
|
function onMouseUp() {
|
|
if (mapDragTranslation !== null) {
|
|
mapTranslation.x += mapDragTranslation.x;
|
|
mapTranslation.y += mapDragTranslation.y;
|
|
}
|
|
if (hoveredElements.length === 1) {
|
|
railSystem.selectedComponent = hoveredElements[0];
|
|
} else {
|
|
railSystem.selectedComponent = null;
|
|
}
|
|
mapDragOrigin = null;
|
|
mapDragTranslation = null;
|
|
}
|
|
|
|
/**
|
|
* @param {MouseEvent} event
|
|
*/
|
|
function onMouseMove(event) {
|
|
const p = getMousePoint(event);
|
|
lastMousePoint = p;
|
|
if (mapDragOrigin !== null) {
|
|
const scale = getScaleFactor();
|
|
const dx = p.x - mapDragOrigin.x;
|
|
const dy = p.y - mapDragOrigin.y;
|
|
mapDragTranslation = {x: dx / scale, y: dy / scale};
|
|
} else {
|
|
hoveredElements.length = 0;
|
|
// Populate with list of hovered elements.
|
|
for (let i = 0; i < railSystem.components.length; i++) {
|
|
const c = railSystem.components[i];
|
|
const componentPoint = new DOMPoint(c.position.x, c.position.z, 0, 1);
|
|
const mapComponentPoint = worldPointToMap(componentPoint);
|
|
const dist2 = (p.x - mapComponentPoint.x) * (p.x - mapComponentPoint.x) + (p.y - mapComponentPoint.y) * (p.y - mapComponentPoint.y);
|
|
if (dist2 < HOVER_RADIUS * HOVER_RADIUS) {
|
|
hoveredElements.push(c);
|
|
}
|
|
}
|
|
}
|
|
draw();
|
|
}
|
|
|
|
/**
|
|
* Gets the point at which the user clicked on the map.
|
|
* @param {MouseEvent} event
|
|
* @returns {DOMPoint}
|
|
*/
|
|
function getMousePoint(event) {
|
|
const rect = mapCanvas.getBoundingClientRect();
|
|
const x = event.clientX - rect.left;
|
|
const y = event.clientY - rect.top;
|
|
return new DOMPoint(x, y, 0, 1);
|
|
}
|