@pascal-app/core 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/events/bus.d.ts +42 -0
- package/dist/events/bus.d.ts.map +1 -0
- package/dist/events/bus.js +13 -0
- package/dist/hooks/scene-registry/scene-registry.d.ts +18 -0
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -0
- package/dist/hooks/scene-registry/scene-registry.js +35 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +90 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +466 -0
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +4 -0
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +115 -0
- package/dist/hooks/spatial-grid/spatial-grid.d.ts +23 -0
- package/dist/hooks/spatial-grid/spatial-grid.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/spatial-grid.js +115 -0
- package/dist/hooks/spatial-grid/use-spatial-query.d.ts +16 -0
- package/dist/hooks/spatial-grid/use-spatial-query.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/use-spatial-query.js +14 -0
- package/dist/hooks/spatial-grid/wall-spatial-grid.d.ts +47 -0
- package/dist/hooks/spatial-grid/wall-spatial-grid.d.ts.map +1 -0
- package/dist/hooks/spatial-grid/wall-spatial-grid.js +113 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/lib/asset-storage.d.ts +11 -0
- package/dist/lib/asset-storage.d.ts.map +1 -0
- package/dist/lib/asset-storage.js +48 -0
- package/dist/lib/space-detection.d.ts +34 -0
- package/dist/lib/space-detection.d.ts.map +1 -0
- package/dist/lib/space-detection.js +499 -0
- package/dist/schema/base.d.ts +30 -0
- package/dist/schema/base.d.ts.map +1 -0
- package/dist/schema/base.js +25 -0
- package/dist/schema/camera.d.ts +13 -0
- package/dist/schema/camera.d.ts.map +1 -0
- package/dist/schema/camera.js +9 -0
- package/dist/schema/index.d.ts +17 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +18 -0
- package/dist/schema/nodes/building.d.ts +25 -0
- package/dist/schema/nodes/building.d.ts.map +1 -0
- package/dist/schema/nodes/building.js +16 -0
- package/dist/schema/nodes/ceiling.d.ts +25 -0
- package/dist/schema/nodes/ceiling.d.ts.map +1 -0
- package/dist/schema/nodes/ceiling.js +16 -0
- package/dist/schema/nodes/guide.d.ts +27 -0
- package/dist/schema/nodes/guide.d.ts.map +1 -0
- package/dist/schema/nodes/guide.js +11 -0
- package/dist/schema/nodes/item.d.ts +65 -0
- package/dist/schema/nodes/item.d.ts.map +1 -0
- package/dist/schema/nodes/item.js +38 -0
- package/dist/schema/nodes/level.d.ts +24 -0
- package/dist/schema/nodes/level.d.ts.map +1 -0
- package/dist/schema/nodes/level.js +21 -0
- package/dist/schema/nodes/roof.d.ts +28 -0
- package/dist/schema/nodes/roof.d.ts.map +1 -0
- package/dist/schema/nodes/roof.js +28 -0
- package/dist/schema/nodes/scan.d.ts +27 -0
- package/dist/schema/nodes/scan.d.ts.map +1 -0
- package/dist/schema/nodes/scan.js +11 -0
- package/dist/schema/nodes/site.d.ts +90 -0
- package/dist/schema/nodes/site.d.ts.map +1 -0
- package/dist/schema/nodes/site.js +39 -0
- package/dist/schema/nodes/slab.d.ts +24 -0
- package/dist/schema/nodes/slab.d.ts.map +1 -0
- package/dist/schema/nodes/slab.js +15 -0
- package/dist/schema/nodes/wall.d.ts +37 -0
- package/dist/schema/nodes/wall.d.ts.map +1 -0
- package/dist/schema/nodes/wall.js +30 -0
- package/dist/schema/nodes/zone.d.ts +24 -0
- package/dist/schema/nodes/zone.d.ts.map +1 -0
- package/dist/schema/nodes/zone.js +22 -0
- package/dist/schema/types.d.ts +339 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +25 -0
- package/dist/store/actions/node-actions.d.ts +12 -0
- package/dist/store/actions/node-actions.d.ts.map +1 -0
- package/dist/store/actions/node-actions.js +121 -0
- package/dist/store/use-scene.d.ts +31 -0
- package/dist/store/use-scene.d.ts.map +1 -0
- package/dist/store/use-scene.js +127 -0
- package/dist/systems/ceiling/ceiling-system.d.ts +8 -0
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -0
- package/dist/systems/ceiling/ceiling-system.js +65 -0
- package/dist/systems/item/item-system.d.ts +2 -0
- package/dist/systems/item/item-system.d.ts.map +1 -0
- package/dist/systems/item/item-system.js +43 -0
- package/dist/systems/roof/roof-system.d.ts +8 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -0
- package/dist/systems/roof/roof-system.js +254 -0
- package/dist/systems/slab/slab-system.d.ts +8 -0
- package/dist/systems/slab/slab-system.d.ts.map +1 -0
- package/dist/systems/slab/slab-system.js +117 -0
- package/dist/systems/wall/wall-mitering.d.ts +32 -0
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -0
- package/dist/systems/wall/wall-mitering.js +214 -0
- package/dist/systems/wall/wall-system.d.ts +12 -0
- package/dist/systems/wall/wall-system.d.ts.map +1 -0
- package/dist/systems/wall/wall-system.js +286 -0
- package/dist/utils/types.d.ts +6 -0
- package/dist/utils/types.d.ts.map +1 -0
- package/dist/utils/types.js +7 -0
- package/package.json +58 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export class SpatialGrid {
|
|
2
|
+
config;
|
|
3
|
+
cells = new Map();
|
|
4
|
+
itemCells = new Map(); // reverse lookup
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
}
|
|
8
|
+
posToCell(x, z) {
|
|
9
|
+
return [Math.floor(x / this.config.cellSize), Math.floor(z / this.config.cellSize)];
|
|
10
|
+
}
|
|
11
|
+
cellKey(cx, cz) {
|
|
12
|
+
return `${cx},${cz}`;
|
|
13
|
+
}
|
|
14
|
+
// Get all cells an item occupies based on its AABB
|
|
15
|
+
getItemCells(position, dimensions, rotation) {
|
|
16
|
+
// Simplified: axis-aligned bounding box
|
|
17
|
+
// For full rotation support, compute rotated corners
|
|
18
|
+
const [x, , z] = position;
|
|
19
|
+
const [w, , d] = dimensions;
|
|
20
|
+
const yRot = rotation[1]; // Y-axis rotation
|
|
21
|
+
// Compute rotated footprint (simplified for 90° increments)
|
|
22
|
+
const cos = Math.abs(Math.cos(yRot));
|
|
23
|
+
const sin = Math.abs(Math.sin(yRot));
|
|
24
|
+
const rotatedW = w * cos + d * sin;
|
|
25
|
+
const rotatedD = w * sin + d * cos;
|
|
26
|
+
const minX = x - rotatedW / 2;
|
|
27
|
+
const maxX = x + rotatedW / 2;
|
|
28
|
+
const minZ = z - rotatedD / 2;
|
|
29
|
+
const maxZ = z + rotatedD / 2;
|
|
30
|
+
const [minCx, minCz] = this.posToCell(minX, minZ);
|
|
31
|
+
// Use exclusive upper bound: subtract epsilon so exact boundaries don't overlap
|
|
32
|
+
// This allows adjacent items (touching but not overlapping) to not conflict
|
|
33
|
+
const epsilon = 1e-6;
|
|
34
|
+
const [maxCx, maxCz] = this.posToCell(maxX - epsilon, maxZ - epsilon);
|
|
35
|
+
const keys = [];
|
|
36
|
+
for (let cx = minCx; cx <= maxCx; cx++) {
|
|
37
|
+
for (let cz = minCz; cz <= maxCz; cz++) {
|
|
38
|
+
keys.push(this.cellKey(cx, cz));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return keys;
|
|
42
|
+
}
|
|
43
|
+
// Register an item
|
|
44
|
+
insert(itemId, position, dimensions, rotation) {
|
|
45
|
+
const cellKeys = this.getItemCells(position, dimensions, rotation);
|
|
46
|
+
this.itemCells.set(itemId, new Set(cellKeys));
|
|
47
|
+
for (const key of cellKeys) {
|
|
48
|
+
if (!this.cells.has(key)) {
|
|
49
|
+
this.cells.set(key, { itemIds: new Set() });
|
|
50
|
+
}
|
|
51
|
+
this.cells.get(key).itemIds.add(itemId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Remove an item
|
|
55
|
+
remove(itemId) {
|
|
56
|
+
const cellKeys = this.itemCells.get(itemId);
|
|
57
|
+
if (!cellKeys)
|
|
58
|
+
return;
|
|
59
|
+
for (const key of cellKeys) {
|
|
60
|
+
const cell = this.cells.get(key);
|
|
61
|
+
if (cell) {
|
|
62
|
+
cell.itemIds.delete(itemId);
|
|
63
|
+
if (cell.itemIds.size === 0) {
|
|
64
|
+
this.cells.delete(key);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
this.itemCells.delete(itemId);
|
|
69
|
+
}
|
|
70
|
+
// Update = remove + insert
|
|
71
|
+
update(itemId, position, dimensions, rotation) {
|
|
72
|
+
this.remove(itemId);
|
|
73
|
+
this.insert(itemId, position, dimensions, rotation);
|
|
74
|
+
}
|
|
75
|
+
// Query: is this placement valid?
|
|
76
|
+
canPlace(position, dimensions, rotation, ignoreIds = []) {
|
|
77
|
+
const cellKeys = this.getItemCells(position, dimensions, rotation);
|
|
78
|
+
const ignoreSet = new Set(ignoreIds);
|
|
79
|
+
const conflicts = new Set();
|
|
80
|
+
for (const key of cellKeys) {
|
|
81
|
+
const cell = this.cells.get(key);
|
|
82
|
+
if (cell) {
|
|
83
|
+
for (const id of cell.itemIds) {
|
|
84
|
+
if (!ignoreSet.has(id)) {
|
|
85
|
+
conflicts.add(id);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
valid: conflicts.size === 0,
|
|
92
|
+
conflictIds: [...conflicts],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Query: get all items near a point (for snapping, selection, etc.)
|
|
96
|
+
queryRadius(x, z, radius) {
|
|
97
|
+
const cellRadius = Math.ceil(radius / this.config.cellSize);
|
|
98
|
+
const [cx, cz] = this.posToCell(x, z);
|
|
99
|
+
const found = new Set();
|
|
100
|
+
for (let dx = -cellRadius; dx <= cellRadius; dx++) {
|
|
101
|
+
for (let dz = -cellRadius; dz <= cellRadius; dz++) {
|
|
102
|
+
const cell = this.cells.get(this.cellKey(cx + dx, cz + dz));
|
|
103
|
+
if (cell) {
|
|
104
|
+
for (const id of cell.itemIds) {
|
|
105
|
+
found.add(id);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return [...found];
|
|
111
|
+
}
|
|
112
|
+
getItemCount() {
|
|
113
|
+
return this.itemCells.size;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CeilingNode, LevelNode, WallNode } from '../../schema';
|
|
2
|
+
export declare function useSpatialQuery(): {
|
|
3
|
+
canPlaceOnFloor: (levelId: LevelNode["id"], position: [number, number, number], dimensions: [number, number, number], rotation: [number, number, number], ignoreIds?: string[]) => {
|
|
4
|
+
valid: boolean;
|
|
5
|
+
conflictIds: string[];
|
|
6
|
+
};
|
|
7
|
+
canPlaceOnWall: (levelId: LevelNode["id"], wallId: WallNode["id"], localX: number, localY: number, dimensions: [number, number, number], attachType?: "wall" | "wall-side", side?: "front" | "back", ignoreIds?: string[]) => {
|
|
8
|
+
valid: boolean;
|
|
9
|
+
conflictIds: string[];
|
|
10
|
+
};
|
|
11
|
+
canPlaceOnCeiling: (ceilingId: CeilingNode["id"], position: [number, number, number], dimensions: [number, number, number], rotation: [number, number, number], ignoreIds?: string[]) => {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
conflictIds: string[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=use-spatial-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-spatial-query.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/use-spatial-query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAGpE,wBAAgB,eAAe;+BAGhB,SAAS,CAAC,IAAI,CAAC,YACd,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,YAC1B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,MAAM,EAAE;;;;8BASX,SAAS,CAAC,IAAI,CAAC,UAChB,QAAQ,CAAC,IAAI,CAAC,UACd,MAAM,UACN,MAAM,cACF,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,eACxB,MAAM,GAAG,WAAW,SACzB,OAAO,GAAG,MAAM,cACX,MAAM,EAAE;;;;mCAkBT,WAAW,CAAC,IAAI,CAAC,YAClB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,YAC1B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,cACtB,MAAM,EAAE;;;;EAQzB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { spatialGridManager } from './spatial-grid-manager';
|
|
3
|
+
export function useSpatialQuery() {
|
|
4
|
+
const canPlaceOnFloor = useCallback((levelId, position, dimensions, rotation, ignoreIds) => {
|
|
5
|
+
return spatialGridManager.canPlaceOnFloor(levelId, position, dimensions, rotation, ignoreIds);
|
|
6
|
+
}, []);
|
|
7
|
+
const canPlaceOnWall = useCallback((levelId, wallId, localX, localY, dimensions, attachType = 'wall', side, ignoreIds) => {
|
|
8
|
+
return spatialGridManager.canPlaceOnWall(levelId, wallId, localX, localY, dimensions, attachType, side, ignoreIds);
|
|
9
|
+
}, []);
|
|
10
|
+
const canPlaceOnCeiling = useCallback((ceilingId, position, dimensions, rotation, ignoreIds) => {
|
|
11
|
+
return spatialGridManager.canPlaceOnCeiling(ceilingId, position, dimensions, rotation, ignoreIds);
|
|
12
|
+
}, []);
|
|
13
|
+
return { canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling };
|
|
14
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
type WallSide = 'front' | 'back';
|
|
2
|
+
type AttachType = 'wall' | 'wall-side';
|
|
3
|
+
interface WallItemPlacement {
|
|
4
|
+
itemId: string;
|
|
5
|
+
wallId: string;
|
|
6
|
+
tStart: number;
|
|
7
|
+
tEnd: number;
|
|
8
|
+
yStart: number;
|
|
9
|
+
yEnd: number;
|
|
10
|
+
attachType?: AttachType;
|
|
11
|
+
side?: WallSide;
|
|
12
|
+
}
|
|
13
|
+
export declare class WallSpatialGrid {
|
|
14
|
+
private wallItems;
|
|
15
|
+
private itemToWall;
|
|
16
|
+
/**
|
|
17
|
+
* Check if an item can be placed on a wall
|
|
18
|
+
* @param wallId - The wall to place on
|
|
19
|
+
* @param wallLength - Length of the wall
|
|
20
|
+
* @param wallHeight - Height of the wall
|
|
21
|
+
* @param tCenter - Parametric center position (0-1) along wall
|
|
22
|
+
* @param itemWidth - Width of the item
|
|
23
|
+
* @param yBottom - Bottom Y position of the item
|
|
24
|
+
* @param itemHeight - Height of the item
|
|
25
|
+
* @param attachType - 'wall' (blocks both sides) or 'wall-side' (blocks one side)
|
|
26
|
+
* @param side - Which side for 'wall-side' items
|
|
27
|
+
* @param ignoreIds - Item IDs to ignore in conflict check
|
|
28
|
+
*/
|
|
29
|
+
canPlaceOnWall(wallId: string, wallLength: number, wallHeight: number, tCenter: number, itemWidth: number, yBottom: number, itemHeight: number, attachType?: AttachType, side?: WallSide, ignoreIds?: string[]): {
|
|
30
|
+
valid: boolean;
|
|
31
|
+
conflictIds: string[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Check if two items conflict based on their attach types and sides
|
|
35
|
+
* - 'wall' items block both sides, so they conflict with everything
|
|
36
|
+
* - 'wall-side' items only conflict if they're on the same side or if the other is a 'wall' item
|
|
37
|
+
*/
|
|
38
|
+
private checkSideConflict;
|
|
39
|
+
insert(placement: WallItemPlacement): void;
|
|
40
|
+
remove(wallId: string, itemId: string): void;
|
|
41
|
+
removeByItemId(itemId: string): void;
|
|
42
|
+
removeWall(wallId: string): string[];
|
|
43
|
+
getWallForItem(itemId: string): string | undefined;
|
|
44
|
+
clear(): void;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=wall-spatial-grid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wall-spatial-grid.d.ts","sourceRoot":"","sources":["../../../src/hooks/spatial-grid/wall-spatial-grid.ts"],"names":[],"mappings":"AAAA,KAAK,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;AAChC,KAAK,UAAU,GAAG,MAAM,GAAG,WAAW,CAAA;AAKtC,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,IAAI,CAAC,EAAE,QAAQ,CAAA;CAChB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,UAAU,CAA4B;IAE9C;;;;;;;;;;;;OAYG;IACH,cAAc,CACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,GAAE,UAAmB,EAC/B,IAAI,CAAC,EAAE,QAAQ,EACf,SAAS,GAAE,MAAM,EAAO,GACvB;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE;IAoC5C;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IA0BzB,MAAM,CAAC,SAAS,EAAE,iBAAiB;IAUnC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IASrC,cAAc,CAAC,MAAM,EAAE,MAAM;IAQ7B,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAapC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIlD,KAAK;CAIN"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Small tolerance for floating point comparison to allow adjacent items
|
|
2
|
+
const EPSILON = 0.001;
|
|
3
|
+
export class WallSpatialGrid {
|
|
4
|
+
wallItems = new Map(); // wallId -> placements
|
|
5
|
+
itemToWall = new Map(); // itemId -> wallId (reverse lookup)
|
|
6
|
+
/**
|
|
7
|
+
* Check if an item can be placed on a wall
|
|
8
|
+
* @param wallId - The wall to place on
|
|
9
|
+
* @param wallLength - Length of the wall
|
|
10
|
+
* @param wallHeight - Height of the wall
|
|
11
|
+
* @param tCenter - Parametric center position (0-1) along wall
|
|
12
|
+
* @param itemWidth - Width of the item
|
|
13
|
+
* @param yBottom - Bottom Y position of the item
|
|
14
|
+
* @param itemHeight - Height of the item
|
|
15
|
+
* @param attachType - 'wall' (blocks both sides) or 'wall-side' (blocks one side)
|
|
16
|
+
* @param side - Which side for 'wall-side' items
|
|
17
|
+
* @param ignoreIds - Item IDs to ignore in conflict check
|
|
18
|
+
*/
|
|
19
|
+
canPlaceOnWall(wallId, wallLength, wallHeight, tCenter, itemWidth, yBottom, itemHeight, attachType = 'wall', side, ignoreIds = []) {
|
|
20
|
+
const halfW = itemWidth / wallLength / 2;
|
|
21
|
+
const tStart = tCenter - halfW;
|
|
22
|
+
const tEnd = tCenter + halfW;
|
|
23
|
+
// yBottom is the bottom of the item, so yEnd = yBottom + itemHeight
|
|
24
|
+
const yStart = yBottom;
|
|
25
|
+
const yEnd = yBottom + itemHeight;
|
|
26
|
+
// Check wall boundaries
|
|
27
|
+
if (tStart < 0 || tEnd > 1 || yStart < 0 || yEnd > wallHeight) {
|
|
28
|
+
return { valid: false, conflictIds: [] };
|
|
29
|
+
}
|
|
30
|
+
const existing = this.wallItems.get(wallId) ?? [];
|
|
31
|
+
const ignoreSet = new Set(ignoreIds);
|
|
32
|
+
const conflicts = [];
|
|
33
|
+
for (const placement of existing) {
|
|
34
|
+
if (ignoreSet.has(placement.itemId))
|
|
35
|
+
continue;
|
|
36
|
+
// Use EPSILON tolerance to allow items to be exactly adjacent
|
|
37
|
+
const tOverlap = tStart < placement.tEnd - EPSILON && tEnd > placement.tStart + EPSILON;
|
|
38
|
+
const yOverlap = yStart < placement.yEnd - EPSILON && yEnd > placement.yStart + EPSILON;
|
|
39
|
+
if (tOverlap && yOverlap) {
|
|
40
|
+
// Check side conflicts based on attach types
|
|
41
|
+
const hasConflict = this.checkSideConflict(attachType, side, placement);
|
|
42
|
+
if (hasConflict) {
|
|
43
|
+
conflicts.push(placement.itemId);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { valid: conflicts.length === 0, conflictIds: conflicts };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if two items conflict based on their attach types and sides
|
|
51
|
+
* - 'wall' items block both sides, so they conflict with everything
|
|
52
|
+
* - 'wall-side' items only conflict if they're on the same side or if the other is a 'wall' item
|
|
53
|
+
*/
|
|
54
|
+
checkSideConflict(newAttachType, newSide, existing) {
|
|
55
|
+
// Treat undefined/legacy attachType as 'wall' (blocks both sides)
|
|
56
|
+
const existingAttachType = existing.attachType ?? 'wall';
|
|
57
|
+
// If new item is 'wall' type, it conflicts with everything (needs both sides)
|
|
58
|
+
if (newAttachType === 'wall') {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
// If existing item is 'wall' type, it blocks both sides
|
|
62
|
+
if (existingAttachType === 'wall') {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
// Both are 'wall-side' - only conflict if they're on the same side
|
|
66
|
+
// If either side is undefined, be conservative and assume conflict
|
|
67
|
+
if (!newSide || !existing.side) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return newSide === existing.side;
|
|
71
|
+
}
|
|
72
|
+
insert(placement) {
|
|
73
|
+
const { wallId, itemId } = placement;
|
|
74
|
+
if (!this.wallItems.has(wallId)) {
|
|
75
|
+
this.wallItems.set(wallId, []);
|
|
76
|
+
}
|
|
77
|
+
this.wallItems.get(wallId).push(placement);
|
|
78
|
+
this.itemToWall.set(itemId, wallId);
|
|
79
|
+
}
|
|
80
|
+
remove(wallId, itemId) {
|
|
81
|
+
const items = this.wallItems.get(wallId);
|
|
82
|
+
if (items) {
|
|
83
|
+
const idx = items.findIndex((p) => p.itemId === itemId);
|
|
84
|
+
if (idx !== -1)
|
|
85
|
+
items.splice(idx, 1);
|
|
86
|
+
}
|
|
87
|
+
this.itemToWall.delete(itemId);
|
|
88
|
+
}
|
|
89
|
+
removeByItemId(itemId) {
|
|
90
|
+
const wallId = this.itemToWall.get(itemId);
|
|
91
|
+
if (wallId) {
|
|
92
|
+
this.remove(wallId, itemId);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Useful for when a wall is deleted - remove all items on it
|
|
96
|
+
removeWall(wallId) {
|
|
97
|
+
const items = this.wallItems.get(wallId) ?? [];
|
|
98
|
+
const removedIds = items.map((p) => p.itemId);
|
|
99
|
+
for (const itemId of removedIds) {
|
|
100
|
+
this.itemToWall.delete(itemId);
|
|
101
|
+
}
|
|
102
|
+
this.wallItems.delete(wallId);
|
|
103
|
+
return removedIds; // Return removed item IDs in case you need to delete them from scene
|
|
104
|
+
}
|
|
105
|
+
// Get which wall an item is on
|
|
106
|
+
getWallForItem(itemId) {
|
|
107
|
+
return this.itemToWall.get(itemId);
|
|
108
|
+
}
|
|
109
|
+
clear() {
|
|
110
|
+
this.wallItems.clear();
|
|
111
|
+
this.itemToWall.clear();
|
|
112
|
+
}
|
|
113
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type { BuildingEvent, CameraControlEvent, EventSuffix, GridEvent, ItemEvent, LevelEvent, NodeEvent, SlabEvent, WallEvent, ZoneEvent, CeilingEvent, RoofEvent, } from './events/bus';
|
|
2
|
+
export { emitter, eventSuffixes } from './events/bus';
|
|
3
|
+
export { sceneRegistry, useRegistry, } from './hooks/scene-registry/scene-registry';
|
|
4
|
+
export { initSpatialGridSync, resolveLevelId, } from './hooks/spatial-grid/spatial-grid-sync';
|
|
5
|
+
export { useSpatialQuery } from './hooks/spatial-grid/use-spatial-query';
|
|
6
|
+
export { pointInPolygon } from './hooks/spatial-grid/spatial-grid-manager';
|
|
7
|
+
export * from './schema';
|
|
8
|
+
export { default as useScene } from './store/use-scene';
|
|
9
|
+
export { CeilingSystem } from './systems/ceiling/ceiling-system';
|
|
10
|
+
export { ItemSystem } from './systems/item/item-system';
|
|
11
|
+
export { RoofSystem } from './systems/roof/roof-system';
|
|
12
|
+
export { SlabSystem } from './systems/slab/slab-system';
|
|
13
|
+
export { WallSystem } from './systems/wall/wall-system';
|
|
14
|
+
export { isObject } from './utils/types';
|
|
15
|
+
export { saveAsset, loadAssetUrl } from './lib/asset-storage';
|
|
16
|
+
export { detectSpacesForLevel, wallTouchesOthers, initSpaceDetectionSync, type Space } from './lib/space-detection';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,YAAY,EACZ,SAAS,GACV,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAErD,OAAO,EACL,aAAa,EACb,WAAW,GACZ,MAAM,uCAAuC,CAAA;AAC9C,OAAO,EACL,mBAAmB,EACnB,cAAc,GACf,MAAM,wCAAwC,CAAA;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,2CAA2C,CAAA;AAE1E,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AAEvD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAE7D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,KAAK,KAAK,EAAE,MAAM,uBAAuB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Store
|
|
2
|
+
// Events
|
|
3
|
+
export { emitter, eventSuffixes } from './events/bus';
|
|
4
|
+
// Hooks
|
|
5
|
+
export { sceneRegistry, useRegistry, } from './hooks/scene-registry/scene-registry';
|
|
6
|
+
export { initSpatialGridSync, resolveLevelId, } from './hooks/spatial-grid/spatial-grid-sync';
|
|
7
|
+
export { useSpatialQuery } from './hooks/spatial-grid/use-spatial-query';
|
|
8
|
+
export { pointInPolygon } from './hooks/spatial-grid/spatial-grid-manager';
|
|
9
|
+
// Schema
|
|
10
|
+
export * from './schema';
|
|
11
|
+
export { default as useScene } from './store/use-scene';
|
|
12
|
+
// Systems
|
|
13
|
+
export { CeilingSystem } from './systems/ceiling/ceiling-system';
|
|
14
|
+
export { ItemSystem } from './systems/item/item-system';
|
|
15
|
+
export { RoofSystem } from './systems/roof/roof-system';
|
|
16
|
+
export { SlabSystem } from './systems/slab/slab-system';
|
|
17
|
+
export { WallSystem } from './systems/wall/wall-system';
|
|
18
|
+
export { isObject } from './utils/types';
|
|
19
|
+
// Asset storage
|
|
20
|
+
export { saveAsset, loadAssetUrl } from './lib/asset-storage';
|
|
21
|
+
// Space detection
|
|
22
|
+
export { detectSpacesForLevel, wallTouchesOthers, initSpaceDetectionSync } from './lib/space-detection';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const ASSET_PREFIX = "asset_data:";
|
|
2
|
+
/**
|
|
3
|
+
* Save a file to IndexedDB and return a custom protocol URL
|
|
4
|
+
*/
|
|
5
|
+
export declare function saveAsset(file: File): Promise<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Load a file from IndexedDB and return an object URL
|
|
8
|
+
* If the URL is not a custom protocol URL, return it as is
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadAssetUrl(url: string): Promise<string | null>;
|
|
11
|
+
//# sourceMappingURL=asset-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-storage.d.ts","sourceRoot":"","sources":["../../src/lib/asset-storage.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,gBAAgB,CAAA;AAKzC;;GAEG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAI3D;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkCtE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { get, set } from 'idb-keyval';
|
|
2
|
+
export const ASSET_PREFIX = 'asset_data:';
|
|
3
|
+
// Cache for active object URLs to prevent leaks and flickering
|
|
4
|
+
const urlCache = new Map();
|
|
5
|
+
/**
|
|
6
|
+
* Save a file to IndexedDB and return a custom protocol URL
|
|
7
|
+
*/
|
|
8
|
+
export async function saveAsset(file) {
|
|
9
|
+
const id = crypto.randomUUID();
|
|
10
|
+
await set(`${ASSET_PREFIX}${id}`, file);
|
|
11
|
+
return `asset://${id}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Load a file from IndexedDB and return an object URL
|
|
15
|
+
* If the URL is not a custom protocol URL, return it as is
|
|
16
|
+
*/
|
|
17
|
+
export async function loadAssetUrl(url) {
|
|
18
|
+
if (!url)
|
|
19
|
+
return null;
|
|
20
|
+
// If it's already a blob or http URL, return as is
|
|
21
|
+
if (url.startsWith('blob:') || url.startsWith('http')) {
|
|
22
|
+
return url;
|
|
23
|
+
}
|
|
24
|
+
// Handle our custom asset protocol
|
|
25
|
+
if (url.startsWith('asset://')) {
|
|
26
|
+
const id = url.replace('asset://', '');
|
|
27
|
+
// Check cache first
|
|
28
|
+
if (urlCache.has(id)) {
|
|
29
|
+
return urlCache.get(id);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const file = await get(`${ASSET_PREFIX}${id}`);
|
|
33
|
+
if (!file) {
|
|
34
|
+
console.warn(`Asset not found: ${id}`);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const objectUrl = URL.createObjectURL(file);
|
|
38
|
+
urlCache.set(id, objectUrl);
|
|
39
|
+
return objectUrl;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error('Failed to load asset:', error);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Legacy data URLs are returned as is
|
|
47
|
+
return url;
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { WallNode } from '../schema';
|
|
2
|
+
export type Space = {
|
|
3
|
+
id: string;
|
|
4
|
+
levelId: string;
|
|
5
|
+
polygon: Array<[number, number]>;
|
|
6
|
+
wallIds: string[];
|
|
7
|
+
isExterior: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Initializes space detection sync with scene and editor stores
|
|
11
|
+
* Call this once during app initialization
|
|
12
|
+
*/
|
|
13
|
+
export declare function initSpaceDetectionSync(sceneStore: any, // useScene store
|
|
14
|
+
editorStore: any): () => void;
|
|
15
|
+
type WallSideUpdate = {
|
|
16
|
+
wallId: string;
|
|
17
|
+
frontSide: 'interior' | 'exterior' | 'unknown';
|
|
18
|
+
backSide: 'interior' | 'exterior' | 'unknown';
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Detects spaces for a level by flood-filling a grid from the edges
|
|
22
|
+
* Returns wall side updates and detected spaces
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectSpacesForLevel(levelId: string, walls: WallNode[], gridResolution?: number): {
|
|
25
|
+
wallUpdates: WallSideUpdate[];
|
|
26
|
+
spaces: Space[];
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a wall touches any other walls
|
|
30
|
+
* Used to determine if space detection should run
|
|
31
|
+
*/
|
|
32
|
+
export declare function wallTouchesOthers(wall: WallNode, otherWalls: WallNode[]): boolean;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=space-detection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"space-detection.d.ts","sourceRoot":"","sources":["../../src/lib/space-detection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAMzC,MAAM,MAAM,KAAK,GAAG;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAChC,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;CACpB,CAAA;AAMD;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,GAAG,EAAE,iBAAiB;AAClC,WAAW,EAAE,GAAG,GACf,MAAM,IAAI,CAgGZ;AA+DD,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAA;IAC9C,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAA;CAC9C,CAAA;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,QAAQ,EAAE,EACjB,cAAc,GAAE,MAAY,GAC3B;IACD,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,MAAM,EAAE,KAAK,EAAE,CAAA;CAChB,CAqBA;AAsWD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,OAAO,CAkBjF"}
|