Files
2025-01-04 00:34:03 +01:00

237 lines
6.7 KiB
JavaScript

import BaseGrid from "./base.mjs";
import {GRID_TYPES, MOVEMENT_DIRECTIONS} from "../constants.mjs";
/**
* The gridless grid class.
*/
export default class GridlessGrid extends BaseGrid {
/** @override */
type = GRID_TYPES.GRIDLESS;
/* -------------------------------------------- */
/** @override */
calculateDimensions(sceneWidth, sceneHeight, padding) {
// Note: Do not replace `* (1 / this.size)` by `/ this.size`!
// It could change the result and therefore break certain scenes.
const x = Math.ceil((padding * sceneWidth) * (1 / this.size)) * this.size;
const y = Math.ceil((padding * sceneHeight) * (1 / this.size)) * this.size;
const width = sceneWidth + (2 * x);
const height = sceneHeight + (2 * y);
return {width, height, x, y, rows: Math.ceil(height), columns: Math.ceil(width)};
}
/* -------------------------------------------- */
/** @override */
getOffset(coords) {
const i = coords.i;
if ( i !== undefined ) return {i, j: coords.j};
return {i: Math.round(coords.y) | 0, j: Math.round(coords.x) | 0};
}
/* -------------------------------------------- */
/** @override */
getOffsetRange({x, y, width, height}) {
const i0 = Math.floor(y);
const j0 = Math.floor(x);
if ( !((width > 0) && (height > 0)) ) return [i0, j0, i0, j0];
return [i0, j0, Math.ceil(y + height) | 0, Math.ceil(x + width) | 0];
}
/* -------------------------------------------- */
/** @override */
getAdjacentOffsets(coords) {
return [];
}
/* -------------------------------------------- */
/** @override */
testAdjacency(coords1, coords2) {
return false;
}
/* -------------------------------------------- */
/** @override */
getShiftedOffset(coords, direction) {
const i = coords.i;
if ( i !== undefined ) coords = {x: coords.j, y: i};
return this.getOffset(this.getShiftedPoint(coords, direction));
}
/* -------------------------------------------- */
/** @override */
getShiftedPoint(point, direction) {
let di = 0;
let dj = 0;
if ( direction & MOVEMENT_DIRECTIONS.UP ) di--;
if ( direction & MOVEMENT_DIRECTIONS.DOWN ) di++;
if ( direction & MOVEMENT_DIRECTIONS.LEFT ) dj--;
if ( direction & MOVEMENT_DIRECTIONS.RIGHT ) dj++;
return {x: point.x + (dj * this.size), y: point.y + (di * this.size)};
}
/* -------------------------------------------- */
/** @override */
getTopLeftPoint(coords) {
const i = coords.i;
if ( i !== undefined ) return {x: coords.j, y: i};
return {x: coords.x, y: coords.y};
}
/* -------------------------------------------- */
/** @override */
getCenterPoint(coords) {
const i = coords.i;
if ( i !== undefined ) return {x: coords.j, y: i};
return {x: coords.x, y: coords.y};
}
/* -------------------------------------------- */
/** @override */
getShape() {
return [];
}
/* -------------------------------------------- */
/** @override */
getVertices(coords) {
return [];
}
/* -------------------------------------------- */
/** @override */
getSnappedPoint({x, y}, behavior) {
return {x, y};
}
/* -------------------------------------------- */
/** @override */
_measurePath(waypoints, {cost}, result) {
result.distance = 0;
result.spaces = 0;
result.cost = 0;
if ( waypoints.length === 0 ) return;
const from = result.waypoints[0];
from.distance = 0;
from.spaces = 0;
from.cost = 0;
// Prepare data for the starting point
const w0 = waypoints[0];
let o0 = this.getOffset(w0);
let p0 = this.getCenterPoint(w0);
// Iterate over additional path points
for ( let i = 1; i < waypoints.length; i++ ) {
const w1 = waypoints[i];
const o1 = this.getOffset(w1);
const p1 = this.getCenterPoint(w1);
// Measure segment
const to = result.waypoints[i];
const segment = to.backward;
if ( !w1.teleport ) {
// Calculate the Euclidean distance
segment.distance = Math.hypot(p0.x - p1.x, p0.y - p1.y) / this.size * this.distance;
segment.spaces = 0;
const offsetDistance = Math.hypot(o0.i - o1.i, o0.j - o1.j) / this.size * this.distance;
segment.cost = cost && (offsetDistance !== 0) ? cost(o0, o1, offsetDistance) : offsetDistance;
} else {
segment.distance = 0;
segment.spaces = 0;
segment.cost = cost && ((o0.i !== o1.i) || (o0.j !== o1.j)) ? cost(o0, o1, 0) : 0;
}
// Accumulate measurements
result.distance += segment.distance;
result.cost += segment.cost;
// Set waypoint measurements
to.distance = result.distance;
to.spaces = 0;
to.cost = result.cost;
o0 = o1;
p0 = p1;
}
}
/* -------------------------------------------- */
/** @override */
getDirectPath(waypoints) {
if ( waypoints.length === 0 ) return [];
let o0 = this.getOffset(waypoints[0]);
const path = [o0];
for ( let i = 1; i < waypoints.length; i++ ) {
const o1 = this.getOffset(waypoints[i]);
if ( (o0.i === o1.i) && (o0.j === o1.j) ) continue;
path.push(o1);
o0 = o1;
}
return path;
}
/* -------------------------------------------- */
/** @override */
getTranslatedPoint(point, direction, distance) {
direction = Math.toRadians(direction);
const dx = Math.cos(direction);
const dy = Math.sin(direction);
const s = distance / this.distance * this.size;
return {x: point.x + (dx * s), y: point.y + (dy * s)};
}
/* -------------------------------------------- */
/** @override */
getCircle({x, y}, radius) {
if ( radius <= 0 ) return [];
const r = radius / this.distance * this.size;
const n = Math.max(Math.ceil(Math.PI / Math.acos(Math.max(r - 0.25, 0) / r)), 4);
const points = new Array(n);
for ( let i = 0; i < n; i++ ) {
const a = 2 * Math.PI * (i / n);
points[i] = {x: x + (Math.cos(a) * r), y: y + (Math.sin(a) * r)};
}
return points;
}
/* -------------------------------------------- */
/** @override */
getCone(origin, radius, direction, angle) {
if ( (radius <= 0) || (angle <= 0) ) return [];
if ( angle >= 360 ) return this.getCircle(origin, radius);
const r = radius / this.distance * this.size;
const n = Math.max(Math.ceil(Math.PI / Math.acos(Math.max(r - 0.25, 0) / r) * (angle / 360)), 4);
const a0 = Math.toRadians(direction - (angle / 2));
const a1 = Math.toRadians(direction + (angle / 2));
const points = new Array(n + 1);
const {x, y} = origin;
points[0] = {x, y};
for ( let i = 0; i <= n; i++ ) {
const a = Math.mix(a0, a1, i / n);
points[i + 1] = {x: x + (Math.cos(a) * r), y: y + (Math.sin(a) * r)};
}
return points;
}
}