fix(editor): harden editor interactions and WebGPU rendering
Fix editor bug sweep regressions, WebGPU CSG/material crashes, Shift snap bypass behavior, arrow handle drag projection, and the wall preview null guard covered by the Sentry follow-up PRs.
This commit is contained in:
@@ -1,26 +1,22 @@
|
||||
import type { RoofSegmentNode, RoofWallFaceId } from '@pascal-app/core'
|
||||
import type { DoorNode, RoofSegmentNode, WindowNode } from '@pascal-app/core'
|
||||
import { getRoofWallFaceFrame, roofFacePointToSegment } from '@pascal-app/core'
|
||||
import { buildOpeningCutoutGeometry, hasFlatOpeningCutoutBottom } from '@pascal-app/viewer'
|
||||
import * as THREE from 'three'
|
||||
|
||||
type RoofWallOpening = {
|
||||
roofSegmentId?: string
|
||||
roofFace?: RoofWallFaceId
|
||||
position: [number, number, number]
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
/**
|
||||
* CSG cut for a door / window hosted on a roof-segment wall face
|
||||
* (`capabilities.roofAccessory.buildCut`). A box through the wall
|
||||
* (`capabilities.roofAccessory.buildCut`). The cut goes through the wall
|
||||
* mid-plane, derived from the CURRENT host geometry (the opening stores
|
||||
* face-local coords), so the hole follows segment resizes for free.
|
||||
* Plain rectangles cut a box; shaped openings (arch / rounded /
|
||||
* frameless `opening` kind) reuse the wall pipeline's cutout profile so
|
||||
* roof-hosted holes match wall-hosted ones.
|
||||
*
|
||||
* Returns null for wall-hosted openings: their cut is handled by the
|
||||
* wall system's own cutout pipeline.
|
||||
*/
|
||||
export function buildRoofWallOpeningCut(
|
||||
node: RoofWallOpening,
|
||||
node: DoorNode | WindowNode,
|
||||
hostSegment: RoofSegmentNode,
|
||||
): THREE.BufferGeometry | null {
|
||||
if (!node.roofSegmentId || !node.roofFace) return null
|
||||
@@ -32,8 +28,10 @@ export function buildRoofWallOpeningCut(
|
||||
|
||||
// A door's cut bottom is coplanar with the wall brush base — extend it
|
||||
// slightly downward so three-bvh-csg never has to clip coplanar faces.
|
||||
// Only a flat bottom chord may extend; a rounded bottom is never
|
||||
// coplanar and shifting it would distort the profile.
|
||||
const bottom = node.position[1] - node.height / 2
|
||||
const bottomPad = bottom < 0.005 ? 0.02 : 0
|
||||
const bottomPad = bottom < 0.005 && hasFlatOpeningCutoutBottom(node) ? 0.02 : 0
|
||||
|
||||
const center = roofFacePointToSegment(hostSegment, node.roofFace, [
|
||||
node.position[0],
|
||||
@@ -42,9 +40,35 @@ export function buildRoofWallOpeningCut(
|
||||
])
|
||||
const { yaw } = getRoofWallFaceFrame(hostSegment, node.roofFace)
|
||||
|
||||
const geo = new THREE.BoxGeometry(node.width, node.height + bottomPad, depth)
|
||||
geo.translate(0, -bottomPad / 2, 0)
|
||||
const geo = buildCutGeometry(node, wallThickness, depth, bottomPad)
|
||||
geo.rotateY(yaw)
|
||||
geo.translate(center[0], center[1], center[2])
|
||||
return geo
|
||||
}
|
||||
|
||||
function buildCutGeometry(
|
||||
node: DoorNode | WindowNode,
|
||||
wallThickness: number,
|
||||
depth: number,
|
||||
bottomPad: number,
|
||||
): THREE.BufferGeometry {
|
||||
const shaped =
|
||||
node.openingKind === 'opening' ||
|
||||
node.openingShape === 'arch' ||
|
||||
node.openingShape === 'rounded'
|
||||
|
||||
if (!shaped) {
|
||||
const geo = new THREE.BoxGeometry(node.width, node.height + bottomPad, depth)
|
||||
geo.translate(0, -bottomPad / 2, 0)
|
||||
return geo
|
||||
}
|
||||
|
||||
const halfWidth = node.width / 2
|
||||
const halfHeight = node.height / 2
|
||||
return buildOpeningCutoutGeometry(
|
||||
node,
|
||||
{ left: -halfWidth, right: halfWidth, bottom: -halfHeight - bottomPad, top: halfHeight },
|
||||
depth,
|
||||
wallThickness,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user