Adjustments pass: shadow bias, polygon editor UX, space-detection load fix (#502)
* fix(viewer): tune shadow biases to stop acne without detaching shadows normalBias 0.02 was too small a texel offset for the building-fit 1024 shadow map and brought back self-shadowing acne. Settle on normalBias 0.08 (0.07 and below acnes, 0.1 reads detached) plus depth bias -0.0005 to suppress the residual acne that a normal bias alone couldn't clear. Also adds a `?debug=shadowcamera` diagnostic that draws a CameraHelper for each shadow camera so the fitted frustum can be inspected while tuning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(editor): polygon editor handle UX — focus, cursors, visibility - vertex/midpoint cylinders ignore scene depth like the edge arrows so they stay visible through walls and slabs; vertex radius 0.1 -> 0.08, midpoint 0.06 -> 0.05, midpoints use the brighter arrow shade at rest - during any drag only the active handle stays mounted (other arrows, vertices, edge bars and the cross disappear) so the gesture reads clearly - handles set pointer cursors: move for vertices/midpoints/cross, and a screen-space direction-aware resize cursor for the edge arrows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(editor): keep ceiling affordances quiet during any interaction The ceiling corner brackets' hit boxes caught drag-time hover (spatial events keep firing during host drags by design), set hoveredId to the ceiling and flashed the ceiling grid mid-gesture — e.g. while dragging a slab polygon vertex. Unmount the brackets while ANY interaction scope is active, and gate CeilingSystem's hover-driven grid reveal on idle scope + no inputDragging as a second layer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(core): stop room detection resurrecting deleted slabs on load Two holes in the wall-driven auto slab sync: - initSpaceDetectionSync started with an empty baseline, so scene hydration (one atomic setScene) read as 'every wall changed' and ran a full detection pass on every load, recreating auto slabs the user had deleted. Seed the baseline from the store at init and treat a level's first snapshot as baseline — detection now only reacts to in-session wall edits. - matchesManualFootprint required mutual coverage, so a single manual slab spanning multiple rooms never suppressed those rooms' auto slabs (only a fraction of it lies inside each room). Suppression now only asks whether the ROOM is substantially covered by the union of manual slabs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9ca3eaa7fe
commit
22c9472066
@@ -252,15 +252,14 @@ function polygonCoverageRatio(subject: Point2D[], covers: Point2D[][]) {
|
||||
}
|
||||
|
||||
// Demoted auto surfaces keep their polygon untouched, so a re-closed room
|
||||
// usually hits the exact-signature manual check. Coverage also handles a room
|
||||
// deliberately split across multiple manual surfaces: their union suppresses
|
||||
// a replacement auto surface as long as the pieces substantially belong to
|
||||
// and cover the room.
|
||||
// usually hits the exact-signature manual check. Coverage handles the rest:
|
||||
// a room split across multiple manual surfaces AND a single manual surface
|
||||
// spanning multiple rooms both suppress a replacement auto surface — what
|
||||
// matters is that the ROOM is already substantially covered, not that any
|
||||
// one manual surface belongs to it (a per-surface "mostly inside the room"
|
||||
// filter dropped multi-room slabs and resurrected deleted auto slabs).
|
||||
function matchesManualFootprint(roomPolygon: Point2D[], manualPolygons: Point2D[][]) {
|
||||
const roomManualPolygons = manualPolygons.filter(
|
||||
(manual) => polygonCoverageRatio(manual, [roomPolygon]) >= ORPHAN_MERGE_COVERAGE_THRESHOLD,
|
||||
)
|
||||
return polygonCoverageRatio(roomPolygon, roomManualPolygons) >= ORPHAN_MERGE_COVERAGE_THRESHOLD
|
||||
return polygonCoverageRatio(roomPolygon, manualPolygons) >= ORPHAN_MERGE_COVERAGE_THRESHOLD
|
||||
}
|
||||
|
||||
function pointDistanceToPolygonBoundary(point: Point2D, polygon: Point2D[]) {
|
||||
@@ -1357,7 +1356,11 @@ export function isSpaceDetectionPaused(): boolean {
|
||||
}
|
||||
|
||||
export function initSpaceDetectionSync(sceneStore: any, editorStore: any): () => void {
|
||||
const previousSnapshots = new Map<string, string>()
|
||||
// Baseline from whatever is already in the store. Detection reacts to wall
|
||||
// edits made IN-SESSION (create / move / delete); it must not re-litigate a
|
||||
// scene that merely loaded — rerunning on hydration resurrected auto slabs
|
||||
// the user had deleted in an earlier session.
|
||||
const previousSnapshots = levelStructureSnapshots(sceneStore.getState().nodes)
|
||||
let isProcessing = false
|
||||
|
||||
const unsubscribe = sceneStore.subscribe((state: any) => {
|
||||
@@ -1380,7 +1383,13 @@ export function initSpaceDetectionSync(sceneStore: any, editorStore: any): () =>
|
||||
|
||||
const levelsToUpdate = new Set<string>()
|
||||
for (const levelId of new Set([...previousSnapshots.keys(), ...currentSnapshots.keys()])) {
|
||||
if ((previousSnapshots.get(levelId) ?? '') !== (currentSnapshots.get(levelId) ?? '')) {
|
||||
// First sight of a level is a hydration baseline, not a wall edit —
|
||||
// `setScene` delivers a loaded scene as one atomic update, and a level's
|
||||
// first wall can't close a room anyway. Record it (below) and only
|
||||
// react to subsequent changes.
|
||||
const previous = previousSnapshots.get(levelId)
|
||||
if (previous === undefined) continue
|
||||
if (previous !== (currentSnapshots.get(levelId) ?? '')) {
|
||||
levelsToUpdate.add(levelId)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user