63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
import type { WallNode } from '@pascal-app/core'
|
|
|
|
/**
|
|
* Default sill height (metres from the floor to the BOTTOM of a window) for a
|
|
* fresh window that has no wall-face height yet — the off-wall ghost and the
|
|
* floor-cursor placement use it so a new window floats slightly above the
|
|
* ground rather than sitting on it. The committed Y is the window's CENTRE, so
|
|
* callers add `height / 2`. An existing window keeps its own sill.
|
|
*/
|
|
export const DEFAULT_WINDOW_SILL_M = 0.5
|
|
|
|
/**
|
|
* Converts wall-local (X along wall, Y = height above wall base) to world XYZ.
|
|
* Wall XZ uses level-local coordinates (levels only offset in Y, not XZ).
|
|
* Pass levelYOffset (the level group's current world Y) and slabElevation (the
|
|
* wall mesh's Y within the level group) so the cursor lands at the correct world
|
|
* height — matching how WallSystem positions the wall mesh at slabElevation.
|
|
*/
|
|
export function wallLocalToWorld(
|
|
wallNode: WallNode,
|
|
localX: number,
|
|
localY: number,
|
|
levelYOffset = 0,
|
|
slabElevation = 0,
|
|
): [number, number, number] {
|
|
const wallAngle = Math.atan2(
|
|
wallNode.end[1] - wallNode.start[1],
|
|
wallNode.end[0] - wallNode.start[0],
|
|
)
|
|
return [
|
|
wallNode.start[0] + localX * Math.cos(wallAngle),
|
|
slabElevation + localY + levelYOffset,
|
|
wallNode.start[1] + localX * Math.sin(wallAngle),
|
|
]
|
|
}
|
|
|
|
/**
|
|
* Clamps window center position so it stays fully within wall bounds.
|
|
*/
|
|
export function clampToWall(
|
|
wallNode: WallNode,
|
|
localX: number,
|
|
localY: number,
|
|
width: number,
|
|
height: number,
|
|
): { clampedX: number; clampedY: number } {
|
|
const dx = wallNode.end[0] - wallNode.start[0]
|
|
const dz = wallNode.end[1] - wallNode.start[1]
|
|
const wallLength = Math.sqrt(dx * dx + dz * dz)
|
|
const wallHeight = wallNode.height ?? 2.5
|
|
|
|
const clampedX = Math.max(width / 2, Math.min(wallLength - width / 2, localX))
|
|
const clampedY = Math.max(height / 2, Math.min(wallHeight - height / 2, localY))
|
|
return { clampedX, clampedY }
|
|
}
|
|
|
|
/**
|
|
* Wall-child overlap is shared by door + window placement (one source of
|
|
* truth in `shared/wall-attach-target.ts`). Re-exported here so existing
|
|
* `./window-math` importers don't change.
|
|
*/
|
|
export { hasWallChildOverlap } from '../shared/wall-attach-target'
|