fix(editor): render slab side walls solid regardless of polygon winding (#377)
Slab and pool side walls assumed a CCW contour (the unflipped quad's right-hand normal is outward only for CCW), but outsetPolygon and the slab tool preserve the drawn winding. A CW-drawn slab therefore got inward-facing side-wall normals that FrontSide culling dropped, so the slab read as see-through from the camera-facing side (~half the time, depending on draw direction). Normalize the contour to CCW (reverse when signed area < 0) before building both the positive-slab and pool geometry. Caps are unaffected (Earcut normalizes the outer ring); holes are already double-emitted + winding-normalized; ceilings use flat ShapeGeometry (no extruded walls); autoFromWalls slabs are already CCW so this is a no-op for them. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4707c5b4f8
commit
f01c443f2a
@@ -81,6 +81,20 @@ export function generateSlabGeometry(slabNode: SlabNode): THREE.BufferGeometry {
|
||||
return elevation < 0 ? generatePoolGeometry(slabNode) : generatePositiveSlabGeometry(slabNode)
|
||||
}
|
||||
|
||||
// Earcut normalizes cap triangulation regardless of input winding, but the side
|
||||
// walls below assume a CCW contour (the unflipped quad's right-hand normal faces
|
||||
// outward only for CCW). outsetPolygon and the slab tool preserve the drawn
|
||||
// winding, so a CW-drawn slab gets inward-facing walls that FrontSide culls and
|
||||
// the slab reads as see-through from the front. Normalize to CCW first.
|
||||
function ensureCounterClockwisePolygon(polygon: Array<[number, number]>): Array<[number, number]> {
|
||||
let area2 = 0
|
||||
for (let i = 0; i < polygon.length; i++) {
|
||||
const j = (i + 1) % polygon.length
|
||||
area2 += polygon[i]![0] * polygon[j]![1] - polygon[j]![0] * polygon[i]![1]
|
||||
}
|
||||
return area2 < 0 ? [...polygon].reverse() : polygon
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard slab: flat extrusion upward from Y=0 by elevation thickness.
|
||||
*
|
||||
@@ -94,7 +108,7 @@ export function generateSlabGeometry(slabNode: SlabNode): THREE.BufferGeometry {
|
||||
* because exactly one faces the camera under FrontSide culling.
|
||||
*/
|
||||
function generatePositiveSlabGeometry(slabNode: SlabNode): THREE.BufferGeometry {
|
||||
const polygon = getRenderableSlabPolygon(slabNode)
|
||||
const polygon = ensureCounterClockwisePolygon(getRenderableSlabPolygon(slabNode))
|
||||
const elevation = slabNode.elevation ?? 0.05
|
||||
const holePolygons = mergeSurfaceHolePolygons(slabNode.holes ?? [])
|
||||
|
||||
@@ -187,7 +201,7 @@ function generatePositiveSlabGeometry(slabNode: SlabNode): THREE.BufferGeometry
|
||||
* - walls from Y=0 to Y=depth, inward-facing normals (visible from inside pool)
|
||||
*/
|
||||
function generatePoolGeometry(slabNode: SlabNode): THREE.BufferGeometry {
|
||||
const polygon = getRenderableSlabPolygon(slabNode)
|
||||
const polygon = ensureCounterClockwisePolygon(getRenderableSlabPolygon(slabNode))
|
||||
const depth = Math.abs(slabNode.elevation ?? 0.05)
|
||||
const holePolygons = mergeSurfaceHolePolygons(slabNode.holes ?? [])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user