Initial commit
This commit is contained in:
+159
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Rink geometry.
|
||||
*
|
||||
* NHL dimensions in metres, kept as plain numbers with no three.js import so
|
||||
* the sim, the tests and (later) a server can all agree on where the boards
|
||||
* are without pulling in a renderer.
|
||||
*
|
||||
* The surface is a rounded rectangle: a `halfX` by `halfZ` box with the four
|
||||
* corners replaced by quarter circles of radius `cornerR`. Every containment
|
||||
* query in the game reduces to "how far outside that shape are you", so it
|
||||
* lives here once as `rinkPenetration`.
|
||||
*/
|
||||
|
||||
/** 200ft x 85ft, 28ft corner radius, 42in boards. */
|
||||
export const RINK = Object.freeze({
|
||||
halfX: 30.48, // length/2 — the long axis runs along X
|
||||
halfZ: 12.95, // width/2
|
||||
cornerR: 8.53,
|
||||
boardHeight: 1.07,
|
||||
/** Glass above the boards is visual only in this spike. */
|
||||
glassHeight: 1.8,
|
||||
});
|
||||
|
||||
/** Blue lines / centre line, as distances from centre ice along X. */
|
||||
export const MARKINGS = Object.freeze({
|
||||
blueLine: 7.77,
|
||||
goalLine: 25.6,
|
||||
faceoffCircleR: 4.57,
|
||||
centreCircleR: 4.57,
|
||||
faceoffDotX: 6.7,
|
||||
faceoffDotZ: 6.7,
|
||||
zoneDotX: 20.2,
|
||||
});
|
||||
|
||||
/**
|
||||
* Centre of the corner arc nearest (x, z), and the sign of the quadrant.
|
||||
* Points outside the straight sections belong to exactly one corner.
|
||||
*/
|
||||
function cornerCentre(x, z, out) {
|
||||
const sx = x >= 0 ? 1 : -1;
|
||||
const sz = z >= 0 ? 1 : -1;
|
||||
out.x = sx * (RINK.halfX - RINK.cornerR);
|
||||
out.z = sz * (RINK.halfZ - RINK.cornerR);
|
||||
return out;
|
||||
}
|
||||
|
||||
const _c = { x: 0, z: 0 };
|
||||
|
||||
/**
|
||||
* Signed distance from the rink's inner surface, plus the inward normal.
|
||||
*
|
||||
* Positive `dist` means the point is outside the playing surface by that much;
|
||||
* `nx`/`nz` point back toward the ice. Returns the same object every call, so
|
||||
* copy anything you need to keep.
|
||||
*/
|
||||
const _pen = { dist: 0, nx: 0, nz: 0 };
|
||||
export function rinkPenetration(x, z, radius = 0) {
|
||||
const ax = Math.abs(x);
|
||||
const az = Math.abs(z);
|
||||
const straightX = RINK.halfX - RINK.cornerR;
|
||||
const straightZ = RINK.halfZ - RINK.cornerR;
|
||||
|
||||
if (ax <= straightX || az <= straightZ) {
|
||||
// Straight section: whichever wall is closer wins. A point can only be
|
||||
// outside one of them here, since the corners are handled below.
|
||||
const overX = ax + radius - RINK.halfX;
|
||||
const overZ = az + radius - RINK.halfZ;
|
||||
if (overX >= overZ) {
|
||||
_pen.dist = overX;
|
||||
_pen.nx = x >= 0 ? -1 : 1;
|
||||
_pen.nz = 0;
|
||||
} else {
|
||||
_pen.dist = overZ;
|
||||
_pen.nx = 0;
|
||||
_pen.nz = z >= 0 ? -1 : 1;
|
||||
}
|
||||
return _pen;
|
||||
}
|
||||
|
||||
cornerCentre(x, z, _c);
|
||||
const dx = x - _c.x;
|
||||
const dz = z - _c.z;
|
||||
const d = Math.hypot(dx, dz) || 1e-6;
|
||||
_pen.dist = d + radius - RINK.cornerR;
|
||||
_pen.nx = -dx / d;
|
||||
_pen.nz = -dz / d;
|
||||
return _pen;
|
||||
}
|
||||
|
||||
/** True when a circle of `radius` at (x, z) is fully on the ice. */
|
||||
export function insideRink(x, z, radius = 0) {
|
||||
return rinkPenetration(x, z, radius).dist <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a body back inside the boards and kill the velocity going into them.
|
||||
*
|
||||
* Box3D owns board contact for anything with a proxy capsule; this is the
|
||||
* headless fallback (tests, and any future server tick without a physics
|
||||
* world) and a cheap safety net against a body escaping the world.
|
||||
*
|
||||
* `restitution` 0 is a dead thud, 1 a perfect bounce. Boards eat most of it.
|
||||
*/
|
||||
export function clampToRink(state, radius = 0.36, restitution = 0.18) {
|
||||
const pen = rinkPenetration(state.x, state.z, radius);
|
||||
if (pen.dist <= 0) return false;
|
||||
state.x += pen.nx * pen.dist;
|
||||
state.z += pen.nz * pen.dist;
|
||||
const into = state.vx * pen.nx + state.vz * pen.nz;
|
||||
if (into < 0) {
|
||||
// Remove the inward-normal component, then add back a fraction reversed.
|
||||
state.vx -= into * pen.nx * (1 + restitution);
|
||||
state.vz -= into * pen.nz * (1 + restitution);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The board line as a closed polyline, counter-clockwise from the +X end.
|
||||
*
|
||||
* The physics boards and the rendered boards are both built from this, so the
|
||||
* wall a skater bounces off is the wall they can see. `cornerSteps` is the
|
||||
* number of segments each of the four corner arcs is cut into.
|
||||
*/
|
||||
export function rinkOutline(cornerSteps = 8) {
|
||||
const sx = RINK.halfX - RINK.cornerR;
|
||||
const sz = RINK.halfZ - RINK.cornerR;
|
||||
const pts = [];
|
||||
// Four corners, each an arc swept from its own quadrant, with the straight
|
||||
// sections falling out as the gaps between consecutive arcs.
|
||||
const corners = [
|
||||
{ cx: sx, cz: sz, a0: 0 }, // +X +Z
|
||||
{ cx: -sx, cz: sz, a0: Math.PI / 2 }, // -X +Z
|
||||
{ cx: -sx, cz: -sz, a0: Math.PI }, // -X -Z
|
||||
{ cx: sx, cz: -sz, a0: -Math.PI / 2 }, // +X -Z
|
||||
];
|
||||
for (const c of corners) {
|
||||
for (let i = 0; i <= cornerSteps; i++) {
|
||||
const a = c.a0 + (i / cornerSteps) * (Math.PI / 2);
|
||||
pts.push({ x: c.cx + Math.cos(a) * RINK.cornerR, z: c.cz + Math.sin(a) * RINK.cornerR });
|
||||
}
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
/**
|
||||
* A random point on the ice, inset from the boards.
|
||||
* `rand` is any () => [0,1) so callers can keep it seeded.
|
||||
*/
|
||||
export function randomIcePoint(rand, inset = 3) {
|
||||
for (let i = 0; i < 24; i++) {
|
||||
const x = (rand() * 2 - 1) * (RINK.halfX - inset);
|
||||
const z = (rand() * 2 - 1) * (RINK.halfZ - inset);
|
||||
if (insideRink(x, z, inset)) return { x, z };
|
||||
}
|
||||
// Rejection sampling in a rounded rect basically never fails, but never
|
||||
// hand back an off-ice waypoint if it does.
|
||||
return { x: 0, z: 0 };
|
||||
}
|
||||
Reference in New Issue
Block a user