diff --git a/packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts b/packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts index ba129bbc..e4c321aa 100644 --- a/packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts +++ b/packages/core/src/hooks/spatial-grid/spatial-grid-manager.ts @@ -137,20 +137,74 @@ export function itemOverlapsPolygon( return false } +/** + * Check if wall segment (a) is substantially on polygon edge segment (b). + * Returns true only if BOTH endpoints of the wall are on or very close to the edge. + * This prevents walls that just touch one point from being detected. + */ +function segmentsCollinearAndOverlap( + ax1: number, az1: number, ax2: number, az2: number, + bx1: number, bz1: number, bx2: number, bz2: number, +): boolean { + const EPSILON = 1e-6 + + // Cross product to check collinearity + const cross1 = (ax2 - ax1) * (bz1 - az1) - (az2 - az1) * (bx1 - ax1) + const cross2 = (ax2 - ax1) * (bz2 - az1) - (az2 - az1) * (bx2 - ax1) + + if (Math.abs(cross1) > EPSILON || Math.abs(cross2) > EPSILON) { + return false // Not collinear + } + + // Check if a point is on segment b + const onSegment = (px: number, pz: number, qx: number, qz: number, rx: number, rz: number) => + Math.min(px, qx) - EPSILON <= rx && rx <= Math.max(px, qx) + EPSILON && + Math.min(pz, qz) - EPSILON <= rz && rz <= Math.max(pz, qz) + EPSILON + + // BOTH endpoints of wall (a) must be on edge (b) for substantial overlap + const a1OnB = onSegment(bx1, bz1, bx2, bz2, ax1, az1) + const a2OnB = onSegment(bx1, bz1, bx2, bz2, ax2, az2) + + return a1OnB && a2OnB +} + /** * Test if a wall segment overlaps with a polygon. + * A wall is considered to overlap if: + * - Its midpoint is inside the polygon (wall crosses through) + * - At least one endpoint is inside (wall partially or fully in slab) + * - It's collinear with and overlaps a polygon edge (wall on slab boundary) + * + * Note: A wall with just one endpoint touching the edge but the rest outside + * is NOT considered overlapping (adjacent only). */ export function wallOverlapsPolygon( start: [number, number], end: [number, number], polygon: Array<[number, number]>, ): boolean { - // Either endpoint inside the polygon - if (pointInPolygon(start[0], start[1], polygon)) return true - if (pointInPolygon(end[0], end[1], polygon)) return true + const startInside = pointInPolygon(start[0], start[1], polygon) + const endInside = pointInPolygon(end[0], end[1], polygon) - // Wall segment intersects any polygon edge - if (segmentIntersectsPolygon(start[0], start[1], end[0], end[1], polygon)) return true + // At least one endpoint strictly inside the polygon + if (startInside || endInside) return true + + // Check if midpoint is inside (catches walls crossing through) + const midX = (start[0] + end[0]) / 2 + const midZ = (start[1] + end[1]) / 2 + if (pointInPolygon(midX, midZ, polygon)) return true + + // Check if the wall is collinear with and overlaps any polygon edge + const n = polygon.length + for (let i = 0; i < n; i++) { + const j = (i + 1) % n + const [p1x, p1z] = polygon[i]! + const [p2x, p2z] = polygon[j]! + + if (segmentsCollinearAndOverlap(start[0], start[1], end[0], end[1], p1x, p1z, p2x, p2z)) { + return true + } + } return false } @@ -460,7 +514,7 @@ export class SpatialGridManager { const slabMap = this.slabsByLevel.get(levelId) if (!slabMap) return 0 - let maxElevation = 0 + let maxElevation = -Infinity for (const slab of slabMap.values()) { if (slab.polygon.length < 3) continue if (wallOverlapsPolygon(start, end, slab.polygon)) { @@ -470,7 +524,7 @@ export class SpatialGridManager { } } } - return maxElevation + return maxElevation === -Infinity ? 0 : maxElevation } /** diff --git a/packages/core/src/systems/wall/wall-system.tsx b/packages/core/src/systems/wall/wall-system.tsx index 213d9e8b..8042b1da 100644 --- a/packages/core/src/systems/wall/wall-system.tsx +++ b/packages/core/src/systems/wall/wall-system.tsx @@ -49,7 +49,6 @@ export const WallSystem = () => { // Process each level that has dirty walls for (const [levelId, dirtyWallIds] of dirtyWallsByLevel) { - console.log(`Updating walls for level ${levelId}`) const levelWalls = getLevelWalls(levelId) const miterData = calculateLevelMiters(levelWalls) @@ -153,6 +152,7 @@ export function generateExtrudedWall( const wallEnd: Point2D = { x: wallNode.end[0], y: wallNode.end[1] } // Wall height is adjusted by slab elevation (positive reduces, negative increases) const height = (wallNode.height ?? 2.5) - slabElevation + const thickness = wallNode.thickness ?? 0.1 const halfT = thickness / 2