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
@@ -23,6 +23,17 @@ const SHADOWS_DISABLED =
|
||||
.map((s) => s.trim()),
|
||||
).has('shadows')
|
||||
|
||||
// Diagnostic toggle: `?debug=shadowcamera` draws a CameraHelper for each
|
||||
// shadow camera so the building-fit frustum (and thus shadow texel density)
|
||||
// can be inspected while tuning margins/bias.
|
||||
const SHADOW_CAMERA_DEBUG =
|
||||
typeof window !== 'undefined' &&
|
||||
new Set(
|
||||
(new URLSearchParams(window.location.search).get('debug') ?? '')
|
||||
.split(',')
|
||||
.map((s) => s.trim()),
|
||||
).has('shadowcamera')
|
||||
|
||||
// Shadow darkness for the bright key lights (themes drive most lights past
|
||||
// intensity 1). Runs high so shadowed areas actually lose the sun's
|
||||
// contribution — the ambient/hemisphere/IBL stack provides the fill. The old
|
||||
@@ -32,9 +43,12 @@ const MAX_SHADOW_INTENSITY = 0.75
|
||||
|
||||
// `normalBias` is measured in world units. The previous 0.3 moved shadow
|
||||
// lookups 30 cm off their surfaces, visibly detaching wall shadows at the
|
||||
// floor. Keep only a small offset for acne, with a tiny depth bias alongside it.
|
||||
const SHADOW_DEPTH_BIAS = -0.0001
|
||||
const SHADOW_NORMAL_BIAS = 0.02
|
||||
// floor; 0.07 and below still acnes on the building-fit 1024 map (large
|
||||
// texels), so 0.08 is the smallest acne-free value at that resolution — even
|
||||
// with the depth bias at -0.0005 (which is itself needed: 0.08 alone still
|
||||
// showed faint acne at -0.0001).
|
||||
const SHADOW_DEPTH_BIAS = -0.0005
|
||||
const SHADOW_NORMAL_BIAS = 0.08
|
||||
|
||||
// Shadow frustum framing. The frustum is fit to the BUILDING geometry (not the
|
||||
// camera): we union the bounds of all registered scene nodes, fit a sphere, and
|
||||
@@ -77,6 +91,7 @@ export function Lights() {
|
||||
const boundsBox = useRef(new THREE.Box3()) // scratch: union AABB
|
||||
const boundsSphere = useRef(new THREE.Sphere()) // scratch: fitted sphere
|
||||
const lastBoundsTime = useRef(-1) // last refresh timestamp (-1 = never)
|
||||
const shadowHelpers = useRef<Array<THREE.CameraHelper | null>>([])
|
||||
|
||||
const hemiRef = useRef<HemisphereLight>(null)
|
||||
const ambientRef = useRef<AmbientLight>(null)
|
||||
@@ -170,6 +185,15 @@ export function Lights() {
|
||||
cam.near = near
|
||||
cam.far = far
|
||||
cam.updateProjectionMatrix()
|
||||
if (SHADOW_CAMERA_DEBUG) {
|
||||
let helper = shadowHelpers.current[index]
|
||||
if (!helper) {
|
||||
helper = new THREE.CameraHelper(cam)
|
||||
shadowHelpers.current[index] = helper
|
||||
state.scene.add(helper)
|
||||
}
|
||||
helper.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user