remove community

This commit is contained in:
wass08
2026-03-13 09:07:46 +01:00
parent 3eeba322a8
commit f2501be2bf
693 changed files with 8264 additions and 35562 deletions
@@ -1,5 +1,5 @@
import { useLayoutEffect } from "react";
import type * as THREE from "three";
import { useLayoutEffect } from 'react'
import type * as THREE from 'three'
export const sceneRegistry = {
// Master lookup: ID -> Object3D
@@ -22,7 +22,7 @@ export const sceneRegistry = {
window: new Set<string>(),
door: new Set<string>(),
},
};
}
export function useRegistry(
id: string,
@@ -30,19 +30,19 @@ export function useRegistry(
ref: React.RefObject<THREE.Object3D>,
) {
useLayoutEffect(() => {
const obj = ref.current;
if (!obj) return;
const obj = ref.current
if (!obj) return
// 1. Add to master map
sceneRegistry.nodes.set(id, obj);
sceneRegistry.nodes.set(id, obj)
// 2. Add to type-specific set
sceneRegistry.byType[type].add(id);
sceneRegistry.byType[type].add(id)
// 4. Cleanup when component unmounts
return () => {
sceneRegistry.nodes.delete(id);
sceneRegistry.byType[type].delete(id);
};
}, [id, type, ref]);
sceneRegistry.nodes.delete(id)
sceneRegistry.byType[type].delete(id)
}
}, [id, type, ref])
}
@@ -1,5 +1,5 @@
import { getScaledDimensions } from '../../schema'
import type { AnyNode, CeilingNode, ItemNode, SlabNode, WallNode } from '../../schema'
import { getScaledDimensions } from '../../schema'
import { SpatialGrid } from './spatial-grid'
import { WallSpatialGrid } from './wall-spatial-grid'
@@ -14,10 +14,12 @@ export function pointInPolygon(px: number, pz: number, polygon: Array<[number, n
let inside = false
const n = polygon.length
for (let i = 0, j = n - 1; i < n; j = i++) {
const xi = polygon[i]![0], zi = polygon[i]![1]
const xj = polygon[j]![0], zj = polygon[j]![1]
const xi = polygon[i]![0],
zi = polygon[i]![1]
const xj = polygon[j]![0],
zj = polygon[j]![1]
if ((zi > pz) !== (zj > pz) && px < ((xj - xi) * (pz - zi)) / (zj - zi) + xi) {
if (zi > pz !== zj > pz && px < ((xj - xi) * (pz - zi)) / (zj - zi) + xi) {
inside = !inside
}
}
@@ -53,8 +55,14 @@ function getItemFootprint(
* Test if two line segments (a1->a2) and (b1->b2) intersect.
*/
function segmentsIntersect(
ax1: number, az1: number, ax2: number, az2: number,
bx1: number, bz1: number, bx2: number, bz2: number,
ax1: number,
az1: number,
ax2: number,
az2: number,
bx1: number,
bz1: number,
bx2: number,
bz2: number,
): boolean {
const cross = (ox: number, oz: number, ax: number, az: number, bx: number, bz: number) =>
(ax - ox) * (bz - oz) - (az - oz) * (bx - ox)
@@ -64,15 +72,16 @@ function segmentsIntersect(
const d3 = cross(ax1, az1, ax2, az2, bx1, bz1)
const d4 = cross(ax1, az1, ax2, az2, bx2, bz2)
if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) {
if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) {
return true
}
// Collinear touching cases
const onSeg = (px: number, pz: number, qx: number, qz: number, rx: number, rz: number) =>
Math.min(px, qx) <= rx && rx <= Math.max(px, qx) &&
Math.min(pz, qz) <= rz && rz <= Math.max(pz, qz)
Math.min(px, qx) <= rx &&
rx <= Math.max(px, qx) &&
Math.min(pz, qz) <= rz &&
rz <= Math.max(pz, qz)
if (d1 === 0 && onSeg(bx1, bz1, bx2, bz2, ax1, az1)) return true
if (d2 === 0 && onSeg(bx1, bz1, bx2, bz2, ax2, az2)) return true
@@ -86,16 +95,27 @@ function segmentsIntersect(
* Test if a line segment intersects any edge of a polygon.
*/
function segmentIntersectsPolygon(
sx1: number, sz1: number, sx2: number, sz2: number,
sx1: number,
sz1: number,
sx2: number,
sz2: number,
polygon: Array<[number, number]>,
): boolean {
const n = polygon.length
for (let i = 0; i < n; i++) {
const j = (i + 1) % n
if (segmentsIntersect(
sx1, sz1, sx2, sz2,
polygon[i]![0], polygon[i]![1], polygon[j]![0], polygon[j]![1],
)) {
if (
segmentsIntersect(
sx1,
sz1,
sx2,
sz2,
polygon[i]![0],
polygon[i]![1],
polygon[j]![0],
polygon[j]![1],
)
) {
return true
}
}
@@ -129,10 +149,16 @@ export function itemOverlapsPolygon(
// Check if any item edge intersects any polygon edge
for (let i = 0; i < 4; i++) {
const j = (i + 1) % 4
if (segmentIntersectsPolygon(
corners[i]![0], corners[i]![1], corners[j]![0], corners[j]![1],
polygon,
)) return true
if (
segmentIntersectsPolygon(
corners[i]![0],
corners[i]![1],
corners[j]![0],
corners[j]![1],
polygon,
)
)
return true
}
return false
@@ -144,8 +170,14 @@ export function itemOverlapsPolygon(
* 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,
ax1: number,
az1: number,
ax2: number,
az2: number,
bx1: number,
bz1: number,
bx2: number,
bz2: number,
): boolean {
const EPSILON = 1e-6
@@ -159,8 +191,10 @@ function segmentsCollinearAndOverlap(
// 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
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)
@@ -314,7 +348,7 @@ export class SpatialGridManager {
// position[1] is the bottom of the item
this.getWallGrid(levelId).insert({
itemId: item.id,
wallId: wallId,
wallId,
tStart: t - halfW,
tEnd: t + halfW,
yStart: item.position[1],
@@ -328,7 +362,12 @@ export class SpatialGridManager {
// Ceiling item - use parentId as the ceiling ID
const ceilingId = item.parentId
if (ceilingId && this.ceilings.has(ceilingId)) {
this.getCeilingGrid(ceilingId).insert(item.id, item.position, getScaledDimensions(item), item.rotation)
this.getCeilingGrid(ceilingId).insert(
item.id,
item.position,
getScaledDimensions(item),
item.rotation,
)
this.itemCeilingMap.set(item.id, ceilingId)
}
} else if (!item.asset.attachTo) {
@@ -367,7 +406,7 @@ export class SpatialGridManager {
// position[1] is the bottom of the item
this.getWallGrid(levelId).insert({
itemId: item.id,
wallId: wallId,
wallId,
tStart: t - halfW,
tEnd: t + halfW,
yStart: item.position[1],
@@ -387,7 +426,12 @@ export class SpatialGridManager {
// Insert into new ceiling grid
const ceilingId = item.parentId
if (ceilingId && this.ceilings.has(ceilingId)) {
this.getCeilingGrid(ceilingId).insert(item.id, item.position, getScaledDimensions(item), item.rotation)
this.getCeilingGrid(ceilingId).insert(
item.id,
item.position,
getScaledDimensions(item),
item.rotation,
)
this.itemCeilingMap.set(item.id, ceilingId)
}
} else if (!item.asset.attachTo) {
@@ -530,9 +574,12 @@ export class SpatialGridManager {
const slabMap = this.slabsByLevel.get(levelId)
if (!slabMap) return 0
let maxElevation = -Infinity
let maxElevation = Number.NEGATIVE_INFINITY
for (const slab of slabMap.values()) {
if (slab.polygon.length >= 3 && itemOverlapsPolygon(position, dimensions, rotation, slab.polygon, 0.01)) {
if (
slab.polygon.length >= 3 &&
itemOverlapsPolygon(position, dimensions, rotation, slab.polygon, 0.01)
) {
// Check if item is entirely within a hole (if so, ignore this slab)
// We consider it entirely in a hole if the item center is in the hole
let inHole = false
@@ -553,7 +600,7 @@ export class SpatialGridManager {
}
}
}
return maxElevation === -Infinity ? 0 : maxElevation
return maxElevation === Number.NEGATIVE_INFINITY ? 0 : maxElevation
}
/**
@@ -561,15 +608,11 @@ export class SpatialGridManager {
* Uses wallOverlapsPolygon which handles edge cases (points on boundary, collinear segments).
* Returns the highest slab elevation found, or 0 if none.
*/
getSlabElevationForWall(
levelId: string,
start: [number, number],
end: [number, number],
): number {
getSlabElevationForWall(levelId: string, start: [number, number], end: [number, number]): number {
const slabMap = this.slabsByLevel.get(levelId)
if (!slabMap) return 0
let maxElevation = -Infinity
let maxElevation = Number.NEGATIVE_INFINITY
for (const slab of slabMap.values()) {
if (slab.polygon.length < 3) continue
if (!wallOverlapsPolygon(start, end, slab.polygon)) continue
@@ -609,7 +652,7 @@ export class SpatialGridManager {
if (elevation > maxElevation) maxElevation = elevation
}
}
return maxElevation === -Infinity ? 0 : maxElevation
return maxElevation === Number.NEGATIVE_INFINITY ? 0 : maxElevation
}
/**
@@ -1,6 +1,17 @@
import { getScaledDimensions, type AnyNode, type AnyNodeId, type ItemNode, type SlabNode, type WallNode } from '../../schema'
import {
type AnyNode,
type AnyNodeId,
getScaledDimensions,
type ItemNode,
type SlabNode,
type WallNode,
} from '../../schema'
import useScene from '../../store/use-scene'
import { itemOverlapsPolygon, spatialGridManager, wallOverlapsPolygon } from './spatial-grid-manager'
import {
itemOverlapsPolygon,
spatialGridManager,
wallOverlapsPolygon,
} from './spatial-grid-manager'
export function resolveLevelId(node: AnyNode, nodes: Record<string, AnyNode>): string {
// If the node itself is a level
@@ -13,10 +24,10 @@ export function resolveLevelId(node: AnyNode, nodes: Record<string, AnyNode>): s
while (current) {
if (current.type === 'level') return current.id
// Find parent (you might need to add parentId to your schema or derive it)
if (!current.parentId) {
current = undefined
} else {
if (current.parentId) {
current = nodes[current.parentId]
} else {
current = undefined
}
}
@@ -71,9 +82,11 @@ export function initSpatialGridSync() {
if (node.type === 'item' && prev.type === 'item') {
if (
!arraysEqual(node.position, prev.position) ||
!arraysEqual(node.rotation, prev.rotation) ||
!arraysEqual(node.scale, prev.scale) ||
!(
arraysEqual(node.position, prev.position) &&
arraysEqual(node.rotation, prev.rotation) &&
arraysEqual(node.scale, prev.scale)
) ||
node.parentId !== prev.parentId ||
node.side !== prev.side
) {
@@ -85,7 +98,11 @@ export function initSpatialGridSync() {
}
}
} else if (node.type === 'slab' && prev.type === 'slab') {
if (node.polygon !== prev.polygon || node.elevation !== prev.elevation || node.holes !== prev.holes) {
if (
node.polygon !== prev.polygon ||
node.elevation !== prev.elevation ||
node.holes !== prev.holes
) {
const levelId = resolveLevelId(node, state.nodes)
spatialGridManager.handleNodeUpdated(node, levelId)
@@ -119,7 +136,15 @@ function markNodesOverlappingSlab(
// Only floor items are affected by slabs
if (item.asset.attachTo) continue
if (resolveLevelId(node, nodes) !== slabLevelId) continue
if (itemOverlapsPolygon(item.position, getScaledDimensions(item), item.rotation, slab.polygon, 0.01)) {
if (
itemOverlapsPolygon(
item.position,
getScaledDimensions(item),
item.rotation,
slab.polygon,
0.01,
)
) {
markDirty(node.id)
}
} else if (node.type === 'wall') {
@@ -49,7 +49,13 @@ export function useSpatialQuery() {
rotation: [number, number, number],
ignoreIds?: string[],
) => {
return spatialGridManager.canPlaceOnCeiling(ceilingId, position, dimensions, rotation, ignoreIds)
return spatialGridManager.canPlaceOnCeiling(
ceilingId,
position,
dimensions,
rotation,
ignoreIds,
)
},
[],
)
@@ -140,7 +140,7 @@ export class WallSpatialGrid {
// Both are 'wall-side' - only conflict if they're on the same side
// If either side is undefined, be conservative and assume conflict
if (!newSide || !existing.side) {
if (!(newSide && existing.side)) {
return true
}
return newSide === existing.side