@pascal-app/core 0.6.0 → 0.8.0
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 +45 -6
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/bus.js +1 -1
- package/dist/hooks/scene-registry/scene-registry.d.ts +2 -0
- package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
- package/dist/hooks/scene-registry/scene-registry.js +2 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +164 -6
- package/dist/index.d.ts +8 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -14
- package/dist/lib/door-operation.d.ts +7 -0
- package/dist/lib/door-operation.d.ts.map +1 -0
- package/dist/lib/door-operation.js +25 -0
- package/dist/lib/polygon-geometry.d.ts.map +1 -1
- package/dist/lib/slab-polygon.d.ts +3 -0
- package/dist/lib/slab-polygon.d.ts.map +1 -0
- package/dist/lib/slab-polygon.js +58 -0
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +10 -8
- package/dist/material-library.d.ts +5 -3
- package/dist/material-library.d.ts.map +1 -1
- package/dist/material-library.js +28 -32
- package/dist/schema/asset-url.d.ts +34 -0
- package/dist/schema/asset-url.d.ts.map +1 -0
- package/dist/schema/asset-url.js +79 -0
- package/dist/schema/asset-url.test.d.ts +2 -0
- package/dist/schema/asset-url.test.d.ts.map +1 -0
- package/dist/schema/asset-url.test.js +134 -0
- package/dist/schema/index.d.ts +7 -5
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +5 -3
- package/dist/schema/material.d.ts +1 -0
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +13 -11
- package/dist/schema/nodes/column.d.ts +520 -0
- package/dist/schema/nodes/column.d.ts.map +1 -0
- package/dist/schema/nodes/column.js +385 -0
- package/dist/schema/nodes/door.d.ts +72 -0
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/door.js +39 -2
- package/dist/schema/nodes/fence.d.ts +1 -1
- package/dist/schema/nodes/fence.js +2 -2
- package/dist/schema/nodes/guide.d.ts +17 -0
- package/dist/schema/nodes/guide.d.ts.map +1 -1
- package/dist/schema/nodes/guide.js +11 -1
- package/dist/schema/nodes/item.d.ts +20 -0
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +30 -1
- package/dist/schema/nodes/level.d.ts +1 -1
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +6 -0
- package/dist/schema/nodes/roof-segment.d.ts +2 -2
- package/dist/schema/nodes/roof.d.ts +2 -2
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +5 -5
- package/dist/schema/nodes/scan.d.ts.map +1 -1
- package/dist/schema/nodes/scan.js +2 -1
- package/dist/schema/nodes/site.d.ts +7 -0
- package/dist/schema/nodes/site.d.ts.map +1 -1
- package/dist/schema/nodes/spawn.d.ts +24 -0
- package/dist/schema/nodes/spawn.d.ts.map +1 -0
- package/dist/schema/nodes/spawn.js +8 -0
- package/dist/schema/nodes/stair.d.ts +6 -6
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +9 -7
- package/dist/schema/nodes/window.d.ts +55 -0
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +29 -0
- package/dist/schema/types.d.ts +320 -5
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +4 -0
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +13 -10
- package/dist/store/use-interactive.d.ts +43 -0
- package/dist/store/use-interactive.d.ts.map +1 -1
- package/dist/store/use-interactive.js +66 -0
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +69 -5
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-sync.js +41 -7
- package/dist/systems/stair/stair-opening-sync.test.d.ts +2 -0
- package/dist/systems/stair/stair-opening-sync.test.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-sync.test.js +63 -0
- package/dist/systems/wall/wall-curve.d.ts +1 -1
- package/dist/systems/wall/wall-curve.d.ts.map +1 -1
- package/dist/systems/wall/wall-curve.js +1 -1
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +2 -6
- package/package.json +34 -5
- package/dist/materials.d.ts +0 -10
- package/dist/materials.d.ts.map +0 -1
- package/dist/materials.js +0 -22
- package/dist/systems/ceiling/ceiling-system.d.ts +0 -8
- package/dist/systems/ceiling/ceiling-system.d.ts.map +0 -1
- package/dist/systems/ceiling/ceiling-system.js +0 -92
- package/dist/systems/door/door-system.d.ts +0 -2
- package/dist/systems/door/door-system.d.ts.map +0 -1
- package/dist/systems/door/door-system.js +0 -195
- package/dist/systems/fence/fence-system.d.ts +0 -2
- package/dist/systems/fence/fence-system.d.ts.map +0 -1
- package/dist/systems/fence/fence-system.js +0 -187
- package/dist/systems/item/item-system.d.ts +0 -2
- package/dist/systems/item/item-system.d.ts.map +0 -1
- package/dist/systems/item/item-system.js +0 -48
- package/dist/systems/roof/roof-system.d.ts +0 -16
- package/dist/systems/roof/roof-system.d.ts.map +0 -1
- package/dist/systems/roof/roof-system.js +0 -797
- package/dist/systems/slab/slab-system.d.ts +0 -8
- package/dist/systems/slab/slab-system.d.ts.map +0 -1
- package/dist/systems/slab/slab-system.js +0 -214
- package/dist/systems/stair/stair-system.d.ts +0 -2
- package/dist/systems/stair/stair-system.d.ts.map +0 -1
- package/dist/systems/stair/stair-system.js +0 -776
- package/dist/systems/wall/wall-system.d.ts +0 -12
- package/dist/systems/wall/wall-system.d.ts.map +0 -1
- package/dist/systems/wall/wall-system.js +0 -455
- package/dist/systems/window/window-system.d.ts +0 -2
- package/dist/systems/window/window-system.d.ts.map +0 -1
- package/dist/systems/window/window-system.js +0 -131
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import { useFrame } from '@react-three/fiber';
|
|
2
|
-
import * as THREE from 'three';
|
|
3
|
-
import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
|
|
4
|
-
import { baseMaterial, glassMaterial } from '../../materials';
|
|
5
|
-
import useScene from '../../store/use-scene';
|
|
6
|
-
// Invisible material for root mesh — used as selection hitbox only
|
|
7
|
-
const hitboxMaterial = new THREE.MeshBasicMaterial({ visible: false });
|
|
8
|
-
export const DoorSystem = () => {
|
|
9
|
-
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
10
|
-
const clearDirty = useScene((state) => state.clearDirty);
|
|
11
|
-
useFrame(() => {
|
|
12
|
-
if (dirtyNodes.size === 0)
|
|
13
|
-
return;
|
|
14
|
-
const nodes = useScene.getState().nodes;
|
|
15
|
-
dirtyNodes.forEach((id) => {
|
|
16
|
-
const node = nodes[id];
|
|
17
|
-
if (!node || node.type !== 'door')
|
|
18
|
-
return;
|
|
19
|
-
const mesh = sceneRegistry.nodes.get(id);
|
|
20
|
-
if (!mesh)
|
|
21
|
-
return; // Keep dirty until mesh mounts
|
|
22
|
-
updateDoorMesh(node, mesh);
|
|
23
|
-
clearDirty(id);
|
|
24
|
-
// Rebuild the parent wall so its cutout reflects the updated door geometry
|
|
25
|
-
if (node.parentId) {
|
|
26
|
-
useScene.getState().dirtyNodes.add(node.parentId);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
}, 3);
|
|
30
|
-
return null;
|
|
31
|
-
};
|
|
32
|
-
function addBox(parent, material, w, h, d, x, y, z) {
|
|
33
|
-
const m = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), material);
|
|
34
|
-
m.position.set(x, y, z);
|
|
35
|
-
parent.add(m);
|
|
36
|
-
}
|
|
37
|
-
function updateDoorMesh(node, mesh) {
|
|
38
|
-
// Root mesh is an invisible hitbox; all visuals live in child meshes
|
|
39
|
-
mesh.geometry.dispose();
|
|
40
|
-
mesh.geometry = new THREE.BoxGeometry(node.width, node.height, node.frameDepth);
|
|
41
|
-
mesh.material = hitboxMaterial;
|
|
42
|
-
// Sync transform from node (React may lag behind the system by a frame during drag)
|
|
43
|
-
mesh.position.set(node.position[0], node.position[1], node.position[2]);
|
|
44
|
-
mesh.rotation.set(node.rotation[0], node.rotation[1], node.rotation[2]);
|
|
45
|
-
// Dispose and remove all old visual children; preserve 'cutout'
|
|
46
|
-
for (const child of [...mesh.children]) {
|
|
47
|
-
if (child.name === 'cutout')
|
|
48
|
-
continue;
|
|
49
|
-
if (child instanceof THREE.Mesh)
|
|
50
|
-
child.geometry.dispose();
|
|
51
|
-
mesh.remove(child);
|
|
52
|
-
}
|
|
53
|
-
const { width, height, frameThickness, frameDepth, threshold, thresholdHeight, segments, handle, handleHeight, handleSide, doorCloser, panicBar, panicBarHeight, contentPadding, hingesSide, } = node;
|
|
54
|
-
// Leaf occupies the full opening (no bottom frame bar — door opens to floor)
|
|
55
|
-
const leafW = width - 2 * frameThickness;
|
|
56
|
-
const leafH = height - frameThickness; // only top frame
|
|
57
|
-
const leafDepth = 0.04;
|
|
58
|
-
// Leaf center is shifted down from door center by half the top frame
|
|
59
|
-
const leafCenterY = -frameThickness / 2;
|
|
60
|
-
// ── Frame members ──
|
|
61
|
-
// Left post — full height
|
|
62
|
-
addBox(mesh, baseMaterial, frameThickness, height, frameDepth, -width / 2 + frameThickness / 2, 0, 0);
|
|
63
|
-
// Right post — full height
|
|
64
|
-
addBox(mesh, baseMaterial, frameThickness, height, frameDepth, width / 2 - frameThickness / 2, 0, 0);
|
|
65
|
-
// Head (top bar) — full width
|
|
66
|
-
addBox(mesh, baseMaterial, width, frameThickness, frameDepth, 0, height / 2 - frameThickness / 2, 0);
|
|
67
|
-
// ── Threshold (inside the frame) ──
|
|
68
|
-
if (threshold) {
|
|
69
|
-
addBox(mesh, baseMaterial, leafW, thresholdHeight, frameDepth, 0, -height / 2 + thresholdHeight / 2, 0);
|
|
70
|
-
}
|
|
71
|
-
// ── Leaf — contentPadding border strips (no full backing; glass areas are open) ──
|
|
72
|
-
const cpX = contentPadding[0];
|
|
73
|
-
const cpY = contentPadding[1];
|
|
74
|
-
if (cpY > 0) {
|
|
75
|
-
// Top strip
|
|
76
|
-
addBox(mesh, baseMaterial, leafW, cpY, leafDepth, 0, leafCenterY + leafH / 2 - cpY / 2, 0);
|
|
77
|
-
// Bottom strip
|
|
78
|
-
addBox(mesh, baseMaterial, leafW, cpY, leafDepth, 0, leafCenterY - leafH / 2 + cpY / 2, 0);
|
|
79
|
-
}
|
|
80
|
-
if (cpX > 0) {
|
|
81
|
-
const innerH = leafH - 2 * cpY;
|
|
82
|
-
// Left strip
|
|
83
|
-
addBox(mesh, baseMaterial, cpX, innerH, leafDepth, -leafW / 2 + cpX / 2, leafCenterY, 0);
|
|
84
|
-
// Right strip
|
|
85
|
-
addBox(mesh, baseMaterial, cpX, innerH, leafDepth, leafW / 2 - cpX / 2, leafCenterY, 0);
|
|
86
|
-
}
|
|
87
|
-
// Content area inside padding
|
|
88
|
-
const contentW = leafW - 2 * cpX;
|
|
89
|
-
const contentH = leafH - 2 * cpY;
|
|
90
|
-
// ── Segments (stacked top to bottom within content area) ──
|
|
91
|
-
const totalRatio = segments.reduce((sum, s) => sum + s.heightRatio, 0);
|
|
92
|
-
const contentTop = leafCenterY + contentH / 2;
|
|
93
|
-
let segY = contentTop;
|
|
94
|
-
for (const seg of segments) {
|
|
95
|
-
const segH = (seg.heightRatio / totalRatio) * contentH;
|
|
96
|
-
const segCenterY = segY - segH / 2;
|
|
97
|
-
const numCols = seg.columnRatios.length;
|
|
98
|
-
const colSum = seg.columnRatios.reduce((a, b) => a + b, 0);
|
|
99
|
-
const usableW = contentW - (numCols - 1) * seg.dividerThickness;
|
|
100
|
-
const colWidths = seg.columnRatios.map((r) => (r / colSum) * usableW);
|
|
101
|
-
// Column x-centers (relative to mesh center)
|
|
102
|
-
const colXCenters = [];
|
|
103
|
-
let cx = -contentW / 2;
|
|
104
|
-
for (let c = 0; c < numCols; c++) {
|
|
105
|
-
colXCenters.push(cx + colWidths[c] / 2);
|
|
106
|
-
cx += colWidths[c];
|
|
107
|
-
if (c < numCols - 1)
|
|
108
|
-
cx += seg.dividerThickness;
|
|
109
|
-
}
|
|
110
|
-
// Column dividers within this segment
|
|
111
|
-
cx = -contentW / 2;
|
|
112
|
-
for (let c = 0; c < numCols - 1; c++) {
|
|
113
|
-
cx += colWidths[c];
|
|
114
|
-
addBox(mesh, baseMaterial, seg.dividerThickness, segH, leafDepth + 0.001, cx + seg.dividerThickness / 2, segCenterY, 0);
|
|
115
|
-
cx += seg.dividerThickness;
|
|
116
|
-
}
|
|
117
|
-
// Segment content per column
|
|
118
|
-
for (let c = 0; c < numCols; c++) {
|
|
119
|
-
const colW = colWidths[c];
|
|
120
|
-
const colX = colXCenters[c];
|
|
121
|
-
if (seg.type === 'glass') {
|
|
122
|
-
// Glass only — no opaque backing so it's truly transparent
|
|
123
|
-
const glassDepth = Math.max(0.004, leafDepth * 0.15);
|
|
124
|
-
addBox(mesh, glassMaterial, colW, segH, glassDepth, colX, segCenterY, 0);
|
|
125
|
-
}
|
|
126
|
-
else if (seg.type === 'panel') {
|
|
127
|
-
// Opaque leaf backing for this column
|
|
128
|
-
addBox(mesh, baseMaterial, colW, segH, leafDepth, colX, segCenterY, 0);
|
|
129
|
-
// Raised panel detail
|
|
130
|
-
const panelW = colW - 2 * seg.panelInset;
|
|
131
|
-
const panelH = segH - 2 * seg.panelInset;
|
|
132
|
-
if (panelW > 0.01 && panelH > 0.01) {
|
|
133
|
-
const effectiveDepth = Math.abs(seg.panelDepth) < 0.002 ? 0.005 : Math.abs(seg.panelDepth);
|
|
134
|
-
const panelZ = leafDepth / 2 + effectiveDepth / 2;
|
|
135
|
-
addBox(mesh, baseMaterial, panelW, panelH, effectiveDepth, colX, segCenterY, panelZ);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
// 'empty' — opaque backing, no detail
|
|
140
|
-
addBox(mesh, baseMaterial, colW, segH, leafDepth, colX, segCenterY, 0);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
segY -= segH;
|
|
144
|
-
}
|
|
145
|
-
// ── Handle ──
|
|
146
|
-
if (handle) {
|
|
147
|
-
// Convert from floor-based height to mesh-center-based Y
|
|
148
|
-
const handleY = handleHeight - height / 2;
|
|
149
|
-
// Handle grip sits on the front face (+Z) of the leaf
|
|
150
|
-
const faceZ = leafDepth / 2;
|
|
151
|
-
// X position: handleSide refers to which side the grip is on
|
|
152
|
-
const handleX = handleSide === 'right' ? leafW / 2 - 0.045 : -leafW / 2 + 0.045;
|
|
153
|
-
// Backplate
|
|
154
|
-
addBox(mesh, baseMaterial, 0.028, 0.14, 0.01, handleX, handleY, faceZ + 0.005);
|
|
155
|
-
// Grip lever
|
|
156
|
-
addBox(mesh, baseMaterial, 0.022, 0.1, 0.035, handleX, handleY, faceZ + 0.025);
|
|
157
|
-
}
|
|
158
|
-
// ── Door closer (commercial hardware at top) ──
|
|
159
|
-
if (doorCloser) {
|
|
160
|
-
const closerY = leafCenterY + leafH / 2 - 0.04;
|
|
161
|
-
// Body
|
|
162
|
-
addBox(mesh, baseMaterial, 0.28, 0.055, 0.055, 0, closerY, leafDepth / 2 + 0.03);
|
|
163
|
-
// Arm (simplified as thin bar to frame side)
|
|
164
|
-
addBox(mesh, baseMaterial, 0.14, 0.015, 0.015, leafW / 4, closerY + 0.025, leafDepth / 2 + 0.015);
|
|
165
|
-
}
|
|
166
|
-
// ── Panic bar ──
|
|
167
|
-
if (panicBar) {
|
|
168
|
-
const barY = panicBarHeight - height / 2;
|
|
169
|
-
addBox(mesh, baseMaterial, leafW * 0.72, 0.04, 0.055, 0, barY, leafDepth / 2 + 0.03);
|
|
170
|
-
}
|
|
171
|
-
// ── Hinges (3 knuckle-style hinges on the hinge side) ──
|
|
172
|
-
{
|
|
173
|
-
const hingeX = hingesSide === 'right' ? leafW / 2 - 0.012 : -leafW / 2 + 0.012;
|
|
174
|
-
const hingeZ = 0; // centered in leaf depth
|
|
175
|
-
const hingeH = 0.1;
|
|
176
|
-
const hingeW = 0.024;
|
|
177
|
-
const hingeD = leafDepth + 0.016;
|
|
178
|
-
// Bottom hinge ~0.25m from floor, middle hinge, top hinge ~0.25m from top
|
|
179
|
-
const leafBottom = leafCenterY - leafH / 2;
|
|
180
|
-
const leafTop = leafCenterY + leafH / 2;
|
|
181
|
-
addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeX, leafBottom + 0.25, hingeZ);
|
|
182
|
-
addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeX, (leafBottom + leafTop) / 2, hingeZ);
|
|
183
|
-
addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeX, leafTop - 0.25, hingeZ);
|
|
184
|
-
}
|
|
185
|
-
// ── Cutout (for wall CSG) — always full door dimensions, 1m deep ──
|
|
186
|
-
let cutout = mesh.getObjectByName('cutout');
|
|
187
|
-
if (!cutout) {
|
|
188
|
-
cutout = new THREE.Mesh();
|
|
189
|
-
cutout.name = 'cutout';
|
|
190
|
-
mesh.add(cutout);
|
|
191
|
-
}
|
|
192
|
-
cutout.geometry.dispose();
|
|
193
|
-
cutout.geometry = new THREE.BoxGeometry(node.width, node.height, 1.0);
|
|
194
|
-
cutout.visible = false;
|
|
195
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fence-system.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-system.tsx"],"names":[],"mappings":"AAyQA,eAAO,MAAM,WAAW,YAiBvB,CAAA"}
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { useFrame } from '@react-three/fiber';
|
|
2
|
-
import * as THREE from 'three';
|
|
3
|
-
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
4
|
-
import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
|
|
5
|
-
import useScene from '../../store/use-scene';
|
|
6
|
-
import { getWallCurveFrameAt, getWallCurveLength } from '../wall/wall-curve';
|
|
7
|
-
const MIN_CURVE_SEGMENT_LENGTH = 0.18;
|
|
8
|
-
function createFencePartGeometry(part) {
|
|
9
|
-
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
10
|
-
geometry.scale(part.scale[0], part.scale[1], part.scale[2]);
|
|
11
|
-
if (part.rotationY) {
|
|
12
|
-
geometry.rotateY(part.rotationY);
|
|
13
|
-
}
|
|
14
|
-
geometry.translate(part.position[0], part.position[1], part.position[2]);
|
|
15
|
-
applyFenceUVs(geometry);
|
|
16
|
-
return geometry;
|
|
17
|
-
}
|
|
18
|
-
function getFencePointAt(fence, t) {
|
|
19
|
-
const frame = getWallCurveFrameAt(fence, t);
|
|
20
|
-
return {
|
|
21
|
-
point: frame.point,
|
|
22
|
-
tangentAngle: Math.atan2(frame.tangent.y, frame.tangent.x),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
function createStraightFenceSpanPart(start, end, centerY, height, depth) {
|
|
26
|
-
const dx = end[0] - start[0];
|
|
27
|
-
const dz = end[1] - start[1];
|
|
28
|
-
const length = Math.hypot(dx, dz);
|
|
29
|
-
if (length <= 1e-4) {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
position: [(start[0] + end[0]) / 2, centerY, (start[1] + end[1]) / 2],
|
|
34
|
-
rotationY: -Math.atan2(dz, dx),
|
|
35
|
-
scale: [length, height, depth],
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
function createFenceCurveSpanParts(fence, startT, endT, centerY, height, depth) {
|
|
39
|
-
const parts = [];
|
|
40
|
-
const frameCount = Math.max(1, Math.ceil((getWallCurveLength(fence) * Math.max(1e-4, endT - startT)) / MIN_CURVE_SEGMENT_LENGTH));
|
|
41
|
-
let previous = getFencePointAt(fence, startT);
|
|
42
|
-
for (let index = 1; index <= frameCount; index += 1) {
|
|
43
|
-
const t = startT + (endT - startT) * (index / frameCount);
|
|
44
|
-
const current = getFencePointAt(fence, t);
|
|
45
|
-
const segment = createStraightFenceSpanPart([previous.point.x, previous.point.y], [current.point.x, current.point.y], centerY, height, depth);
|
|
46
|
-
if (segment) {
|
|
47
|
-
parts.push(segment);
|
|
48
|
-
}
|
|
49
|
-
previous = current;
|
|
50
|
-
}
|
|
51
|
-
return parts;
|
|
52
|
-
}
|
|
53
|
-
function applyFenceUVs(geometry) {
|
|
54
|
-
const position = geometry.getAttribute('position');
|
|
55
|
-
const normal = geometry.getAttribute('normal');
|
|
56
|
-
if (!(position && normal))
|
|
57
|
-
return;
|
|
58
|
-
const uvs = new Float32Array(position.count * 2);
|
|
59
|
-
let minX = Number.POSITIVE_INFINITY;
|
|
60
|
-
let minY = Number.POSITIVE_INFINITY;
|
|
61
|
-
let minZ = Number.POSITIVE_INFINITY;
|
|
62
|
-
for (let index = 0; index < position.count; index += 1) {
|
|
63
|
-
minX = Math.min(minX, position.getX(index));
|
|
64
|
-
minY = Math.min(minY, position.getY(index));
|
|
65
|
-
minZ = Math.min(minZ, position.getZ(index));
|
|
66
|
-
}
|
|
67
|
-
for (let index = 0; index < position.count; index += 1) {
|
|
68
|
-
const px = position.getX(index);
|
|
69
|
-
const py = position.getY(index);
|
|
70
|
-
const pz = position.getZ(index);
|
|
71
|
-
const nx = Math.abs(normal.getX(index));
|
|
72
|
-
const ny = Math.abs(normal.getY(index));
|
|
73
|
-
const nz = Math.abs(normal.getZ(index));
|
|
74
|
-
let u = 0;
|
|
75
|
-
let v = 0;
|
|
76
|
-
if (ny >= nx && ny >= nz) {
|
|
77
|
-
u = px - minX;
|
|
78
|
-
v = pz - minZ;
|
|
79
|
-
}
|
|
80
|
-
else if (nx >= nz) {
|
|
81
|
-
u = pz - minZ;
|
|
82
|
-
v = py - minY;
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
u = px - minX;
|
|
86
|
-
v = py - minY;
|
|
87
|
-
}
|
|
88
|
-
uvs[index * 2] = u;
|
|
89
|
-
uvs[index * 2 + 1] = v;
|
|
90
|
-
}
|
|
91
|
-
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
|
|
92
|
-
geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(uvs.slice(), 2));
|
|
93
|
-
}
|
|
94
|
-
function getStyleDefaults(style) {
|
|
95
|
-
if (style === 'privacy') {
|
|
96
|
-
return { spacingFactor: 0.42, postFactor: 1.35, baseFactor: 1.2, topFactor: 1.2 };
|
|
97
|
-
}
|
|
98
|
-
if (style === 'rail') {
|
|
99
|
-
return { spacingFactor: 0.68, postFactor: 0.8, baseFactor: 0.85, topFactor: 0.85 };
|
|
100
|
-
}
|
|
101
|
-
return { spacingFactor: 0.3, postFactor: 0.55, baseFactor: 1, topFactor: 0.75 };
|
|
102
|
-
}
|
|
103
|
-
function createFenceParts(fence) {
|
|
104
|
-
const parts = [];
|
|
105
|
-
const length = Math.max(getWallCurveLength(fence), 0.01);
|
|
106
|
-
const panelDepth = Math.max(fence.thickness, 0.03);
|
|
107
|
-
const clearance = Math.max(fence.groundClearance, 0);
|
|
108
|
-
const styleDefaults = getStyleDefaults(fence.style);
|
|
109
|
-
const baseHeight = Math.max(fence.baseHeight * styleDefaults.baseFactor, 0.04);
|
|
110
|
-
const topRailHeight = Math.max(fence.topRailHeight * styleDefaults.topFactor, 0.01);
|
|
111
|
-
const verticalHeight = Math.max(fence.height - baseHeight - topRailHeight, 0.08);
|
|
112
|
-
const postWidth = Math.max(fence.postSize * styleDefaults.postFactor, 0.01);
|
|
113
|
-
const spacing = Math.max(fence.postSpacing * styleDefaults.spacingFactor, postWidth * 1.2);
|
|
114
|
-
const edgeInset = Math.max(fence.edgeInset ?? 0.015, 0.005);
|
|
115
|
-
const isFloating = fence.baseStyle === 'floating';
|
|
116
|
-
const baseY = isFloating ? clearance : 0;
|
|
117
|
-
const effectiveBaseHeight = baseHeight;
|
|
118
|
-
const startInsetT = Math.min(0.499, edgeInset / length);
|
|
119
|
-
const endInsetT = Math.max(0.501, 1 - edgeInset / length);
|
|
120
|
-
if (!isFloating) {
|
|
121
|
-
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight / 2, effectiveBaseHeight, panelDepth * 1.05));
|
|
122
|
-
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight * 0.15, topRailHeight * 0.8, panelDepth * 0.35));
|
|
123
|
-
}
|
|
124
|
-
const count = Math.max(2, Math.floor((length - edgeInset * 2) / spacing) + 1);
|
|
125
|
-
const verticalY = baseY + effectiveBaseHeight + verticalHeight / 2;
|
|
126
|
-
for (let index = 0; index < count; index += 1) {
|
|
127
|
-
const t = count === 1 ? 0.5 : startInsetT + (endInsetT - startInsetT) * (index / (count - 1));
|
|
128
|
-
const frame = getFencePointAt(fence, t);
|
|
129
|
-
const isEdgePost = index === 0 || index === count - 1;
|
|
130
|
-
const postHeight = isFloating && isEdgePost
|
|
131
|
-
? effectiveBaseHeight + verticalHeight + topRailHeight + clearance
|
|
132
|
-
: verticalHeight;
|
|
133
|
-
const postY = isFloating && isEdgePost ? postHeight / 2 : verticalY;
|
|
134
|
-
parts.push({
|
|
135
|
-
position: [frame.point.x, postY, frame.point.y],
|
|
136
|
-
rotationY: -frame.tangentAngle,
|
|
137
|
-
scale: [postWidth, postHeight, Math.max(panelDepth * 0.35, 0.012)],
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + verticalHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
|
|
141
|
-
if (isFloating) {
|
|
142
|
-
parts.push(...createFenceCurveSpanParts(fence, 0, 1, baseY + effectiveBaseHeight + topRailHeight / 2, topRailHeight, Math.max(panelDepth * 0.55, 0.018)));
|
|
143
|
-
}
|
|
144
|
-
return parts;
|
|
145
|
-
}
|
|
146
|
-
function generateFenceGeometry(fence) {
|
|
147
|
-
const parts = createFenceParts(fence);
|
|
148
|
-
const geometries = parts.map(createFencePartGeometry);
|
|
149
|
-
const merged = mergeGeometries(geometries, false) ?? new THREE.BufferGeometry();
|
|
150
|
-
geometries.forEach((geometry) => geometry.dispose());
|
|
151
|
-
const mergedUv = merged.getAttribute('uv');
|
|
152
|
-
if (mergedUv) {
|
|
153
|
-
merged.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(mergedUv.array), 2));
|
|
154
|
-
}
|
|
155
|
-
merged.computeVertexNormals();
|
|
156
|
-
return merged;
|
|
157
|
-
}
|
|
158
|
-
function updateFenceGeometry(fenceId) {
|
|
159
|
-
const node = useScene.getState().nodes[fenceId];
|
|
160
|
-
if (!node || node.type !== 'fence')
|
|
161
|
-
return;
|
|
162
|
-
const mesh = sceneRegistry.nodes.get(fenceId);
|
|
163
|
-
if (!mesh)
|
|
164
|
-
return;
|
|
165
|
-
const newGeometry = generateFenceGeometry(node);
|
|
166
|
-
mesh.geometry.dispose();
|
|
167
|
-
mesh.geometry = newGeometry;
|
|
168
|
-
mesh.position.set(0, 0, 0);
|
|
169
|
-
mesh.rotation.set(0, 0, 0);
|
|
170
|
-
}
|
|
171
|
-
export const FenceSystem = () => {
|
|
172
|
-
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
173
|
-
const clearDirty = useScene((state) => state.clearDirty);
|
|
174
|
-
useFrame(() => {
|
|
175
|
-
if (dirtyNodes.size === 0)
|
|
176
|
-
return;
|
|
177
|
-
const nodes = useScene.getState().nodes;
|
|
178
|
-
dirtyNodes.forEach((id) => {
|
|
179
|
-
const node = nodes[id];
|
|
180
|
-
if (!node || node.type !== 'fence')
|
|
181
|
-
return;
|
|
182
|
-
updateFenceGeometry(id);
|
|
183
|
-
clearDirty(id);
|
|
184
|
-
});
|
|
185
|
-
}, 4);
|
|
186
|
-
return null;
|
|
187
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"item-system.d.ts","sourceRoot":"","sources":["../../../src/systems/item/item-system.tsx"],"names":[],"mappings":"AAYA,eAAO,MAAM,UAAU,YA6CtB,CAAA"}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { useFrame } from '@react-three/fiber';
|
|
2
|
-
import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
|
|
3
|
-
import { spatialGridManager } from '../../hooks/spatial-grid/spatial-grid-manager';
|
|
4
|
-
import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync';
|
|
5
|
-
import { getScaledDimensions } from '../../schema';
|
|
6
|
-
import useScene from '../../store/use-scene';
|
|
7
|
-
// ============================================================================
|
|
8
|
-
// ITEM SYSTEM
|
|
9
|
-
// ============================================================================
|
|
10
|
-
export const ItemSystem = () => {
|
|
11
|
-
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
12
|
-
const clearDirty = useScene((state) => state.clearDirty);
|
|
13
|
-
useFrame(() => {
|
|
14
|
-
if (dirtyNodes.size === 0)
|
|
15
|
-
return;
|
|
16
|
-
const nodes = useScene.getState().nodes;
|
|
17
|
-
dirtyNodes.forEach((id) => {
|
|
18
|
-
const node = nodes[id];
|
|
19
|
-
if (!node || node.type !== 'item')
|
|
20
|
-
return;
|
|
21
|
-
const item = node;
|
|
22
|
-
const mesh = sceneRegistry.nodes.get(id);
|
|
23
|
-
if (!mesh)
|
|
24
|
-
return;
|
|
25
|
-
if (item.asset.attachTo === 'wall-side') {
|
|
26
|
-
// Wall-attached item: offset Z by half the parent wall's thickness
|
|
27
|
-
const parentWall = item.parentId ? nodes[item.parentId] : undefined;
|
|
28
|
-
if (parentWall && parentWall.type === 'wall') {
|
|
29
|
-
const wallThickness = parentWall.thickness ?? 0.1;
|
|
30
|
-
const side = item.side === 'front' ? 1 : -1;
|
|
31
|
-
mesh.position.z = (wallThickness / 2) * side;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
else if (!item.asset.attachTo) {
|
|
35
|
-
// If parented to another item (surface placement), R3F handles positioning via the hierarchy
|
|
36
|
-
const parentNode = item.parentId ? nodes[item.parentId] : undefined;
|
|
37
|
-
if (parentNode?.type !== 'item') {
|
|
38
|
-
// Floor item: elevate by slab height (using full footprint overlap)
|
|
39
|
-
const levelId = resolveLevelId(item, nodes);
|
|
40
|
-
const slabElevation = spatialGridManager.getSlabElevationForItem(levelId, item.position, getScaledDimensions(item), item.rotation);
|
|
41
|
-
mesh.position.y = slabElevation + item.position[1];
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
clearDirty(id);
|
|
45
|
-
});
|
|
46
|
-
}, 2);
|
|
47
|
-
return null;
|
|
48
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
2
|
-
import { Brush } from 'three-bvh-csg';
|
|
3
|
-
import type { RoofSegmentNode } from '../../schema';
|
|
4
|
-
export declare const RoofSystem: () => null;
|
|
5
|
-
/**
|
|
6
|
-
* Generate complete hollow-shell geometry for a roof segment.
|
|
7
|
-
* Ports the prototype's CSG approach using three-bvh-csg.
|
|
8
|
-
*/
|
|
9
|
-
export declare function getRoofSegmentBrushes(node: RoofSegmentNode): {
|
|
10
|
-
deckSlab: Brush;
|
|
11
|
-
shinSlab: Brush;
|
|
12
|
-
wallBrush: Brush;
|
|
13
|
-
innerBrush: Brush;
|
|
14
|
-
} | null;
|
|
15
|
-
export declare function generateRoofSegmentGeometry(node: RoofSegmentNode): THREE.BufferGeometry;
|
|
16
|
-
//# sourceMappingURL=roof-system.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"roof-system.d.ts","sourceRoot":"","sources":["../../../src/systems/roof/roof-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAY,KAAK,EAA0B,MAAM,eAAe,CAAA;AAGvE,OAAO,KAAK,EAAgC,eAAe,EAAE,MAAM,cAAc,CAAA;AAgCjF,eAAO,MAAM,UAAU,YAuFtB,CAAA;AAwLD;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,eAAe,GACpB;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,KAAK,CAAA;CAAE,GAAG,IAAI,CAsRlF;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,eAAe,GAAG,KAAK,CAAC,cAAc,CAqDvF"}
|