65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
import type { NodePort } from '@pascal-app/core'
|
|
import { Euler, Quaternion, Vector3 } from 'three'
|
|
import { equivalentDiameterIn, ovalEquivalentDiameterIn } from '../duct-segment/geometry'
|
|
import type { DuctTerminalNode } from './schema'
|
|
|
|
/** Collar stub length in meters behind the face. */
|
|
export const COLLAR_LENGTH = 0.12
|
|
|
|
/**
|
|
* Mount orientation: rotation applied to the canonical floor frame
|
|
* (face normal +Y, collar pointing -Y). Ceiling flips it; wall stands
|
|
* it up so the face looks along +Z and the collar points -Z (into the
|
|
* wall). Yaw is applied on top by the renderer / port transform.
|
|
*/
|
|
export function mountQuaternion(mount: DuctTerminalNode['mount']): Quaternion {
|
|
if (mount === 'ceiling') return new Quaternion().setFromEuler(new Euler(Math.PI, 0, 0))
|
|
if (mount === 'wall') return new Quaternion().setFromEuler(new Euler(Math.PI / 2, 0, 0))
|
|
return new Quaternion()
|
|
}
|
|
|
|
export function terminalSystem(node: DuctTerminalNode): 'supply' | 'return' {
|
|
return node.terminalType === 'return-grille' ? 'return' : 'supply'
|
|
}
|
|
|
|
/**
|
|
* Diameter (inches) the collar advertises at its port. Rect / oval
|
|
* collars report the area-equivalent round diameter so round runs mate
|
|
* at a sensible size — the same convention duct segments use.
|
|
*/
|
|
export function collarPortDiameterIn(node: DuctTerminalNode): number {
|
|
if (node.collarShape === 'rect') return equivalentDiameterIn(node.collarWidth, node.collarHeight)
|
|
if (node.collarShape === 'oval') {
|
|
return ovalEquivalentDiameterIn(node.collarWidth, node.collarHeight)
|
|
}
|
|
return node.collarDiameter
|
|
}
|
|
|
|
/**
|
|
* `def.ports` — the single collar port in level-local space. Canonical
|
|
* frame: collar tip at (0, -COLLAR_LENGTH, 0) pointing -Y (away from the
|
|
* face); mount + yaw + position transform it. Direction points OUT of
|
|
* the terminal — i.e. toward the duct that should connect.
|
|
*/
|
|
export function getDuctTerminalPorts(node: DuctTerminalNode): NodePort[] {
|
|
const transform = new Quaternion()
|
|
.setFromEuler(new Euler(0, node.rotation, 0))
|
|
.multiply(mountQuaternion(node.mount))
|
|
const position = new Vector3(0, -COLLAR_LENGTH, 0)
|
|
.applyQuaternion(transform)
|
|
.add(new Vector3(node.position[0], node.position[1], node.position[2]))
|
|
const direction = new Vector3(0, -1, 0).applyQuaternion(transform).normalize()
|
|
return [
|
|
{
|
|
id: 'collar',
|
|
position: [position.x, position.y, position.z] as const,
|
|
direction: [direction.x, direction.y, direction.z] as const,
|
|
diameter: collarPortDiameterIn(node),
|
|
system: terminalSystem(node),
|
|
shape: node.collarShape,
|
|
width: node.collarWidth,
|
|
height: node.collarHeight,
|
|
},
|
|
]
|
|
}
|