handle better slab polygon detection

This commit is contained in:
wass08
2026-02-02 11:35:44 +09:00
parent 72a0e3952a
commit 5392d66bd6
2 changed files with 62 additions and 8 deletions
@@ -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
}
/**
@@ -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