Improve roof node editing

This commit is contained in:
Aymeric Rabot
2026-06-07 00:24:15 -04:00
parent b7d6038348
commit f84910848b
11 changed files with 216 additions and 72 deletions
+3 -1
View File
@@ -316,7 +316,9 @@ export type TapActionHandle<N = any> = {
* the hit into the node's parent-local frame, and reports the new local XZ
* (optionally grid-snapped via `snapExtents`) to `apply`. Press-drag-release
* with the same live-override → commit-on-release flow as the resize / rotate
* handles. Rendered as a 4-way cross of double-headed arrows.
* handles. Rendered as a 4-way cross of double-headed arrows. Pure translation
* does not require geometry dirtying; renderers consume the live position
* override directly.
*/
export type TranslateHandle<N = any> = {
kind: 'translate'
+1
View File
@@ -101,6 +101,7 @@ export {
getActiveRoofHeight,
getEffectiveSegmentSurfaceMaterial,
getPitchFromActiveRoofHeight,
getRoofSegmentSurfaceY,
getSegmentSlopeFrame,
hasSegmentMaterialOverride,
ROOF_SHAPE_DEFAULTS,
+36 -1
View File
@@ -181,7 +181,6 @@ function getPrimarySlopeRun(input: PitchInputs & ShapeRatios): number {
return min * input.mansardSteepWidthRatio
case 'dutch':
return min * input.dutchHipWidthRatio
case 'hip':
default:
return min / 2
}
@@ -253,6 +252,42 @@ export function getActiveRoofHeight(node: Parameters<typeof getSegmentSlopeFrame
return getSegmentSlopeFrame(node).activeRh
}
/** Segment-local surface height used by roof accessory placement and hit disambiguation. */
export function getRoofSegmentSurfaceY(
node: Pick<RoofSegmentNode, 'roofType' | 'width' | 'depth' | 'wallHeight'> &
Parameters<typeof getSegmentSlopeFrame>[0],
localX: number,
localZ: number,
): number {
const activeRh = getActiveRoofHeight(node)
const peakY = node.wallHeight + activeRh
if (activeRh === 0) return node.wallHeight
if (
node.roofType === 'gable' ||
node.roofType === 'gambrel' ||
node.roofType === 'mansard' ||
node.roofType === 'dutch'
) {
const t = node.depth > 0 ? Math.abs(localZ) / (node.depth / 2) : 0
return peakY - t * activeRh
}
if (node.roofType === 'shed') {
const t = (localZ + node.depth / 2) / (node.depth || 1)
return peakY - t * activeRh
}
if (node.roofType === 'hip') {
const fx = node.width > 0 ? Math.abs(localX) / (node.width / 2) : 0
const fz = node.depth > 0 ? Math.abs(localZ) / (node.depth / 2) : 0
return peakY - Math.max(fx, fz) * activeRh
}
const t = node.depth > 0 ? Math.abs(localZ) / (node.depth / 2) : 0
return peakY - t * activeRh
}
/**
* Inverse of `getActiveRoofHeight` — recover the pitch a legacy
* `roofHeight` value would correspond to. Used by the scene migration.