161 lines
5.3 KiB
TypeScript
161 lines
5.3 KiB
TypeScript
import * as THREE from 'three'
|
|
import { createDormerArchShape, createDormerRoundedShape } from './csg-geometry'
|
|
|
|
/**
|
|
* Frame + glass geometry for the window opening on a dormer's gable
|
|
* face. The extruded frame profile uses the same shape builders as the
|
|
* CSG cut in the viewer (`generateDormerGeometry`), so the frame sits
|
|
* flush in the wall — keeping the cut and the frame visually in sync.
|
|
*
|
|
* Only the frame bars and glass panes are produced here; the wall
|
|
* opening itself is CSG-subtracted from the dormer body inside the
|
|
* viewer's `generateDormerGeometry`.
|
|
*/
|
|
export type DormerWindowShape = 'rectangle' | 'rounded' | 'arch'
|
|
|
|
export type WindowGeometries = {
|
|
frameBars: { geo: THREE.BufferGeometry; pos: [number, number, number] }[]
|
|
glassPanes: { geo: THREE.BufferGeometry; pos: [number, number, number] }[]
|
|
}
|
|
|
|
export function buildDormerWindowGeometries(
|
|
winW: number,
|
|
winH: number,
|
|
ft: number,
|
|
fd: number,
|
|
cols: number,
|
|
rows: number,
|
|
dt: number,
|
|
shape: DormerWindowShape = 'rectangle',
|
|
archHeight = 0.35,
|
|
cornerRadii: [number, number, number, number] = [0.15, 0.15, 0.15, 0.15],
|
|
): WindowGeometries {
|
|
const safeFt = Math.max(0.001, ft)
|
|
const safeDt = Math.max(0.001, dt)
|
|
const innerW = Math.max(0.01, winW - 2 * safeFt)
|
|
const innerH = Math.max(0.01, winH - 2 * safeFt)
|
|
const hw = winW / 2
|
|
const hh = winH / 2
|
|
|
|
const frameBars: WindowGeometries['frameBars'] = []
|
|
const glassPanes: WindowGeometries['glassPanes'] = []
|
|
|
|
if (shape === 'arch' || shape === 'rounded') {
|
|
const insetRadii = cornerRadii.map((r) => Math.max(r - safeFt, 0)) as [
|
|
number,
|
|
number,
|
|
number,
|
|
number,
|
|
]
|
|
const outerShape =
|
|
shape === 'arch'
|
|
? createDormerArchShape(winW, winH, archHeight)
|
|
: createDormerRoundedShape(winW, winH, cornerRadii)
|
|
|
|
const innerHole =
|
|
shape === 'arch'
|
|
? createDormerArchShape(
|
|
winW - 2 * safeFt,
|
|
winH - 2 * safeFt,
|
|
Math.max(archHeight - safeFt, 0.01),
|
|
)
|
|
: createDormerRoundedShape(winW - 2 * safeFt, winH - 2 * safeFt, insetRadii)
|
|
|
|
outerShape.holes.push(innerHole)
|
|
const frameGeo = new THREE.ExtrudeGeometry(outerShape, {
|
|
depth: fd,
|
|
bevelEnabled: false,
|
|
curveSegments: 24,
|
|
})
|
|
frameGeo.translate(0, 0, -fd / 2)
|
|
frameBars.push({ geo: frameGeo, pos: [0, 0, 0] })
|
|
|
|
const colDividerCount = cols - 1
|
|
const totalColDividerW = colDividerCount * safeDt
|
|
const paneAreaW = Math.max(0.01, innerW - totalColDividerW)
|
|
const paneW = paneAreaW / cols
|
|
|
|
for (let c = 1; c < cols; c++) {
|
|
const x = -innerW / 2 + c * paneW + (c - 0.5) * safeDt
|
|
frameBars.push({ geo: new THREE.BoxGeometry(safeDt, innerH, fd), pos: [x, 0, 0] })
|
|
}
|
|
|
|
const rowDividerCount = rows - 1
|
|
const totalRowDividerH = rowDividerCount * safeDt
|
|
const paneAreaH = Math.max(0.01, innerH - totalRowDividerH)
|
|
const paneH = paneAreaH / rows
|
|
|
|
for (let r = 1; r < rows; r++) {
|
|
const y = -innerH / 2 + r * paneH + (r - 0.5) * safeDt
|
|
frameBars.push({ geo: new THREE.BoxGeometry(innerW, safeDt, fd), pos: [0, y, 0] })
|
|
}
|
|
|
|
const glassShape =
|
|
shape === 'arch'
|
|
? createDormerArchShape(
|
|
winW - 2 * safeFt,
|
|
winH - 2 * safeFt,
|
|
Math.max(archHeight - safeFt, 0.01),
|
|
)
|
|
: createDormerRoundedShape(winW - 2 * safeFt, winH - 2 * safeFt, insetRadii)
|
|
const glassGeo = new THREE.ExtrudeGeometry(glassShape, {
|
|
depth: 0.008,
|
|
bevelEnabled: false,
|
|
curveSegments: 24,
|
|
})
|
|
glassGeo.translate(0, 0, -0.004)
|
|
glassPanes.push({ geo: glassGeo, pos: [0, 0, 0] })
|
|
} else {
|
|
frameBars.push({
|
|
geo: new THREE.BoxGeometry(winW, safeFt, fd),
|
|
pos: [0, hh - safeFt / 2, 0],
|
|
})
|
|
frameBars.push({
|
|
geo: new THREE.BoxGeometry(winW, safeFt, fd),
|
|
pos: [0, -hh + safeFt / 2, 0],
|
|
})
|
|
frameBars.push({
|
|
geo: new THREE.BoxGeometry(safeFt, innerH, fd),
|
|
pos: [-hw + safeFt / 2, 0, 0],
|
|
})
|
|
frameBars.push({
|
|
geo: new THREE.BoxGeometry(safeFt, innerH, fd),
|
|
pos: [hw - safeFt / 2, 0, 0],
|
|
})
|
|
|
|
const colDividerCount = cols - 1
|
|
const totalColDividerW = colDividerCount * safeDt
|
|
const paneAreaW = Math.max(0.01, innerW - totalColDividerW)
|
|
const paneW = paneAreaW / cols
|
|
|
|
for (let c = 1; c < cols; c++) {
|
|
const x = -innerW / 2 + c * paneW + (c - 0.5) * safeDt
|
|
frameBars.push({ geo: new THREE.BoxGeometry(safeDt, innerH, fd), pos: [x, 0, 0] })
|
|
}
|
|
|
|
const rowDividerCount = rows - 1
|
|
const totalRowDividerH = rowDividerCount * safeDt
|
|
const paneAreaH = Math.max(0.01, innerH - totalRowDividerH)
|
|
const paneH = paneAreaH / rows
|
|
|
|
for (let r = 1; r < rows; r++) {
|
|
const y = -innerH / 2 + r * paneH + (r - 0.5) * safeDt
|
|
frameBars.push({ geo: new THREE.BoxGeometry(innerW, safeDt, fd), pos: [0, y, 0] })
|
|
}
|
|
|
|
const glassW = Math.max(0.01, paneAreaW / cols)
|
|
const glassH = Math.max(0.01, paneAreaH / rows)
|
|
const glassGeo = new THREE.BoxGeometry(glassW, glassH, 0.008)
|
|
|
|
for (let c = 0; c < cols; c++) {
|
|
const cx = -innerW / 2 + paneAreaW / cols / 2 + c * (paneAreaW / cols + safeDt)
|
|
for (let r = 0; r < rows; r++) {
|
|
const cy = -innerH / 2 + paneAreaH / rows / 2 + r * (paneAreaH / rows + safeDt)
|
|
glassPanes.push({ geo: glassGeo, pos: [cx, cy, 0] })
|
|
}
|
|
}
|
|
}
|
|
|
|
return { frameBars, glassPanes }
|
|
}
|