Fix floorplan labels and door/fence geometry
This commit is contained in:
@@ -109,6 +109,25 @@ function addShape(
|
||||
parent.add(mesh)
|
||||
}
|
||||
|
||||
function addShapeAt(
|
||||
parent: THREE.Object3D,
|
||||
material: THREE.Material,
|
||||
shape: THREE.Shape,
|
||||
depth: number,
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
) {
|
||||
const geometry = new THREE.ExtrudeGeometry(shape, {
|
||||
depth,
|
||||
bevelEnabled: false,
|
||||
curveSegments: 24,
|
||||
})
|
||||
geometry.translate(x, y, z - depth / 2)
|
||||
const mesh = new THREE.Mesh(geometry, material)
|
||||
parent.add(mesh)
|
||||
}
|
||||
|
||||
function getClampedArchHeight(width: number, height: number, archHeight: number | undefined) {
|
||||
return Math.min(Math.max(archHeight ?? width / 2, 0.01), Math.max(height, 0.01))
|
||||
}
|
||||
@@ -145,6 +164,51 @@ function getArchBoundaryY(x: number, halfWidth: number, springY: number, archHei
|
||||
return springY + archHeight * Math.sqrt(Math.max(1 - t * t, 0))
|
||||
}
|
||||
|
||||
function getOneSidedArchBoundaryYAtX(
|
||||
x: number,
|
||||
left: number,
|
||||
right: number,
|
||||
top: number,
|
||||
archHeight: number,
|
||||
curvedSide: 'left' | 'right',
|
||||
) {
|
||||
const width = right - left
|
||||
if (width <= 1e-6) return top
|
||||
const clampedArchHeight = getClampedArchHeight(width, Number.MAX_SAFE_INTEGER, archHeight)
|
||||
const springY = top - clampedArchHeight
|
||||
const distanceFromApex =
|
||||
curvedSide === 'left'
|
||||
? Math.max(0, Math.min((right - x) / width, 1))
|
||||
: Math.max(0, Math.min((x - left) / width, 1))
|
||||
return (
|
||||
springY + clampedArchHeight * Math.sqrt(Math.max(1 - distanceFromApex * distanceFromApex, 0))
|
||||
)
|
||||
}
|
||||
|
||||
function getRoundedBoundaryYAtX(
|
||||
x: number,
|
||||
left: number,
|
||||
right: number,
|
||||
top: number,
|
||||
radii: TopCornerRadii,
|
||||
) {
|
||||
if (radii.topLeft > 1e-6 && x < left + radii.topLeft) {
|
||||
const centerX = left + radii.topLeft
|
||||
const centerY = top - radii.topLeft
|
||||
const dx = x - centerX
|
||||
return centerY + Math.sqrt(Math.max(radii.topLeft * radii.topLeft - dx * dx, 0))
|
||||
}
|
||||
|
||||
if (radii.topRight > 1e-6 && x > right - radii.topRight) {
|
||||
const centerX = right - radii.topRight
|
||||
const centerY = top - radii.topRight
|
||||
const dx = x - centerX
|
||||
return centerY + Math.sqrt(Math.max(radii.topRight * radii.topRight - dx * dx, 0))
|
||||
}
|
||||
|
||||
return top
|
||||
}
|
||||
|
||||
function createArchBandShape(
|
||||
width: number,
|
||||
outerSpringY: number,
|
||||
@@ -358,6 +422,174 @@ function createRoundedLeafFrameShape(
|
||||
return outer
|
||||
}
|
||||
|
||||
function createRoundedClippedLeafFrameShape(
|
||||
left: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
top: number,
|
||||
fullLeft: number,
|
||||
fullRight: number,
|
||||
radii: TopCornerRadii,
|
||||
insetX: number,
|
||||
insetY: number,
|
||||
) {
|
||||
const outerRadii = normalizeTopCornerRadii(radii, fullRight - fullLeft, top - bottom)
|
||||
const outer = createTopClippedRectShape(left, right, bottom, top, (x) =>
|
||||
getRoundedBoundaryYAtX(x, fullLeft, fullRight, top, {
|
||||
topLeft: outerRadii.topLeft,
|
||||
topRight: outerRadii.topRight,
|
||||
bottomLeft: 0,
|
||||
bottomRight: 0,
|
||||
}),
|
||||
)
|
||||
|
||||
if (!outer) return null
|
||||
|
||||
const innerLeft = left + insetX
|
||||
const innerRight = right - insetX
|
||||
const innerBottom = bottom + insetY
|
||||
const innerTop = top - insetY
|
||||
|
||||
if (innerRight <= innerLeft + 0.01 || innerTop <= innerBottom + 0.01) return outer
|
||||
|
||||
const innerFullLeft = fullLeft + insetX
|
||||
const innerFullRight = fullRight - insetX
|
||||
const innerRadii = normalizeTopCornerRadii(
|
||||
{
|
||||
topLeft: Math.max(outerRadii.topLeft - Math.max(insetX, insetY), 0),
|
||||
topRight: Math.max(outerRadii.topRight - Math.max(insetX, insetY), 0),
|
||||
},
|
||||
innerFullRight - innerFullLeft,
|
||||
innerTop - innerBottom,
|
||||
)
|
||||
const holeShape = createTopClippedRectShape(innerLeft, innerRight, innerBottom, innerTop, (x) =>
|
||||
getRoundedBoundaryYAtX(x, innerFullLeft, innerFullRight, innerTop, {
|
||||
topLeft: innerRadii.topLeft,
|
||||
topRight: innerRadii.topRight,
|
||||
bottomLeft: 0,
|
||||
bottomRight: 0,
|
||||
}),
|
||||
)
|
||||
|
||||
if (holeShape) outer.holes.push(shapeToReversedPath(holeShape))
|
||||
|
||||
return outer
|
||||
}
|
||||
|
||||
function createArchedLeafFrameShape(
|
||||
width: number,
|
||||
bottom: number,
|
||||
top: number,
|
||||
archHeight: number,
|
||||
insetX: number,
|
||||
insetY: number,
|
||||
) {
|
||||
const halfWidth = width / 2
|
||||
const outer = createArchShape(-halfWidth, halfWidth, bottom, top, archHeight)
|
||||
const innerLeft = -halfWidth + insetX
|
||||
const innerRight = halfWidth - insetX
|
||||
const innerBottom = bottom + insetY
|
||||
const innerTop = top - insetY
|
||||
|
||||
if (innerRight <= innerLeft + 0.01 || innerTop <= innerBottom + 0.01) return outer
|
||||
|
||||
const innerArchHeight = getClampedArchHeight(
|
||||
innerRight - innerLeft,
|
||||
innerTop - innerBottom,
|
||||
Math.max(archHeight - insetY, 0.01),
|
||||
)
|
||||
outer.holes.push(
|
||||
shapeToReversedPath(
|
||||
createArchShape(innerLeft, innerRight, innerBottom, innerTop, innerArchHeight),
|
||||
),
|
||||
)
|
||||
|
||||
return outer
|
||||
}
|
||||
|
||||
function createArchedClippedLeafFrameShape(
|
||||
left: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
top: number,
|
||||
fullLeft: number,
|
||||
fullRight: number,
|
||||
archHeight: number,
|
||||
insetX: number,
|
||||
insetY: number,
|
||||
) {
|
||||
const fullCenterX = (fullLeft + fullRight) / 2
|
||||
const fullHalfWidth = (fullRight - fullLeft) / 2
|
||||
const springY = top - archHeight
|
||||
const outer = createTopClippedRectShape(left, right, bottom, top, (x) =>
|
||||
getArchBoundaryY(x - fullCenterX, fullHalfWidth, springY, archHeight),
|
||||
)
|
||||
|
||||
if (!outer) return null
|
||||
|
||||
const innerLeft = left + insetX
|
||||
const innerRight = right - insetX
|
||||
const innerBottom = bottom + insetY
|
||||
const innerTop = top - insetY
|
||||
|
||||
if (innerRight <= innerLeft + 0.01 || innerTop <= innerBottom + 0.01) return outer
|
||||
|
||||
const innerFullLeft = fullLeft + insetX
|
||||
const innerFullRight = fullRight - insetX
|
||||
const innerArchHeight = getClampedArchHeight(
|
||||
innerFullRight - innerFullLeft,
|
||||
innerTop - innerBottom,
|
||||
Math.max(archHeight - insetY, 0.01),
|
||||
)
|
||||
const innerFullCenterX = (innerFullLeft + innerFullRight) / 2
|
||||
const innerFullHalfWidth = (innerFullRight - innerFullLeft) / 2
|
||||
const innerSpringY = innerTop - innerArchHeight
|
||||
const holeShape = createTopClippedRectShape(innerLeft, innerRight, innerBottom, innerTop, (x) =>
|
||||
getArchBoundaryY(x - innerFullCenterX, innerFullHalfWidth, innerSpringY, innerArchHeight),
|
||||
)
|
||||
|
||||
if (holeShape) outer.holes.push(shapeToReversedPath(holeShape))
|
||||
|
||||
return outer
|
||||
}
|
||||
|
||||
function createOneSidedArchLeafFrameShape(
|
||||
left: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
top: number,
|
||||
archHeight: number,
|
||||
insetX: number,
|
||||
insetY: number,
|
||||
curvedSide: 'left' | 'right',
|
||||
) {
|
||||
const outer = createTopClippedRectShape(left, right, bottom, top, (x) =>
|
||||
getOneSidedArchBoundaryYAtX(x, left, right, top, archHeight, curvedSide),
|
||||
)
|
||||
|
||||
if (!outer) return null
|
||||
|
||||
const innerLeft = left + insetX
|
||||
const innerRight = right - insetX
|
||||
const innerBottom = bottom + insetY
|
||||
const innerTop = top - insetY
|
||||
|
||||
if (innerRight <= innerLeft + 0.01 || innerTop <= innerBottom + 0.01) return outer
|
||||
|
||||
const innerArchHeight = getClampedArchHeight(
|
||||
innerRight - innerLeft,
|
||||
innerTop - innerBottom,
|
||||
Math.max(archHeight - insetY, 0.01),
|
||||
)
|
||||
const holeShape = createTopClippedRectShape(innerLeft, innerRight, innerBottom, innerTop, (x) =>
|
||||
getOneSidedArchBoundaryYAtX(x, innerLeft, innerRight, innerTop, innerArchHeight, curvedSide),
|
||||
)
|
||||
|
||||
if (holeShape) outer.holes.push(shapeToReversedPath(holeShape))
|
||||
|
||||
return outer
|
||||
}
|
||||
|
||||
function createTopClippedRectShape(
|
||||
left: number,
|
||||
right: number,
|
||||
@@ -395,6 +627,7 @@ function disposeObject(object: THREE.Object3D) {
|
||||
|
||||
function addLeafSegmentContent({
|
||||
addLeafBox,
|
||||
addLeafShape,
|
||||
leafWidth,
|
||||
leafHeight,
|
||||
leafCenterX,
|
||||
@@ -403,6 +636,11 @@ function addLeafSegmentContent({
|
||||
segments,
|
||||
contentPadding,
|
||||
keepFrameWhenEmpty = false,
|
||||
renderPerimeterFrame = true,
|
||||
openingShape = 'rectangle',
|
||||
openingTopRadii,
|
||||
archHeight,
|
||||
archOuterSide,
|
||||
}: {
|
||||
addLeafBox: (
|
||||
material: THREE.Material,
|
||||
@@ -413,6 +651,7 @@ function addLeafSegmentContent({
|
||||
y: number,
|
||||
z: number,
|
||||
) => void
|
||||
addLeafShape?: (material: THREE.Material, shape: THREE.Shape, depth: number) => void
|
||||
leafWidth: number
|
||||
leafHeight: number
|
||||
leafCenterX: number
|
||||
@@ -421,12 +660,17 @@ function addLeafSegmentContent({
|
||||
segments: DoorNode['segments']
|
||||
contentPadding: DoorNode['contentPadding']
|
||||
keepFrameWhenEmpty?: boolean
|
||||
renderPerimeterFrame?: boolean
|
||||
openingShape?: DoorNode['openingShape']
|
||||
openingTopRadii?: TopCornerRadii
|
||||
archHeight?: number
|
||||
archOuterSide?: 'left' | 'right'
|
||||
}) {
|
||||
const hasLeafContent = segments.some((seg) => seg.type !== 'empty')
|
||||
const shouldRenderFrame = hasLeafContent || keepFrameWhenEmpty
|
||||
const cpX = contentPadding[0]
|
||||
const cpY = contentPadding[1]
|
||||
if (shouldRenderFrame && cpY > 0) {
|
||||
if (renderPerimeterFrame && shouldRenderFrame && cpY > 0) {
|
||||
addLeafBox(
|
||||
baseMaterial,
|
||||
leafWidth,
|
||||
@@ -446,7 +690,7 @@ function addLeafSegmentContent({
|
||||
0,
|
||||
)
|
||||
}
|
||||
if (shouldRenderFrame && cpX > 0) {
|
||||
if (renderPerimeterFrame && shouldRenderFrame && cpX > 0) {
|
||||
const innerH = leafHeight - 2 * cpY
|
||||
addLeafBox(
|
||||
baseMaterial,
|
||||
@@ -474,9 +718,46 @@ function addLeafSegmentContent({
|
||||
const contentTop = leafCenterY + contentH / 2
|
||||
|
||||
let segY = contentTop
|
||||
const leafLeft = leafCenterX - leafWidth / 2
|
||||
const leafRight = leafCenterX + leafWidth / 2
|
||||
const leafTop = leafCenterY + leafHeight / 2
|
||||
const hasShapedTop = openingShape === 'rounded' || openingShape === 'arch'
|
||||
const clampedLeafArchHeight =
|
||||
openingShape === 'arch' ? getClampedArchHeight(leafWidth, leafHeight, archHeight) : 0
|
||||
const leafSpringY = leafTop - clampedLeafArchHeight
|
||||
const topBoundaryAtX = (x: number) => {
|
||||
if (openingShape === 'rounded' && openingTopRadii) {
|
||||
return getRoundedBoundaryYAtX(x, leafLeft, leafRight, leafTop, {
|
||||
topLeft: openingTopRadii.topLeft,
|
||||
topRight: openingTopRadii.topRight,
|
||||
bottomLeft: 0,
|
||||
bottomRight: 0,
|
||||
})
|
||||
}
|
||||
|
||||
if (openingShape === 'arch') {
|
||||
if (archOuterSide) {
|
||||
return getOneSidedArchBoundaryYAtX(
|
||||
x,
|
||||
leafLeft,
|
||||
leafRight,
|
||||
leafTop,
|
||||
archHeight,
|
||||
archOuterSide,
|
||||
)
|
||||
}
|
||||
|
||||
return getArchBoundaryY(x - leafCenterX, leafWidth / 2, leafSpringY, clampedLeafArchHeight)
|
||||
}
|
||||
|
||||
return leafTop
|
||||
}
|
||||
|
||||
for (const seg of segments) {
|
||||
const segH = (seg.heightRatio / totalRatio) * contentH
|
||||
const segCenterY = segY - segH / 2
|
||||
const segTop = segY
|
||||
const segBottom = segY - segH
|
||||
const numCols = seg.columnRatios.length
|
||||
const colSum = seg.columnRatios.reduce((a, b) => a + b, 0)
|
||||
const usableW = contentW - (numCols - 1) * seg.dividerThickness
|
||||
@@ -494,15 +775,32 @@ function addLeafSegmentContent({
|
||||
cx = leafCenterX - contentW / 2
|
||||
for (let c = 0; c < numCols - 1; c++) {
|
||||
cx += colWidths[c]!
|
||||
addLeafBox(
|
||||
baseMaterial,
|
||||
seg.dividerThickness,
|
||||
segH,
|
||||
leafDepth + 0.001,
|
||||
cx + seg.dividerThickness / 2,
|
||||
segCenterY,
|
||||
0,
|
||||
)
|
||||
const dividerLeft = cx
|
||||
const dividerRight = cx + seg.dividerThickness
|
||||
const dividerShape =
|
||||
hasShapedTop && addLeafShape
|
||||
? createTopClippedRectShape(
|
||||
dividerLeft,
|
||||
dividerRight,
|
||||
segBottom,
|
||||
segTop,
|
||||
topBoundaryAtX,
|
||||
)
|
||||
: null
|
||||
|
||||
if (dividerShape && addLeafShape) {
|
||||
addLeafShape(baseMaterial, dividerShape, leafDepth + 0.001)
|
||||
} else {
|
||||
addLeafBox(
|
||||
baseMaterial,
|
||||
seg.dividerThickness,
|
||||
segH,
|
||||
leafDepth + 0.001,
|
||||
cx + seg.dividerThickness / 2,
|
||||
segCenterY,
|
||||
0,
|
||||
)
|
||||
}
|
||||
cx += seg.dividerThickness
|
||||
}
|
||||
}
|
||||
@@ -513,15 +811,68 @@ function addLeafSegmentContent({
|
||||
|
||||
if (seg.type === 'glass') {
|
||||
const glassDepth = Math.max(0.004, leafDepth * 0.15)
|
||||
addLeafBox(glassMaterial, colW, segH, glassDepth, colX, segCenterY, 0)
|
||||
const segmentLeft = colX - colW / 2
|
||||
const segmentRight = colX + colW / 2
|
||||
const glassShape =
|
||||
hasShapedTop && addLeafShape
|
||||
? createTopClippedRectShape(
|
||||
segmentLeft,
|
||||
segmentRight,
|
||||
segBottom,
|
||||
segTop,
|
||||
topBoundaryAtX,
|
||||
)
|
||||
: null
|
||||
|
||||
if (glassShape && addLeafShape) {
|
||||
addLeafShape(glassMaterial, glassShape, glassDepth)
|
||||
} else {
|
||||
addLeafBox(glassMaterial, colW, segH, glassDepth, colX, segCenterY, 0)
|
||||
}
|
||||
} else if (seg.type === 'panel') {
|
||||
addLeafBox(baseMaterial, colW, segH, leafDepth, colX, segCenterY, 0)
|
||||
const segmentLeft = colX - colW / 2
|
||||
const segmentRight = colX + colW / 2
|
||||
const outerPanelShape =
|
||||
hasShapedTop && addLeafShape
|
||||
? createTopClippedRectShape(
|
||||
segmentLeft,
|
||||
segmentRight,
|
||||
segBottom,
|
||||
segTop,
|
||||
topBoundaryAtX,
|
||||
)
|
||||
: null
|
||||
|
||||
if (outerPanelShape && addLeafShape) {
|
||||
addLeafShape(baseMaterial, outerPanelShape, leafDepth)
|
||||
} else {
|
||||
addLeafBox(baseMaterial, colW, segH, leafDepth, colX, segCenterY, 0)
|
||||
}
|
||||
const panelW = colW - 2 * seg.panelInset
|
||||
const panelH = segH - 2 * seg.panelInset
|
||||
if (panelW > 0.01 && panelH > 0.01) {
|
||||
const effectiveDepth = Math.abs(seg.panelDepth) < 0.002 ? 0.005 : Math.abs(seg.panelDepth)
|
||||
const panelZ = leafDepth / 2 + effectiveDepth / 2
|
||||
addLeafBox(baseMaterial, panelW, panelH, effectiveDepth, colX, segCenterY, panelZ)
|
||||
const insetLeft = colX - panelW / 2
|
||||
const insetRight = colX + panelW / 2
|
||||
const insetTop = segTop - seg.panelInset
|
||||
const insetBottom = segBottom + seg.panelInset
|
||||
const innerPanelShape =
|
||||
hasShapedTop && addLeafShape
|
||||
? createTopClippedRectShape(
|
||||
insetLeft,
|
||||
insetRight,
|
||||
insetBottom,
|
||||
insetTop,
|
||||
topBoundaryAtX,
|
||||
)
|
||||
: null
|
||||
|
||||
if (innerPanelShape && addLeafShape) {
|
||||
addLeafShape(baseMaterial, innerPanelShape, effectiveDepth)
|
||||
} else {
|
||||
addLeafBox(baseMaterial, panelW, panelH, effectiveDepth, colX, segCenterY, panelZ)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -551,6 +902,12 @@ function addDoorLeaf(
|
||||
panicBar,
|
||||
panicBarHeight,
|
||||
doorHeight,
|
||||
openingShape,
|
||||
openingTopRadii,
|
||||
archHeight,
|
||||
roundedBoundary,
|
||||
archedBoundary,
|
||||
archOuterSide,
|
||||
}: {
|
||||
leafWidth: number
|
||||
leafHeight: number
|
||||
@@ -570,6 +927,20 @@ function addDoorLeaf(
|
||||
panicBar: boolean
|
||||
panicBarHeight: number
|
||||
doorHeight: number
|
||||
openingShape: DoorNode['openingShape']
|
||||
openingTopRadii: TopCornerRadii
|
||||
archHeight: number
|
||||
roundedBoundary?: {
|
||||
fullLeft: number
|
||||
fullRight: number
|
||||
radii: TopCornerRadii
|
||||
}
|
||||
archedBoundary?: {
|
||||
fullLeft: number
|
||||
fullRight: number
|
||||
archHeight: number
|
||||
}
|
||||
archOuterSide?: 'left' | 'right'
|
||||
},
|
||||
) {
|
||||
const hasLeafContent = segments.some((seg) => seg.type !== 'empty')
|
||||
@@ -587,9 +958,90 @@ function addDoorLeaf(
|
||||
y: number,
|
||||
z: number,
|
||||
) => addBox(leafGroup, material, w, h, d, x - hingeX, y, z)
|
||||
const addLeafShape = (material: THREE.Material, shape: THREE.Shape, depth: number) =>
|
||||
addShapeAt(leafGroup, material, shape, depth, -hingeX, 0, 0)
|
||||
|
||||
const localLeafCenterX = leafCenterX - hingeX
|
||||
const leafBottom = leafCenterY - leafHeight / 2
|
||||
const leafTop = leafCenterY + leafHeight / 2
|
||||
const usesShapedLeafFrame = openingShape === 'rounded' || openingShape === 'arch'
|
||||
|
||||
if (usesShapedLeafFrame && hasLeafContent) {
|
||||
if (openingShape === 'rounded') {
|
||||
const roundedLeafShape = roundedBoundary
|
||||
? createRoundedClippedLeafFrameShape(
|
||||
leafCenterX - leafWidth / 2,
|
||||
leafCenterX + leafWidth / 2,
|
||||
leafBottom,
|
||||
leafTop,
|
||||
roundedBoundary.fullLeft,
|
||||
roundedBoundary.fullRight,
|
||||
roundedBoundary.radii,
|
||||
contentPadding[0],
|
||||
contentPadding[1],
|
||||
)
|
||||
: createRoundedLeafFrameShape(
|
||||
leafWidth,
|
||||
leafBottom,
|
||||
leafTop,
|
||||
openingTopRadii,
|
||||
contentPadding[0],
|
||||
contentPadding[1],
|
||||
)
|
||||
|
||||
if (roundedLeafShape) {
|
||||
if (roundedBoundary) {
|
||||
addLeafShape(baseMaterial, roundedLeafShape, leafDepth)
|
||||
} else {
|
||||
addShapeAt(leafGroup, baseMaterial, roundedLeafShape, leafDepth, localLeafCenterX, 0, 0)
|
||||
}
|
||||
}
|
||||
} else if (openingShape === 'arch') {
|
||||
const archedLeafShape = archOuterSide
|
||||
? createOneSidedArchLeafFrameShape(
|
||||
leafCenterX - leafWidth / 2,
|
||||
leafCenterX + leafWidth / 2,
|
||||
leafBottom,
|
||||
leafTop,
|
||||
archHeight,
|
||||
contentPadding[0],
|
||||
contentPadding[1],
|
||||
archOuterSide,
|
||||
)
|
||||
: archedBoundary
|
||||
? createArchedClippedLeafFrameShape(
|
||||
leafCenterX - leafWidth / 2,
|
||||
leafCenterX + leafWidth / 2,
|
||||
leafBottom,
|
||||
leafTop,
|
||||
archedBoundary.fullLeft,
|
||||
archedBoundary.fullRight,
|
||||
archedBoundary.archHeight,
|
||||
contentPadding[0],
|
||||
contentPadding[1],
|
||||
)
|
||||
: createArchedLeafFrameShape(
|
||||
leafWidth,
|
||||
leafBottom,
|
||||
leafTop,
|
||||
getClampedArchHeight(leafWidth, leafHeight, archHeight),
|
||||
contentPadding[0],
|
||||
contentPadding[1],
|
||||
)
|
||||
|
||||
if (archedLeafShape) {
|
||||
if (archOuterSide || archedBoundary) {
|
||||
addLeafShape(baseMaterial, archedLeafShape, leafDepth)
|
||||
} else {
|
||||
addShapeAt(leafGroup, baseMaterial, archedLeafShape, leafDepth, localLeafCenterX, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addLeafSegmentContent({
|
||||
addLeafBox,
|
||||
addLeafShape,
|
||||
leafWidth,
|
||||
leafHeight,
|
||||
leafCenterX,
|
||||
@@ -597,6 +1049,11 @@ function addDoorLeaf(
|
||||
leafDepth,
|
||||
segments,
|
||||
contentPadding,
|
||||
renderPerimeterFrame: !usesShapedLeafFrame,
|
||||
openingShape,
|
||||
openingTopRadii,
|
||||
archHeight,
|
||||
archOuterSide,
|
||||
})
|
||||
|
||||
if (hasLeafContent && handle) {
|
||||
@@ -640,8 +1097,6 @@ function addDoorLeaf(
|
||||
const hingeH = 0.1
|
||||
const hingeW = 0.024
|
||||
const hingeD = leafDepth + 0.016
|
||||
const leafBottom = leafCenterY - leafHeight / 2
|
||||
const leafTop = leafCenterY + leafHeight / 2
|
||||
addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeMarkerX, leafBottom + 0.25, 0)
|
||||
addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeMarkerX, (leafBottom + leafTop) / 2, 0)
|
||||
addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeMarkerX, leafTop - 0.25, 0)
|
||||
@@ -1401,6 +1856,15 @@ function addGarageTiltupDoor(
|
||||
addBox(mesh, revealMaterial, insideWidth, 0.026, Math.max(frameDepth * 0.4, 0.035), 0, hingeY, 0)
|
||||
}
|
||||
|
||||
function getEffectiveOpeningShape(node: DoorNode): DoorNode['openingShape'] {
|
||||
return node.doorType === 'folding' ||
|
||||
node.doorType === 'pocket' ||
|
||||
node.doorType === 'barn' ||
|
||||
node.doorType === 'sliding'
|
||||
? 'rectangle'
|
||||
: (node.openingShape ?? 'rectangle')
|
||||
}
|
||||
|
||||
function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
|
||||
// Root mesh is an invisible hitbox; all visuals live in child meshes
|
||||
mesh.geometry.dispose()
|
||||
@@ -1422,7 +1886,7 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
|
||||
width,
|
||||
height,
|
||||
openingKind,
|
||||
openingShape,
|
||||
openingShape: rawOpeningShape,
|
||||
frameThickness,
|
||||
frameDepth,
|
||||
threshold,
|
||||
@@ -1444,6 +1908,7 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
|
||||
slideDirection = 'left',
|
||||
garagePanelCount = 4,
|
||||
} = node
|
||||
const openingShape = getEffectiveOpeningShape(node) ?? rawOpeningShape
|
||||
const runtimeDoorState = useInteractive.getState().doors[node.id]
|
||||
const swingAngle = runtimeDoorState?.swingAngle ?? nodeSwingAngle
|
||||
const operationState = runtimeDoorState?.operationState ?? nodeOperationState
|
||||
@@ -1662,6 +2127,15 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
|
||||
})
|
||||
} else if (doorType === 'double' || doorType === 'french') {
|
||||
const doubleLeafW = insideWidth / 2
|
||||
const fullLeafTopRadii = getDoorTopRadii(node, insideWidth, leafH)
|
||||
const roundedBoundary =
|
||||
openingShape === 'rounded'
|
||||
? {
|
||||
fullLeft: -insideWidth / 2,
|
||||
fullRight: insideWidth / 2,
|
||||
radii: fullLeafTopRadii,
|
||||
}
|
||||
: undefined
|
||||
addDoorLeaf(mesh, {
|
||||
leafWidth: doubleLeafW,
|
||||
leafHeight: leafH,
|
||||
@@ -1681,6 +2155,14 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
|
||||
panicBar,
|
||||
panicBarHeight,
|
||||
doorHeight: height,
|
||||
openingShape,
|
||||
openingTopRadii:
|
||||
openingShape === 'rounded'
|
||||
? { topLeft: fullLeafTopRadii.topLeft, topRight: 0 }
|
||||
: fullLeafTopRadii,
|
||||
archHeight: node.archHeight ?? 0.45,
|
||||
roundedBoundary,
|
||||
archOuterSide: openingShape === 'arch' ? 'left' : undefined,
|
||||
})
|
||||
addDoorLeaf(mesh, {
|
||||
leafWidth: doubleLeafW,
|
||||
@@ -1701,6 +2183,14 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
|
||||
panicBar,
|
||||
panicBarHeight,
|
||||
doorHeight: height,
|
||||
openingShape,
|
||||
openingTopRadii:
|
||||
openingShape === 'rounded'
|
||||
? { topLeft: 0, topRight: fullLeafTopRadii.topRight }
|
||||
: fullLeafTopRadii,
|
||||
archHeight: node.archHeight ?? 0.45,
|
||||
roundedBoundary,
|
||||
archOuterSide: openingShape === 'arch' ? 'right' : undefined,
|
||||
})
|
||||
} else {
|
||||
const hingeX = hingesSide === 'right' ? insideWidth / 2 : -insideWidth / 2
|
||||
@@ -1724,6 +2214,9 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
|
||||
panicBar,
|
||||
panicBarHeight,
|
||||
doorHeight: height,
|
||||
openingShape,
|
||||
openingTopRadii: getDoorTopRadii(node, insideWidth, leafH),
|
||||
archHeight: node.archHeight ?? 0.45,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1739,7 +2232,8 @@ function syncDoorCutout(node: DoorNode, mesh: THREE.Mesh) {
|
||||
mesh.add(cutout)
|
||||
}
|
||||
cutout.geometry.dispose()
|
||||
if (node.openingShape === 'arch') {
|
||||
const openingShape = getEffectiveOpeningShape(node)
|
||||
if (openingShape === 'arch') {
|
||||
cutout.geometry = new THREE.ExtrudeGeometry(
|
||||
createArchShape(
|
||||
-node.width / 2,
|
||||
@@ -1755,7 +2249,7 @@ function syncDoorCutout(node: DoorNode, mesh: THREE.Mesh) {
|
||||
},
|
||||
)
|
||||
cutout.geometry.translate(0, 0, -0.5)
|
||||
} else if (node.openingShape === 'rounded') {
|
||||
} else if (openingShape === 'rounded') {
|
||||
cutout.geometry = new THREE.ExtrudeGeometry(
|
||||
createRoundedTopShape(
|
||||
-node.width / 2,
|
||||
|
||||
Reference in New Issue
Block a user