@pascal-app/viewer 0.5.1 → 0.7.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/components/error-boundary.d.ts +7 -3
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js +3 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.d.ts.map +1 -1
- package/dist/components/renderers/ceiling/ceiling-renderer.js +21 -4
- package/dist/components/renderers/column/column-renderer.d.ts +5 -0
- package/dist/components/renderers/column/column-renderer.d.ts.map +1 -0
- package/dist/components/renderers/column/column-renderer.js +594 -0
- package/dist/components/renderers/door/door-renderer.d.ts.map +1 -1
- package/dist/components/renderers/door/door-renderer.js +8 -10
- package/dist/components/renderers/fence/fence-renderer.d.ts.map +1 -1
- package/dist/components/renderers/fence/fence-renderer.js +16 -2
- package/dist/components/renderers/guide/guide-renderer.js +1 -1
- package/dist/components/renderers/item/item-renderer.d.ts.map +1 -1
- package/dist/components/renderers/item/item-renderer.js +2 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +3 -1
- package/dist/components/renderers/roof/roof-renderer.d.ts.map +1 -1
- package/dist/components/renderers/roof/roof-renderer.js +19 -9
- package/dist/components/renderers/roof-segment/roof-segment-renderer.d.ts.map +1 -1
- package/dist/components/renderers/roof-segment/roof-segment-renderer.js +27 -8
- package/dist/components/renderers/site/site-renderer.d.ts.map +1 -1
- package/dist/components/renderers/site/site-renderer.js +7 -1
- package/dist/components/renderers/slab/slab-renderer.d.ts.map +1 -1
- package/dist/components/renderers/slab/slab-renderer.js +48 -7
- package/dist/components/renderers/spawn/spawn-renderer.d.ts +5 -0
- package/dist/components/renderers/spawn/spawn-renderer.d.ts.map +1 -0
- package/dist/components/renderers/spawn/spawn-renderer.js +32 -0
- package/dist/components/renderers/stair/stair-renderer.d.ts.map +1 -1
- package/dist/components/renderers/stair/stair-renderer.js +154 -39
- package/dist/components/renderers/stair-segment/stair-segment-renderer.d.ts.map +1 -1
- package/dist/components/renderers/stair-segment/stair-segment-renderer.js +22 -10
- package/dist/components/renderers/wall/wall-renderer.d.ts.map +1 -1
- package/dist/components/renderers/wall/wall-renderer.js +4 -9
- package/dist/components/renderers/window/window-renderer.d.ts.map +1 -1
- package/dist/components/renderers/window/window-renderer.js +5 -2
- package/dist/components/viewer/frame-limiter.d.ts +6 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -0
- package/dist/components/viewer/frame-limiter.js +31 -0
- package/dist/components/viewer/ground-occluder.d.ts.map +1 -1
- package/dist/components/viewer/ground-occluder.js +5 -1
- package/dist/components/viewer/index.d.ts +2 -0
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +90 -20
- package/dist/components/viewer/post-processing.d.ts +13 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +98 -44
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +31 -3
- package/dist/hooks/use-node-events.d.ts +9 -1
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +2 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/lib/materials.d.ts +7 -1
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +192 -2
- package/dist/lib/merged-outline-node.d.ts +2 -0
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +36 -9
- package/dist/store/use-viewer.d.ts +6 -6
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +2 -2
- 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 +91 -0
- package/dist/systems/column/column-geometry.d.ts +22 -0
- package/dist/systems/column/column-geometry.d.ts.map +1 -0
- package/dist/systems/column/column-geometry.js +108 -0
- package/dist/systems/door/door-animation-system.d.ts +2 -0
- package/dist/systems/door/door-animation-system.d.ts.map +1 -0
- package/dist/systems/door/door-animation-system.js +53 -0
- package/dist/systems/door/door-system.d.ts +2 -0
- package/dist/systems/door/door-system.d.ts.map +1 -0
- package/dist/systems/door/door-system.js +867 -0
- package/dist/systems/fence/fence-system.d.ts +2 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -0
- package/dist/systems/fence/fence-system.js +185 -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 +44 -0
- package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts +14 -0
- package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.d.ts.map +1 -0
- package/dist/systems/item-mesh-metadata/compute-item-mesh-metadata.js +155 -0
- package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts +6 -0
- package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.d.ts.map +1 -0
- package/dist/systems/item-mesh-metadata/item-mesh-metadata-system.js +75 -0
- package/dist/systems/item-mesh-metadata/sync-request.d.ts +7 -0
- package/dist/systems/item-mesh-metadata/sync-request.d.ts.map +1 -0
- package/dist/systems/item-mesh-metadata/sync-request.js +25 -0
- package/dist/systems/roof/roof-materials.d.ts +5 -0
- package/dist/systems/roof/roof-materials.d.ts.map +1 -0
- package/dist/systems/roof/roof-materials.js +46 -0
- package/dist/systems/roof/roof-system.d.ts +16 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -0
- package/dist/systems/roof/roof-system.js +797 -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 +155 -0
- package/dist/systems/stair/stair-materials.d.ts +7 -0
- package/dist/systems/stair/stair-materials.d.ts.map +1 -0
- package/dist/systems/stair/stair-materials.js +56 -0
- package/dist/systems/stair/stair-system.d.ts +2 -0
- package/dist/systems/stair/stair-system.d.ts.map +1 -0
- package/dist/systems/stair/stair-system.js +772 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +4 -123
- package/dist/systems/wall/wall-materials.d.ts +16 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -0
- package/dist/systems/wall/wall-materials.js +148 -0
- package/dist/systems/wall/wall-system.d.ts +11 -0
- package/dist/systems/wall/wall-system.d.ts.map +1 -0
- package/dist/systems/wall/wall-system.js +592 -0
- package/dist/systems/window/window-animation-system.d.ts +2 -0
- package/dist/systems/window/window-animation-system.d.ts.map +1 -0
- package/dist/systems/window/window-animation-system.js +135 -0
- package/dist/systems/window/window-system.d.ts +12 -0
- package/dist/systems/window/window-system.d.ts.map +1 -0
- package/dist/systems/window/window-system.js +1430 -0
- package/package.json +6 -4
package/dist/lib/materials.js
CHANGED
|
@@ -1,20 +1,204 @@
|
|
|
1
|
-
import { resolveMaterial } from '@pascal-app/core';
|
|
1
|
+
import { getMaterialPresetByRef, resolveMaterial, } from '@pascal-app/core';
|
|
2
2
|
import * as THREE from 'three';
|
|
3
|
+
import { MeshStandardNodeMaterial } from 'three/webgpu';
|
|
4
|
+
export const baseMaterial = new MeshStandardNodeMaterial({
|
|
5
|
+
color: '#f2f0ed',
|
|
6
|
+
roughness: 0.5,
|
|
7
|
+
metalness: 0.0,
|
|
8
|
+
});
|
|
9
|
+
export const glassMaterial = new MeshStandardNodeMaterial({
|
|
10
|
+
color: '#e0f2fe',
|
|
11
|
+
roughness: 0.05,
|
|
12
|
+
metalness: 0.0,
|
|
13
|
+
transparent: true,
|
|
14
|
+
opacity: 0.35,
|
|
15
|
+
side: THREE.DoubleSide,
|
|
16
|
+
});
|
|
3
17
|
const sideMap = {
|
|
4
18
|
front: THREE.FrontSide,
|
|
5
19
|
back: THREE.BackSide,
|
|
6
20
|
double: THREE.DoubleSide,
|
|
7
21
|
};
|
|
8
22
|
const materialCache = new Map();
|
|
23
|
+
const textureCache = new Map();
|
|
24
|
+
const textureLoadPromises = new Map();
|
|
25
|
+
const textureLoader = new THREE.TextureLoader();
|
|
26
|
+
const wrapMap = {
|
|
27
|
+
Repeat: THREE.RepeatWrapping,
|
|
28
|
+
ClampToEdge: THREE.ClampToEdgeWrapping,
|
|
29
|
+
MirroredRepeat: THREE.MirroredRepeatWrapping,
|
|
30
|
+
};
|
|
31
|
+
const SRGB_TEXTURE_SLOTS = ['map', 'emissiveMap'];
|
|
9
32
|
function getCacheKey(props) {
|
|
10
33
|
return `${props.color}-${props.roughness}-${props.metalness}-${props.opacity}-${props.transparent}-${props.side}`;
|
|
11
34
|
}
|
|
35
|
+
function getTextureKey(material) {
|
|
36
|
+
const texture = material?.texture;
|
|
37
|
+
if (!texture)
|
|
38
|
+
return 'none';
|
|
39
|
+
const repeat = texture.repeat?.join('x') ?? 'default';
|
|
40
|
+
const scale = texture.scale ?? 'default';
|
|
41
|
+
return `${texture.url}-${repeat}-${scale}`;
|
|
42
|
+
}
|
|
43
|
+
function getTexture(material) {
|
|
44
|
+
const textureConfig = material?.texture;
|
|
45
|
+
if (!textureConfig?.url)
|
|
46
|
+
return undefined;
|
|
47
|
+
const cacheKey = getTextureKey(material);
|
|
48
|
+
const cached = textureCache.get(cacheKey);
|
|
49
|
+
if (cached)
|
|
50
|
+
return cached;
|
|
51
|
+
const texture = textureLoader.load(textureConfig.url);
|
|
52
|
+
texture.wrapS = THREE.RepeatWrapping;
|
|
53
|
+
texture.wrapT = THREE.RepeatWrapping;
|
|
54
|
+
const repeatX = textureConfig.repeat?.[0] ?? textureConfig.scale ?? 1;
|
|
55
|
+
const repeatY = textureConfig.repeat?.[1] ?? textureConfig.scale ?? 1;
|
|
56
|
+
texture.repeat.set(repeatX, repeatY);
|
|
57
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
58
|
+
textureCache.set(cacheKey, texture);
|
|
59
|
+
return texture;
|
|
60
|
+
}
|
|
61
|
+
function isStandardMaterial(material) {
|
|
62
|
+
return (material instanceof THREE.MeshStandardMaterial ||
|
|
63
|
+
material instanceof THREE.MeshPhysicalMaterial);
|
|
64
|
+
}
|
|
65
|
+
function applyTextureProperties(texture, props, slot) {
|
|
66
|
+
texture.wrapS = wrapMap[props.wrapS];
|
|
67
|
+
texture.wrapT = wrapMap[props.wrapT];
|
|
68
|
+
texture.repeat.set(props.repeatX, props.repeatY);
|
|
69
|
+
texture.rotation = props.rotation;
|
|
70
|
+
texture.flipY = props.flipY;
|
|
71
|
+
texture.colorSpace = SRGB_TEXTURE_SLOTS.includes(slot ?? 'map')
|
|
72
|
+
? THREE.SRGBColorSpace
|
|
73
|
+
: THREE.NoColorSpace;
|
|
74
|
+
texture.needsUpdate = true;
|
|
75
|
+
return texture;
|
|
76
|
+
}
|
|
77
|
+
function getPresetTextureCacheKey(path, props, slot) {
|
|
78
|
+
return `${path}-${props.repeatX}-${props.repeatY}-${props.rotation}-${props.wrapS}-${props.wrapT}-${props.flipY}-${slot ?? 'map'}`;
|
|
79
|
+
}
|
|
80
|
+
function getPresetTexture(path, props, slot) {
|
|
81
|
+
const cacheKey = getPresetTextureCacheKey(path, props, slot);
|
|
82
|
+
const cached = textureCache.get(cacheKey);
|
|
83
|
+
if (cached)
|
|
84
|
+
return cached;
|
|
85
|
+
const texture = textureLoader.load(path);
|
|
86
|
+
applyTextureProperties(texture, props, slot);
|
|
87
|
+
textureCache.set(cacheKey, texture);
|
|
88
|
+
return texture;
|
|
89
|
+
}
|
|
90
|
+
async function loadPresetTexture(path, props, slot) {
|
|
91
|
+
const cacheKey = getPresetTextureCacheKey(path, props, slot);
|
|
92
|
+
const cached = textureCache.get(cacheKey);
|
|
93
|
+
if (cached)
|
|
94
|
+
return cached;
|
|
95
|
+
const existingPromise = textureLoadPromises.get(cacheKey);
|
|
96
|
+
if (existingPromise)
|
|
97
|
+
return existingPromise;
|
|
98
|
+
const promise = textureLoader
|
|
99
|
+
.loadAsync(path)
|
|
100
|
+
.then((texture) => {
|
|
101
|
+
applyTextureProperties(texture, props, slot);
|
|
102
|
+
textureCache.set(cacheKey, texture);
|
|
103
|
+
textureLoadPromises.delete(cacheKey);
|
|
104
|
+
return texture;
|
|
105
|
+
})
|
|
106
|
+
.catch((error) => {
|
|
107
|
+
console.warn('[viewer] Failed to load material texture', path, error);
|
|
108
|
+
textureLoadPromises.delete(cacheKey);
|
|
109
|
+
return null;
|
|
110
|
+
});
|
|
111
|
+
textureLoadPromises.set(cacheKey, promise);
|
|
112
|
+
return promise;
|
|
113
|
+
}
|
|
114
|
+
function queueTextureAssignment(material, slot, path, props) {
|
|
115
|
+
if (!path) {
|
|
116
|
+
material[slot] = null;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const cacheKey = getPresetTextureCacheKey(path, props, slot);
|
|
120
|
+
const cached = textureCache.get(cacheKey);
|
|
121
|
+
if (cached) {
|
|
122
|
+
material[slot] = cached;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
material[slot] = null;
|
|
126
|
+
void loadPresetTexture(path, props, slot).then((texture) => {
|
|
127
|
+
if (!texture)
|
|
128
|
+
return;
|
|
129
|
+
material[slot] = texture;
|
|
130
|
+
material.needsUpdate = true;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function applyMaterialMapProperties(material, mapProperties) {
|
|
134
|
+
material.color.set(mapProperties.color);
|
|
135
|
+
material.roughness = mapProperties.roughness;
|
|
136
|
+
material.metalness = mapProperties.metalness;
|
|
137
|
+
material.emissiveIntensity = mapProperties.emissiveIntensity;
|
|
138
|
+
material.emissive.set(mapProperties.emissiveColor);
|
|
139
|
+
material.displacementScale = mapProperties.displacementScale;
|
|
140
|
+
material.bumpScale = mapProperties.bumpScale;
|
|
141
|
+
material.aoMapIntensity = mapProperties.aoMapIntensity;
|
|
142
|
+
material.lightMapIntensity = mapProperties.lightMapIntensity;
|
|
143
|
+
material.transparent = mapProperties.transparent;
|
|
144
|
+
material.opacity = mapProperties.opacity;
|
|
145
|
+
material.side =
|
|
146
|
+
mapProperties.side === 0
|
|
147
|
+
? THREE.FrontSide
|
|
148
|
+
: mapProperties.side === 1
|
|
149
|
+
? THREE.BackSide
|
|
150
|
+
: THREE.DoubleSide;
|
|
151
|
+
material.normalScale.set(mapProperties.normalScaleX, mapProperties.normalScaleY);
|
|
152
|
+
material.needsUpdate = true;
|
|
153
|
+
}
|
|
154
|
+
function applyMaterialPresetTextures(material, preset) {
|
|
155
|
+
const { maps, mapProperties } = preset;
|
|
156
|
+
queueTextureAssignment(material, 'map', maps.albedoMap, mapProperties);
|
|
157
|
+
queueTextureAssignment(material, 'normalMap', maps.normalMap, mapProperties);
|
|
158
|
+
queueTextureAssignment(material, 'roughnessMap', maps.roughnessMap, mapProperties);
|
|
159
|
+
queueTextureAssignment(material, 'metalnessMap', maps.metalnessMap, mapProperties);
|
|
160
|
+
queueTextureAssignment(material, 'displacementMap', maps.displacementMap, mapProperties);
|
|
161
|
+
queueTextureAssignment(material, 'aoMap', maps.aoMap, mapProperties);
|
|
162
|
+
queueTextureAssignment(material, 'bumpMap', maps.bumpMap, mapProperties);
|
|
163
|
+
queueTextureAssignment(material, 'alphaMap', maps.alphaMap, mapProperties);
|
|
164
|
+
queueTextureAssignment(material, 'lightMap', maps.lightMap, mapProperties);
|
|
165
|
+
queueTextureAssignment(material, 'emissiveMap', maps.emissiveMap, mapProperties);
|
|
166
|
+
material.needsUpdate = true;
|
|
167
|
+
}
|
|
168
|
+
export function applyMaterialPresetToMaterials(materialInput, preset) {
|
|
169
|
+
if (!preset)
|
|
170
|
+
return;
|
|
171
|
+
const materials = (Array.isArray(materialInput) ? materialInput : [materialInput]).filter(isStandardMaterial);
|
|
172
|
+
if (materials.length === 0)
|
|
173
|
+
return;
|
|
174
|
+
for (const material of materials) {
|
|
175
|
+
applyMaterialMapProperties(material, preset.mapProperties);
|
|
176
|
+
applyMaterialPresetTextures(material, preset);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export function createMaterialFromPreset(preset) {
|
|
180
|
+
const cacheKey = JSON.stringify(preset);
|
|
181
|
+
if (materialCache.has(cacheKey)) {
|
|
182
|
+
return materialCache.get(cacheKey);
|
|
183
|
+
}
|
|
184
|
+
const material = new THREE.MeshStandardMaterial();
|
|
185
|
+
applyMaterialPresetToMaterials(material, preset);
|
|
186
|
+
materialCache.set(cacheKey, material);
|
|
187
|
+
return material;
|
|
188
|
+
}
|
|
189
|
+
export function createMaterialFromPresetRef(materialPreset) {
|
|
190
|
+
const preset = getMaterialPresetByRef(materialPreset);
|
|
191
|
+
if (!preset)
|
|
192
|
+
return null;
|
|
193
|
+
return createMaterialFromPreset(preset);
|
|
194
|
+
}
|
|
12
195
|
export function createMaterial(material) {
|
|
13
196
|
const props = resolveMaterial(material);
|
|
14
|
-
const cacheKey = getCacheKey(props)
|
|
197
|
+
const cacheKey = `${getCacheKey(props)}-${getTextureKey(material)}`;
|
|
15
198
|
if (materialCache.has(cacheKey)) {
|
|
16
199
|
return materialCache.get(cacheKey);
|
|
17
200
|
}
|
|
201
|
+
const map = getTexture(material);
|
|
18
202
|
const threeMaterial = new THREE.MeshStandardMaterial({
|
|
19
203
|
color: props.color,
|
|
20
204
|
roughness: props.roughness,
|
|
@@ -22,6 +206,7 @@ export function createMaterial(material) {
|
|
|
22
206
|
opacity: props.opacity,
|
|
23
207
|
transparent: props.transparent,
|
|
24
208
|
side: sideMap[props.side],
|
|
209
|
+
map,
|
|
25
210
|
});
|
|
26
211
|
materialCache.set(cacheKey, threeMaterial);
|
|
27
212
|
return threeMaterial;
|
|
@@ -56,4 +241,9 @@ export function clearMaterialCache() {
|
|
|
56
241
|
material.dispose();
|
|
57
242
|
}
|
|
58
243
|
materialCache.clear();
|
|
244
|
+
for (const texture of textureCache.values()) {
|
|
245
|
+
texture.dispose();
|
|
246
|
+
}
|
|
247
|
+
textureCache.clear();
|
|
248
|
+
textureLoadPromises.clear();
|
|
59
249
|
}
|
|
@@ -62,6 +62,8 @@ export declare class MergedOutlineNode extends TempNode {
|
|
|
62
62
|
private readonly _compositeMatB;
|
|
63
63
|
private readonly _cacheA;
|
|
64
64
|
private readonly _cacheB;
|
|
65
|
+
private _wroteGroupALastFrame;
|
|
66
|
+
private _wroteGroupBLastFrame;
|
|
65
67
|
private readonly _textureNodeA;
|
|
66
68
|
private readonly _textureNodeB;
|
|
67
69
|
constructor(scene: any, camera: any, params?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merged-outline-node.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAA2B,KAAK,QAAQ,EAAyB,MAAM,OAAO,CAAA;AAyBrF,OAAO,EAML,QAAQ,EACT,MAAM,cAAc,CAAA;AA8DrB,qBAAa,iBAAkB,SAAQ,QAAQ;IAC7C,MAAM,KAAK,IAAI,WAEd;IAED,KAAK,EAAE,GAAG,CAAA;IACV,MAAM,EAAE,GAAG,CAAA;IACX,cAAc,EAAE,QAAQ,EAAE,CAAA;IAC1B,gBAAgB,EAAE,QAAQ,EAAE,CAAA;IAC5B,wBAAwB,EAAE,GAAG,CAAA;IAC7B,0BAA0B,EAAE,GAAG,CAAA;IAC/B,mBAAmB,EAAE,GAAG,CAAA;IACxB,qBAAqB,EAAE,GAAG,CAAA;IAC1B,eAAe,EAAE,MAAM,CAAA;IACvB,gBAAgB,EAAE,MAAM,CAAA;IAExB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAK;IAEtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAK;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAK;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAK;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAK;IAC/B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAK;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAK;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAK;IACrC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAK;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAK;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAK;IAEhC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAc;IAC7C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAoB;IACzD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAc;IAC/C,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAoB;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAc;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAc;IAC7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAc;IAC/C,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAoB;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAc;IAC9C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAc;IAE7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;
|
|
1
|
+
{"version":3,"file":"merged-outline-node.d.ts","sourceRoot":"","sources":["../../src/lib/merged-outline-node.ts"],"names":[],"mappings":"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"}
|
|
@@ -120,6 +120,13 @@ export class MergedOutlineNode extends TempNode {
|
|
|
120
120
|
_compositeMatB;
|
|
121
121
|
_cacheA = new Set();
|
|
122
122
|
_cacheB = new Set();
|
|
123
|
+
// Tracks whether either group rendered last frame. We use this to decide
|
|
124
|
+
// when it's safe to skip renderer state manipulation entirely — touching
|
|
125
|
+
// the renderer (resetRendererAndSceneState + setRenderTarget + clearColor)
|
|
126
|
+
// corrupts the FBO state on the WebGL2 backend (iOS Chrome fallback) and
|
|
127
|
+
// the subsequent scene render comes out blank.
|
|
128
|
+
_wroteGroupALastFrame = false;
|
|
129
|
+
_wroteGroupBLastFrame = false;
|
|
123
130
|
_textureNodeA;
|
|
124
131
|
_textureNodeB;
|
|
125
132
|
constructor(scene, camera, params = {}) {
|
|
@@ -213,27 +220,40 @@ export class MergedOutlineNode extends TempNode {
|
|
|
213
220
|
updateBefore(frame) {
|
|
214
221
|
const hasPrimary = this.primaryObjects.length > 0;
|
|
215
222
|
const hasSecondary = this.secondaryObjects.length > 0;
|
|
223
|
+
const hasAny = hasPrimary || hasSecondary;
|
|
224
|
+
// Fast-path: nothing to render and nothing was rendered last frame either,
|
|
225
|
+
// so there are no stale composites to clear. Touch nothing — on the WebGL2
|
|
226
|
+
// backend (iOS Chrome fallback) even an empty reset/setRenderTarget cycle
|
|
227
|
+
// corrupts the framebuffer state and the next scene render goes blank.
|
|
228
|
+
const needsCleanupA = !hasPrimary && this._wroteGroupALastFrame;
|
|
229
|
+
const needsCleanupB = !hasSecondary && this._wroteGroupBLastFrame;
|
|
230
|
+
if (!(hasAny || needsCleanupA || needsCleanupB)) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
216
233
|
const { renderer } = frame;
|
|
217
234
|
const { camera, scene } = this;
|
|
218
235
|
_rendererState = RendererUtils.resetRendererAndSceneState(renderer, scene, _rendererState);
|
|
219
236
|
const size = renderer.getDrawingBufferSize(_size);
|
|
220
237
|
this.setSize(size.width, size.height);
|
|
221
|
-
// Clear composites for
|
|
222
|
-
//
|
|
223
|
-
if (
|
|
238
|
+
// Clear composites for groups that just transitioned from "has content"
|
|
239
|
+
// to "empty" — without this, the previous outline lingers on the GPU.
|
|
240
|
+
if (needsCleanupA) {
|
|
224
241
|
renderer.setRenderTarget(this._groupA.composite);
|
|
225
242
|
renderer.clearColor();
|
|
243
|
+
this._wroteGroupALastFrame = false;
|
|
226
244
|
}
|
|
227
|
-
if (
|
|
245
|
+
if (needsCleanupB) {
|
|
228
246
|
renderer.setRenderTarget(this._groupB.composite);
|
|
229
247
|
renderer.clearColor();
|
|
248
|
+
this._wroteGroupBLastFrame = false;
|
|
230
249
|
}
|
|
231
|
-
const hasAny = hasPrimary || hasSecondary;
|
|
232
250
|
if (!hasAny) {
|
|
233
251
|
RendererUtils.restoreRendererAndSceneState(renderer, scene, _rendererState);
|
|
234
252
|
return;
|
|
235
253
|
}
|
|
236
254
|
renderer.setClearColor(0xff_ff_ff, 1);
|
|
255
|
+
this._wroteGroupALastFrame = hasPrimary;
|
|
256
|
+
this._wroteGroupBLastFrame = hasSecondary;
|
|
237
257
|
if (hasPrimary)
|
|
238
258
|
this._buildCache(this.primaryObjects, this._cacheA);
|
|
239
259
|
if (hasSecondary)
|
|
@@ -440,10 +460,17 @@ export class MergedOutlineNode extends TempNode {
|
|
|
440
460
|
}
|
|
441
461
|
_buildCache(objects, cache) {
|
|
442
462
|
for (const obj of objects) {
|
|
443
|
-
obj.traverse
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
463
|
+
if (!obj || !obj.traverse)
|
|
464
|
+
continue;
|
|
465
|
+
try {
|
|
466
|
+
obj.traverse((child) => {
|
|
467
|
+
if (child.isMesh || child.isSprite)
|
|
468
|
+
cache.add(child);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
// Skip objects that were disposed or removed from the scene graph
|
|
473
|
+
}
|
|
447
474
|
}
|
|
448
475
|
}
|
|
449
476
|
}
|
|
@@ -14,8 +14,8 @@ type ViewerState = {
|
|
|
14
14
|
selection: SelectionPath;
|
|
15
15
|
previewSelectedIds: BaseNode['id'][];
|
|
16
16
|
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void;
|
|
17
|
-
hoverHighlightMode:
|
|
18
|
-
setHoverHighlightMode: (mode:
|
|
17
|
+
hoverHighlightMode: string;
|
|
18
|
+
setHoverHighlightMode: (mode: string) => void;
|
|
19
19
|
hoveredId: AnyNode['id'] | ZoneNode['id'] | null;
|
|
20
20
|
setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void;
|
|
21
21
|
cameraMode: 'perspective' | 'orthographic';
|
|
@@ -61,8 +61,8 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
61
61
|
cameraMode: "perspective" | "orthographic";
|
|
62
62
|
theme: "light" | "dark";
|
|
63
63
|
unit: "metric" | "imperial";
|
|
64
|
-
levelMode: "
|
|
65
|
-
wallMode: "up" | "
|
|
64
|
+
levelMode: "manual" | "stacked" | "exploded" | "solo";
|
|
65
|
+
wallMode: "up" | "down" | "cutaway";
|
|
66
66
|
projectPreferences: Record<string, {
|
|
67
67
|
showScans?: boolean;
|
|
68
68
|
showGuides?: boolean;
|
|
@@ -78,8 +78,8 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
78
78
|
cameraMode: "perspective" | "orthographic";
|
|
79
79
|
theme: "light" | "dark";
|
|
80
80
|
unit: "metric" | "imperial";
|
|
81
|
-
levelMode: "
|
|
82
|
-
wallMode: "up" | "
|
|
81
|
+
levelMode: "manual" | "stacked" | "exploded" | "solo";
|
|
82
|
+
wallMode: "up" | "down" | "cutaway";
|
|
83
83
|
projectPreferences: Record<string, {
|
|
84
84
|
showScans?: boolean;
|
|
85
85
|
showGuides?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAKrC,KAAK,aAAa,GAAG;IACnB,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACrC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC/B,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC7B,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAA;CAC9B,CAAA;AAED,KAAK,QAAQ,GAAG;IACd,eAAe,EAAE,QAAQ,EAAE,CAAA;IAC3B,cAAc,EAAE,QAAQ,EAAE,CAAA;CAC3B,CAAA;AAED,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,aAAa,CAAA;IACxB,kBAAkB,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAA;IACpC,qBAAqB,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,CAAA;IACtD,kBAAkB,EAAE,
|
|
1
|
+
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
|
package/dist/store/use-viewer.js
CHANGED
|
@@ -6,9 +6,9 @@ const useViewer = create()(persist((set) => ({
|
|
|
6
6
|
previewSelectedIds: [],
|
|
7
7
|
setPreviewSelectedIds: (ids) => set({ previewSelectedIds: ids }),
|
|
8
8
|
hoverHighlightMode: 'default',
|
|
9
|
-
setHoverHighlightMode: (mode) => set({ hoverHighlightMode: mode }),
|
|
9
|
+
setHoverHighlightMode: (mode) => set((state) => (state.hoverHighlightMode === mode ? state : { hoverHighlightMode: mode })),
|
|
10
10
|
hoveredId: null,
|
|
11
|
-
setHoveredId: (id) => set({ hoveredId: id }),
|
|
11
|
+
setHoveredId: (id) => set((state) => (state.hoveredId === id ? state : { hoveredId: id })),
|
|
12
12
|
cameraMode: 'perspective',
|
|
13
13
|
setCameraMode: (mode) => set({ cameraMode: mode }),
|
|
14
14
|
theme: 'light',
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type CeilingNode } from '@pascal-app/core';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
export declare const CeilingSystem: () => null;
|
|
4
|
+
/**
|
|
5
|
+
* Generates flat ceiling geometry from polygon (no extrusion)
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateCeilingGeometry(ceilingNode: CeilingNode): THREE.BufferGeometry;
|
|
8
|
+
//# sourceMappingURL=ceiling-system.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,WAAW,EAA2B,MAAM,kBAAkB,CAAA;AAC5F,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAa9B,eAAO,MAAM,aAAa,YAuBzB,CAAA;AAqBD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,KAAK,CAAC,cAAc,CAgDtF"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useFrame } from '@react-three/fiber';
|
|
2
|
+
import { sceneRegistry, useScene } from '@pascal-app/core';
|
|
3
|
+
import * as THREE from 'three';
|
|
4
|
+
function ensureUv2Attribute(geometry) {
|
|
5
|
+
const uv = geometry.getAttribute('uv');
|
|
6
|
+
if (!uv)
|
|
7
|
+
return;
|
|
8
|
+
geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(uv.array), 2));
|
|
9
|
+
}
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// CEILING SYSTEM
|
|
12
|
+
// ============================================================================
|
|
13
|
+
export const CeilingSystem = () => {
|
|
14
|
+
const dirtyNodes = useScene((state) => state.dirtyNodes);
|
|
15
|
+
const clearDirty = useScene((state) => state.clearDirty);
|
|
16
|
+
useFrame(() => {
|
|
17
|
+
if (dirtyNodes.size === 0)
|
|
18
|
+
return;
|
|
19
|
+
const nodes = useScene.getState().nodes;
|
|
20
|
+
// Process dirty ceilings
|
|
21
|
+
dirtyNodes.forEach((id) => {
|
|
22
|
+
const node = nodes[id];
|
|
23
|
+
if (!node || node.type !== 'ceiling')
|
|
24
|
+
return;
|
|
25
|
+
const mesh = sceneRegistry.nodes.get(id);
|
|
26
|
+
if (mesh) {
|
|
27
|
+
updateCeilingGeometry(node, mesh);
|
|
28
|
+
clearDirty(id);
|
|
29
|
+
}
|
|
30
|
+
// If mesh not found, keep it dirty for next frame
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Updates the geometry for a single ceiling
|
|
37
|
+
*/
|
|
38
|
+
function updateCeilingGeometry(node, mesh) {
|
|
39
|
+
const newGeo = generateCeilingGeometry(node);
|
|
40
|
+
mesh.geometry.dispose();
|
|
41
|
+
mesh.geometry = newGeo;
|
|
42
|
+
const gridMesh = mesh.getObjectByName('ceiling-grid');
|
|
43
|
+
if (gridMesh) {
|
|
44
|
+
gridMesh.geometry.dispose();
|
|
45
|
+
gridMesh.geometry = newGeo;
|
|
46
|
+
}
|
|
47
|
+
// Position at the ceiling height
|
|
48
|
+
mesh.position.y = (node.height ?? 2.5) - 0.01; // Slight offset to avoid z-fighting with upper-level slabs
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generates flat ceiling geometry from polygon (no extrusion)
|
|
52
|
+
*/
|
|
53
|
+
export function generateCeilingGeometry(ceilingNode) {
|
|
54
|
+
const polygon = ceilingNode.polygon;
|
|
55
|
+
if (polygon.length < 3) {
|
|
56
|
+
return new THREE.BufferGeometry();
|
|
57
|
+
}
|
|
58
|
+
// Create shape from polygon
|
|
59
|
+
// Shape is in X-Y plane, we'll rotate to X-Z plane
|
|
60
|
+
const shape = new THREE.Shape();
|
|
61
|
+
const firstPt = polygon[0];
|
|
62
|
+
// Negate Y (which becomes Z) to get correct orientation after rotation
|
|
63
|
+
shape.moveTo(firstPt[0], -firstPt[1]);
|
|
64
|
+
for (let i = 1; i < polygon.length; i++) {
|
|
65
|
+
const pt = polygon[i];
|
|
66
|
+
shape.lineTo(pt[0], -pt[1]);
|
|
67
|
+
}
|
|
68
|
+
shape.closePath();
|
|
69
|
+
// Add holes to the shape
|
|
70
|
+
const holes = ceilingNode.holes || [];
|
|
71
|
+
for (const holePolygon of holes) {
|
|
72
|
+
if (holePolygon.length < 3)
|
|
73
|
+
continue;
|
|
74
|
+
const holePath = new THREE.Path();
|
|
75
|
+
const holeFirstPt = holePolygon[0];
|
|
76
|
+
holePath.moveTo(holeFirstPt[0], -holeFirstPt[1]);
|
|
77
|
+
for (let i = 1; i < holePolygon.length; i++) {
|
|
78
|
+
const pt = holePolygon[i];
|
|
79
|
+
holePath.lineTo(pt[0], -pt[1]);
|
|
80
|
+
}
|
|
81
|
+
holePath.closePath();
|
|
82
|
+
shape.holes.push(holePath);
|
|
83
|
+
}
|
|
84
|
+
// Create flat shape geometry (no extrusion)
|
|
85
|
+
const geometry = new THREE.ShapeGeometry(shape);
|
|
86
|
+
// Rotate so the shape lies flat in X-Z plane
|
|
87
|
+
geometry.rotateX(-Math.PI / 2);
|
|
88
|
+
geometry.computeVertexNormals();
|
|
89
|
+
ensureUv2Attribute(geometry);
|
|
90
|
+
return geometry;
|
|
91
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type BufferGeometry } from 'three';
|
|
2
|
+
export declare function createColumnBoxGeometry(width: number, height: number, depth: number, bevelRadius?: number): BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
|
|
3
|
+
export declare function createColumnCylinderGeometry({ height, radiusBottom, radiusTop, radiusX, radiusZ, segments, }: {
|
|
4
|
+
height: number;
|
|
5
|
+
radiusBottom: number;
|
|
6
|
+
radiusTop?: number;
|
|
7
|
+
radiusX?: number;
|
|
8
|
+
radiusZ?: number;
|
|
9
|
+
segments?: number;
|
|
10
|
+
}): BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
|
|
11
|
+
export declare function createColumnSphereGeometry(radius: number, widthSegments?: number, heightSegments?: number): BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
|
|
12
|
+
export declare function createColumnTorusGeometry({ arc, radialSegments, ringRadius, scaleX, scaleY, scaleZ, tubeRadius, tubularSegments, }: {
|
|
13
|
+
arc?: number;
|
|
14
|
+
radialSegments?: number;
|
|
15
|
+
ringRadius: number;
|
|
16
|
+
scaleX?: number;
|
|
17
|
+
scaleY?: number;
|
|
18
|
+
scaleZ?: number;
|
|
19
|
+
tubeRadius: number;
|
|
20
|
+
tubularSegments?: number;
|
|
21
|
+
}): BufferGeometry<import("three").NormalBufferAttributes, import("three").BufferGeometryEventMap>;
|
|
22
|
+
//# sourceMappingURL=column-geometry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column-geometry.d.ts","sourceRoot":"","sources":["../../../src/systems/column/column-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EAKpB,MAAM,OAAO,CAAA;AAkHd,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,WAAW,SAAI,kGAOhB;AAED,wBAAgB,4BAA4B,CAAC,EAC3C,MAAM,EACN,YAAY,EACZ,SAAwB,EACxB,OAAW,EACX,OAAW,EACX,QAAa,GACd,EAAE;IACD,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,kGASA;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,SAAK,EAAE,cAAc,SAAI,kGAEhG;AAED,wBAAgB,yBAAyB,CAAC,EACxC,GAAiB,EACjB,cAAmB,EACnB,UAAU,EACV,MAAmB,EACnB,MAAmB,EACnB,MAAU,EACV,UAAU,EACV,eAAoB,GACrB,EAAE;IACD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,kGAIA"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { BoxGeometry, CylinderGeometry, Float32BufferAttribute, SphereGeometry, TorusGeometry, } from 'three';
|
|
2
|
+
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';
|
|
3
|
+
const COLUMN_UV_SCALE = 1;
|
|
4
|
+
function setUvAttributes(geometry, uvs) {
|
|
5
|
+
geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
|
|
6
|
+
geometry.setAttribute('uv2', new Float32BufferAttribute(uvs.slice(), 2));
|
|
7
|
+
return geometry;
|
|
8
|
+
}
|
|
9
|
+
function toUvReadyGeometry(geometry) {
|
|
10
|
+
return geometry.index ? geometry.toNonIndexed() : geometry;
|
|
11
|
+
}
|
|
12
|
+
function applyPlanarColumnUvs(geometry) {
|
|
13
|
+
const mappedGeometry = toUvReadyGeometry(geometry);
|
|
14
|
+
const positions = mappedGeometry.getAttribute('position');
|
|
15
|
+
const normals = mappedGeometry.getAttribute('normal');
|
|
16
|
+
const uvs = [];
|
|
17
|
+
for (let index = 0; index < positions.count; index += 1) {
|
|
18
|
+
const x = positions.getX(index);
|
|
19
|
+
const y = positions.getY(index);
|
|
20
|
+
const z = positions.getZ(index);
|
|
21
|
+
const normalX = normals ? Math.abs(normals.getX(index)) : 0;
|
|
22
|
+
const normalY = normals ? Math.abs(normals.getY(index)) : 1;
|
|
23
|
+
const normalZ = normals ? Math.abs(normals.getZ(index)) : 0;
|
|
24
|
+
if (normalY >= normalX && normalY >= normalZ) {
|
|
25
|
+
uvs.push(x * COLUMN_UV_SCALE, z * COLUMN_UV_SCALE);
|
|
26
|
+
}
|
|
27
|
+
else if (normalX >= normalZ) {
|
|
28
|
+
uvs.push(z * COLUMN_UV_SCALE, y * COLUMN_UV_SCALE);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
uvs.push(x * COLUMN_UV_SCALE, y * COLUMN_UV_SCALE);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return setUvAttributes(mappedGeometry, uvs);
|
|
35
|
+
}
|
|
36
|
+
function ellipseCircumference(radiusX, radiusZ) {
|
|
37
|
+
const a = Math.max(0.001, Math.abs(radiusX));
|
|
38
|
+
const b = Math.max(0.001, Math.abs(radiusZ));
|
|
39
|
+
return Math.PI * (3 * (a + b) - Math.sqrt((3 * a + b) * (a + 3 * b)));
|
|
40
|
+
}
|
|
41
|
+
function applyCylindricalColumnUvs(geometry, sideCircumference, height) {
|
|
42
|
+
const mappedGeometry = toUvReadyGeometry(geometry);
|
|
43
|
+
const positions = mappedGeometry.getAttribute('position');
|
|
44
|
+
const normals = mappedGeometry.getAttribute('normal');
|
|
45
|
+
const defaultUvs = mappedGeometry.getAttribute('uv');
|
|
46
|
+
const halfHeight = height / 2;
|
|
47
|
+
const uvs = [];
|
|
48
|
+
for (let index = 0; index < positions.count; index += 1) {
|
|
49
|
+
const x = positions.getX(index);
|
|
50
|
+
const y = positions.getY(index);
|
|
51
|
+
const z = positions.getZ(index);
|
|
52
|
+
const normalY = normals ? Math.abs(normals.getY(index)) : 0;
|
|
53
|
+
if (normalY > 0.65) {
|
|
54
|
+
uvs.push(x * COLUMN_UV_SCALE, z * COLUMN_UV_SCALE);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const defaultU = defaultUvs ? defaultUvs.getX(index) : 0;
|
|
58
|
+
uvs.push(defaultU * sideCircumference * COLUMN_UV_SCALE, (y + halfHeight) * COLUMN_UV_SCALE);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return setUvAttributes(mappedGeometry, uvs);
|
|
62
|
+
}
|
|
63
|
+
function applySphericalColumnUvs(geometry, radius) {
|
|
64
|
+
const mappedGeometry = toUvReadyGeometry(geometry);
|
|
65
|
+
const defaultUvs = mappedGeometry.getAttribute('uv');
|
|
66
|
+
if (!defaultUvs)
|
|
67
|
+
return mappedGeometry;
|
|
68
|
+
const uvs = [];
|
|
69
|
+
const circumference = Math.PI * 2 * radius;
|
|
70
|
+
const arcHeight = Math.PI * radius;
|
|
71
|
+
for (let index = 0; index < defaultUvs.count; index += 1) {
|
|
72
|
+
uvs.push(defaultUvs.getX(index) * circumference * COLUMN_UV_SCALE, defaultUvs.getY(index) * arcHeight * COLUMN_UV_SCALE);
|
|
73
|
+
}
|
|
74
|
+
return setUvAttributes(mappedGeometry, uvs);
|
|
75
|
+
}
|
|
76
|
+
function applyTorusColumnUvs(geometry, ringRadius, tubeRadius) {
|
|
77
|
+
const mappedGeometry = toUvReadyGeometry(geometry);
|
|
78
|
+
const defaultUvs = mappedGeometry.getAttribute('uv');
|
|
79
|
+
if (!defaultUvs)
|
|
80
|
+
return mappedGeometry;
|
|
81
|
+
const uvs = [];
|
|
82
|
+
const ringLength = Math.PI * 2 * Math.max(0.001, ringRadius);
|
|
83
|
+
const tubeLength = Math.PI * 2 * Math.max(0.001, tubeRadius);
|
|
84
|
+
for (let index = 0; index < defaultUvs.count; index += 1) {
|
|
85
|
+
uvs.push(defaultUvs.getX(index) * ringLength * COLUMN_UV_SCALE, defaultUvs.getY(index) * tubeLength * COLUMN_UV_SCALE);
|
|
86
|
+
}
|
|
87
|
+
return setUvAttributes(mappedGeometry, uvs);
|
|
88
|
+
}
|
|
89
|
+
export function createColumnBoxGeometry(width, height, depth, bevelRadius = 0) {
|
|
90
|
+
const geometry = bevelRadius > 0.001
|
|
91
|
+
? new RoundedBoxGeometry(width, height, depth, 3, bevelRadius)
|
|
92
|
+
: new BoxGeometry(width, height, depth);
|
|
93
|
+
return applyPlanarColumnUvs(geometry);
|
|
94
|
+
}
|
|
95
|
+
export function createColumnCylinderGeometry({ height, radiusBottom, radiusTop = radiusBottom, radiusX = 1, radiusZ = 1, segments = 32, }) {
|
|
96
|
+
const geometry = new CylinderGeometry(radiusTop, radiusBottom, height, segments);
|
|
97
|
+
geometry.scale(radiusX, 1, radiusZ);
|
|
98
|
+
const sideRadius = Math.max(radiusTop, radiusBottom);
|
|
99
|
+
return applyCylindricalColumnUvs(geometry, ellipseCircumference(sideRadius * radiusX, sideRadius * radiusZ), height);
|
|
100
|
+
}
|
|
101
|
+
export function createColumnSphereGeometry(radius, widthSegments = 10, heightSegments = 8) {
|
|
102
|
+
return applySphericalColumnUvs(new SphereGeometry(radius, widthSegments, heightSegments), radius);
|
|
103
|
+
}
|
|
104
|
+
export function createColumnTorusGeometry({ arc = Math.PI * 2, radialSegments = 10, ringRadius, scaleX = ringRadius, scaleY = ringRadius, scaleZ = 1, tubeRadius, tubularSegments = 24, }) {
|
|
105
|
+
const geometry = new TorusGeometry(1, 0.18, radialSegments, tubularSegments, arc);
|
|
106
|
+
geometry.scale(scaleX, scaleY, scaleZ);
|
|
107
|
+
return applyTorusColumnUvs(geometry, ringRadius, tubeRadius);
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"door-animation-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-animation-system.tsx"],"names":[],"mappings":"AAYA,eAAO,MAAM,mBAAmB,YA8C/B,CAAA"}
|