slab outset + wall pushing up

This commit is contained in:
wass08
2026-01-27 19:33:30 +09:00
parent d4ff59e9c5
commit 1a5bd11009
3 changed files with 104 additions and 6 deletions
@@ -404,6 +404,40 @@ export class SpatialGridManager {
return maxElevation
}
/**
* Get the slab elevation for a wall by sampling its start, mid, and end points.
* Returns the highest slab elevation found, or 0 if none.
*/
getSlabElevationForWall(
levelId: string,
start: [number, number],
end: [number, number],
): number {
const slabMap = this.slabsByLevel.get(levelId)
if (!slabMap) return 0
const samples: [number, number][] = [
start,
[(start[0] + end[0]) / 2, (start[1] + end[1]) / 2],
end,
]
let maxElevation = 0
for (const slab of slabMap.values()) {
if (slab.polygon.length < 3) continue
for (const [sx, sz] of samples) {
if (pointInPolygon(sx, sz, slab.polygon)) {
const elevation = slab.elevation ?? 0.05
if (elevation > maxElevation) {
maxElevation = elevation
}
break // This slab matched, no need to check more sample points
}
}
}
return maxElevation
}
clearLevel(levelId: string) {
this.floorGrids.delete(levelId)
this.wallGrids.delete(levelId)
+55 -1
View File
@@ -40,11 +40,65 @@ function updateSlabGeometry(node: SlabNode, mesh: THREE.Mesh) {
mesh.geometry = newGeo
}
/** Half of default wall thickness — used to extend slab geometry under walls */
const SLAB_OUTSET = 0.05
/**
* Expand a polygon outward by a uniform distance.
* Offsets each edge outward then intersects consecutive offset edges.
*/
function outsetPolygon(polygon: Array<[number, number]>, amount: number): Array<[number, number]> {
const n = polygon.length
if (n < 3) return polygon
// Determine winding via signed area
let area2 = 0
for (let i = 0; i < n; i++) {
const j = (i + 1) % n
area2 += polygon[i]![0] * polygon[j]![1] - polygon[j]![0] * polygon[i]![1]
}
const s = area2 >= 0 ? 1 : -1
// Offset each edge outward by amount
const offEdges: Array<[number, number, number, number]> = []
for (let i = 0; i < n; i++) {
const j = (i + 1) % n
const dx = polygon[j]![0] - polygon[i]![0]
const dz = polygon[j]![1] - polygon[i]![1]
const len = Math.sqrt(dx * dx + dz * dz)
if (len < 1e-9) {
offEdges.push([polygon[i]![0], polygon[i]![1], dx, dz])
continue
}
const nx = (s * dz / len) * amount
const nz = (s * -dx / len) * amount
offEdges.push([polygon[i]![0] + nx, polygon[i]![1] + nz, dx, dz])
}
// Intersect consecutive offset edges to get new vertices
const result: Array<[number, number]> = []
for (let i = 0; i < n; i++) {
const j = (i + 1) % n
const [ax, az, adx, adz] = offEdges[i]!
const [bx, bz, bdx, bdz] = offEdges[j]!
const denom = adx * bdz - adz * bdx
if (Math.abs(denom) < 1e-9) {
// Parallel edges — use offset endpoint
result.push([ax + adx, az + adz])
} else {
const t = ((bx - ax) * bdz - (bz - az) * bdx) / denom
result.push([ax + t * adx, az + t * adz])
}
}
return result
}
/**
* Generates extruded slab geometry from polygon
*/
export function generateSlabGeometry(slabNode: SlabNode): THREE.BufferGeometry {
const polygon = slabNode.polygon
const polygon = outsetPolygon(slabNode.polygon, SLAB_OUTSET)
const elevation = slabNode.elevation ?? 0.05
if (polygon.length < 3) {
+15 -5
View File
@@ -2,6 +2,8 @@ import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
import { Brush, Evaluator, SUBTRACTION } from 'three-bvh-csg'
import { sceneRegistry } from '../../hooks/scene-registry/scene-registry'
import { spatialGridManager } from '../../hooks/spatial-grid/spatial-grid-manager'
import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync'
import type { AnyNode, AnyNodeId, WallNode } from '../../schema'
import useScene from '../../store/use-scene'
import {
@@ -95,18 +97,22 @@ function getLevelWalls(levelId: string): WallNode[] {
* Updates the geometry for a single wall
*/
function updateWallGeometry(wallId: string, miterData: WallMiterData) {
const node = useScene.getState().nodes[wallId as WallNode['id']]
const nodes = useScene.getState().nodes
const node = nodes[wallId as WallNode['id']]
if (!node || node.type !== 'wall') return
const mesh = sceneRegistry.nodes.get(wallId) as THREE.Mesh
if (!mesh) return
const levelId = resolveLevelId(node, nodes)
const slabElevation = spatialGridManager.getSlabElevationForWall(levelId, node.start, node.end)
const childrenIds = node.children || []
const childrenNodes = childrenIds
.map((childId) => useScene.getState().nodes[childId])
.map((childId) => nodes[childId])
.filter((n): n is AnyNode => n !== undefined)
const newGeo = generateExtrudedWall(node, childrenNodes, miterData)
const newGeo = generateExtrudedWall(node, childrenNodes, miterData, slabElevation)
mesh.geometry.dispose()
mesh.geometry = newGeo
@@ -114,7 +120,7 @@ function updateWallGeometry(wallId: string, miterData: WallMiterData) {
// Update collision mesh
const collisionMesh = mesh.getObjectByName('collision-mesh') as THREE.Mesh
if (collisionMesh) {
const collisionGeo = generateExtrudedWall(node, [], miterData)
const collisionGeo = generateExtrudedWall(node, [], miterData, slabElevation)
collisionMesh.geometry.dispose()
collisionMesh.geometry = collisionGeo
}
@@ -134,12 +140,13 @@ export function generateExtrudedWall(
wallNode: WallNode,
childrenNodes: AnyNode[],
miterData: WallMiterData,
slabElevation = 0,
) {
const { junctionData } = miterData
const wallStart: Point2D = { x: wallNode.start[0], y: wallNode.start[1] }
const wallEnd: Point2D = { x: wallNode.end[0], y: wallNode.end[1] }
const height = wallNode.height ?? 2.5
const height = (wallNode.height ?? 2.5) - slabElevation
const thickness = wallNode.thickness ?? 0.1
const halfT = thickness / 2
@@ -232,6 +239,9 @@ export function generateExtrudedWall(
// Rotate so extrusion direction (Z) becomes height direction (Y)
geometry.rotateX(-Math.PI / 2)
if (slabElevation > 0) {
geometry.translate(0, slabElevation, 0)
}
geometry.computeVertexNormals()
// Apply CSG subtraction for cutouts (doors/windows)