u-space 0.0.2 → 0.0.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/index.cjs +2074 -1
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +1512 -840
- package/dist/index.js.map +1 -0
- package/docs/api-objects.md +25 -3
- package/docs/api-plugins.md +9 -11
- package/docs/api-viewer.md +46 -0
- package/docs/getting-started.md +1 -2
- package/docs/index.md +2 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { Raycaster
|
|
2
|
-
import { pass
|
|
3
|
-
import
|
|
4
|
-
import { RoomEnvironment as
|
|
5
|
-
import { ViewHelper
|
|
6
|
-
import { GLTFLoader
|
|
7
|
-
import { FBXLoader
|
|
8
|
-
import { OBJLoader
|
|
9
|
-
import { STLLoader
|
|
10
|
-
import { DRACOLoader
|
|
11
|
-
import { KTX2Loader
|
|
12
|
-
import { MeshoptDecoder
|
|
13
|
-
import { HDRLoader
|
|
14
|
-
import { clone
|
|
15
|
-
import { Color as
|
|
16
|
-
import { Easing
|
|
17
|
-
class
|
|
1
|
+
import { Raycaster, Vector2, RenderPipeline as RenderPipeline$1, Sphere, Box3, Spherical, Matrix4, Quaternion, Vector4, Vector3, MathUtils, PMREMGenerator, OrthographicCamera, TimestampQuery, Cache, EventDispatcher, Timer, PerspectiveCamera, WebGPURenderer, PCFShadowMap, AgXToneMapping, Scene, Color, MeshStandardNodeMaterial, Loader, LoaderUtils, FileLoader, TextureLoader, Group, SRGBColorSpace, RepeatWrapping, BufferGeometry, BufferAttribute, Mesh, FrontSide, BackSide, DoubleSide, REVISION, CubeTextureLoader, EquirectangularReflectionMapping, Sprite, SphereGeometry, TubeGeometry, LineCurve3, CatmullRomCurve3, SpriteNodeMaterial, CanvasTexture, ShapeGeometry, Shape, ExtrudeGeometry, PlaneGeometry, CircleGeometry } from "three/webgpu";
|
|
2
|
+
import { pass, blendColor, color, uv, time, pow, sin, mix, mx_noise_float, Fn, userData, materialColor, select, materialOpacity, vec3 } from "three/tsl";
|
|
3
|
+
import CameraControlsBase from "camera-controls";
|
|
4
|
+
import { RoomEnvironment as RoomEnvironment$1 } from "three/addons/environments/RoomEnvironment.js";
|
|
5
|
+
import { ViewHelper } from "three/addons/helpers/ViewHelper.js";
|
|
6
|
+
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
7
|
+
import { FBXLoader } from "three/addons/loaders/FBXLoader.js";
|
|
8
|
+
import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
|
|
9
|
+
import { STLLoader } from "three/addons/loaders/STLLoader.js";
|
|
10
|
+
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
|
|
11
|
+
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
|
|
12
|
+
import { MeshoptDecoder } from "three/addons/libs/meshopt_decoder.module.js";
|
|
13
|
+
import { HDRLoader } from "three/addons/loaders/HDRLoader.js";
|
|
14
|
+
import { clone } from "three/addons/utils/SkeletonUtils.js";
|
|
15
|
+
import { Color as Color$1 } from "three";
|
|
16
|
+
import { Easing, Tween as Tween$1 } from "three/addons/libs/tween.module.js";
|
|
17
|
+
class InteractionEvent {
|
|
18
18
|
/** The type of event (e.g., 'click', 'pointerdown'). */
|
|
19
19
|
type;
|
|
20
20
|
/** The original 3D object that triggered the event. */
|
|
@@ -25,15 +25,19 @@ class Ct {
|
|
|
25
25
|
intersect;
|
|
26
26
|
/** The original DOM event, or undefined if not applicable. */
|
|
27
27
|
originalEvent;
|
|
28
|
-
_propagationStopped =
|
|
29
|
-
constructor(
|
|
30
|
-
this.type =
|
|
28
|
+
_propagationStopped = false;
|
|
29
|
+
constructor(init) {
|
|
30
|
+
this.type = init.type;
|
|
31
|
+
this.target = init.target;
|
|
32
|
+
this.currentTarget = init.target;
|
|
33
|
+
this.intersect = init.intersect;
|
|
34
|
+
this.originalEvent = init.originalEvent;
|
|
31
35
|
}
|
|
32
36
|
/**
|
|
33
37
|
* Stops the event from bubbling up the parent chain.
|
|
34
38
|
*/
|
|
35
39
|
stopPropagation() {
|
|
36
|
-
this._propagationStopped =
|
|
40
|
+
this._propagationStopped = true;
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
43
|
* Returns true if `stopPropagation()` has been called.
|
|
@@ -42,15 +46,15 @@ class Ct {
|
|
|
42
46
|
return this._propagationStopped;
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
|
-
class
|
|
49
|
+
class InteractionManager {
|
|
46
50
|
domElement;
|
|
47
51
|
scene;
|
|
48
52
|
camera;
|
|
49
|
-
raycaster = new
|
|
50
|
-
pointer = new
|
|
53
|
+
raycaster = new Raycaster();
|
|
54
|
+
pointer = new Vector2();
|
|
51
55
|
hoveredObject = null;
|
|
52
56
|
pointerDownTime = 0;
|
|
53
|
-
pointerDownPos = new
|
|
57
|
+
pointerDownPos = new Vector2();
|
|
54
58
|
clickTimer = null;
|
|
55
59
|
longPressThreshold = 500;
|
|
56
60
|
moveThreshold = 0.01;
|
|
@@ -62,92 +66,142 @@ class xt {
|
|
|
62
66
|
* Whether to enable pointer move events (pointermove, pointerenter, pointerleave).
|
|
63
67
|
* Disabled by default for performance optimization.
|
|
64
68
|
*/
|
|
65
|
-
pointerMoveEventsEnabled =
|
|
66
|
-
constructor(
|
|
67
|
-
this.domElement =
|
|
69
|
+
pointerMoveEventsEnabled = false;
|
|
70
|
+
constructor(domElement, scene, camera) {
|
|
71
|
+
this.domElement = domElement;
|
|
72
|
+
this.scene = scene;
|
|
73
|
+
this.camera = camera;
|
|
74
|
+
this.raycaster.params.Points.threshold = 0.1;
|
|
75
|
+
this.raycaster.params.Line.threshold = 0.1;
|
|
76
|
+
this.targetObjects = this.scene.children;
|
|
77
|
+
this._bindEvents();
|
|
68
78
|
}
|
|
69
79
|
/**
|
|
70
80
|
* Updates the camera reference (useful when switching cameras).
|
|
71
81
|
*/
|
|
72
|
-
setCamera(
|
|
73
|
-
this.camera =
|
|
82
|
+
setCamera(camera) {
|
|
83
|
+
this.camera = camera;
|
|
74
84
|
}
|
|
75
85
|
_bindEvents() {
|
|
76
|
-
this.domElement.addEventListener("click", this._onClick)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.
|
|
86
|
+
this.domElement.addEventListener("click", this._onClick);
|
|
87
|
+
this.domElement.addEventListener("dblclick", this._onDblClick);
|
|
88
|
+
this.domElement.addEventListener("contextmenu", this._onContextMenu);
|
|
89
|
+
this.domElement.addEventListener("pointerdown", this._onPointerDown);
|
|
90
|
+
this.domElement.addEventListener("pointerup", this._onPointerUp);
|
|
91
|
+
this.domElement.addEventListener("pointermove", this._onPointerMove);
|
|
92
|
+
}
|
|
93
|
+
_updatePointer(event) {
|
|
94
|
+
const rect = this.domElement.getBoundingClientRect();
|
|
95
|
+
this.pointer.x = (event.clientX - rect.left) / rect.width * 2 - 1;
|
|
96
|
+
this.pointer.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
|
81
97
|
}
|
|
82
98
|
_getIntersects() {
|
|
83
|
-
|
|
99
|
+
this.raycaster.setFromCamera(this.pointer, this.camera);
|
|
100
|
+
return this.raycaster.intersectObjects(this.targetObjects, true);
|
|
84
101
|
}
|
|
85
102
|
/**
|
|
86
103
|
* Dispatches an event to the target object and bubbles up the parent chain.
|
|
87
104
|
*/
|
|
88
|
-
_dispatchToTarget(
|
|
89
|
-
const
|
|
90
|
-
type
|
|
91
|
-
target
|
|
92
|
-
intersect
|
|
93
|
-
originalEvent
|
|
105
|
+
_dispatchToTarget(type, target, intersect, originalEvent) {
|
|
106
|
+
const event3d = new InteractionEvent({
|
|
107
|
+
type,
|
|
108
|
+
target,
|
|
109
|
+
intersect,
|
|
110
|
+
originalEvent
|
|
94
111
|
});
|
|
95
|
-
let
|
|
96
|
-
|
|
97
|
-
|
|
112
|
+
let current = target;
|
|
113
|
+
while (current && !event3d.propagationStopped) {
|
|
114
|
+
event3d.currentTarget = current;
|
|
115
|
+
current.dispatchEvent({ type, event: event3d });
|
|
116
|
+
current = current.parent;
|
|
117
|
+
}
|
|
98
118
|
}
|
|
99
119
|
/**
|
|
100
120
|
* Handles a DOM event by raycasting and dispatching to the first intersected object.
|
|
101
121
|
*/
|
|
102
|
-
_handleEvent(
|
|
103
|
-
this._updatePointer(
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
const
|
|
107
|
-
this._dispatchToTarget(
|
|
122
|
+
_handleEvent(type, originalEvent) {
|
|
123
|
+
this._updatePointer(originalEvent);
|
|
124
|
+
const intersects = this._getIntersects();
|
|
125
|
+
if (intersects.length === 0) return;
|
|
126
|
+
const hit = intersects[0];
|
|
127
|
+
this._dispatchToTarget(type, hit.object, hit, originalEvent);
|
|
108
128
|
}
|
|
109
129
|
_onClick = (e) => {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
this.clickTimer = setTimeout(() => {
|
|
117
|
-
this._handleEvent("click", e), this.clickTimer = null;
|
|
118
|
-
}, 200);
|
|
130
|
+
const duration = Date.now() - this.pointerDownTime;
|
|
131
|
+
const moveDistance = this.pointer.distanceTo(this.pointerDownPos);
|
|
132
|
+
if (duration > this.longPressThreshold || moveDistance > this.moveThreshold) {
|
|
133
|
+
return;
|
|
119
134
|
}
|
|
135
|
+
if (this.clickTimer) {
|
|
136
|
+
clearTimeout(this.clickTimer);
|
|
137
|
+
this.clickTimer = null;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this.clickTimer = setTimeout(() => {
|
|
141
|
+
this._handleEvent("click", e);
|
|
142
|
+
this.clickTimer = null;
|
|
143
|
+
}, 200);
|
|
120
144
|
};
|
|
121
145
|
_onDblClick = (e) => {
|
|
122
|
-
|
|
146
|
+
if (this.clickTimer) {
|
|
147
|
+
clearTimeout(this.clickTimer);
|
|
148
|
+
this.clickTimer = null;
|
|
149
|
+
}
|
|
150
|
+
this._handleEvent("dblclick", e);
|
|
123
151
|
};
|
|
124
152
|
_onContextMenu = (e) => {
|
|
125
153
|
e.preventDefault();
|
|
126
154
|
};
|
|
127
155
|
_onPointerDown = (e) => {
|
|
128
|
-
this.pointerDownTime = Date.now()
|
|
156
|
+
this.pointerDownTime = Date.now();
|
|
157
|
+
this._updatePointer(e);
|
|
158
|
+
this.pointerDownPos.copy(this.pointer);
|
|
159
|
+
this._handleEvent("pointerdown", e);
|
|
129
160
|
};
|
|
130
161
|
_onPointerUp = (e) => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
162
|
+
this._updatePointer(e);
|
|
163
|
+
if (e.button === 2) {
|
|
164
|
+
const duration = Date.now() - this.pointerDownTime;
|
|
165
|
+
const moveDistance = this.pointer.distanceTo(this.pointerDownPos);
|
|
166
|
+
if (duration <= this.longPressThreshold && moveDistance <= this.moveThreshold) {
|
|
167
|
+
this._handleEvent("rightclick", e);
|
|
168
|
+
}
|
|
134
169
|
}
|
|
135
170
|
this._handleEvent("pointerup", e);
|
|
136
171
|
};
|
|
137
172
|
_onPointerMove = (e) => {
|
|
138
173
|
if (!this.pointerMoveEventsEnabled) return;
|
|
139
174
|
this._updatePointer(e);
|
|
140
|
-
const
|
|
141
|
-
|
|
175
|
+
const intersects = this._getIntersects();
|
|
176
|
+
const hitObject = intersects.length > 0 ? intersects[0].object : null;
|
|
177
|
+
const intersect = intersects.length > 0 ? intersects[0] : null;
|
|
178
|
+
if (this.hoveredObject !== hitObject) {
|
|
179
|
+
if (this.hoveredObject) {
|
|
180
|
+
this._dispatchToTarget("pointerleave", this.hoveredObject, null, e);
|
|
181
|
+
}
|
|
182
|
+
this.hoveredObject = hitObject;
|
|
183
|
+
if (hitObject && intersect) {
|
|
184
|
+
this._dispatchToTarget("pointerenter", hitObject, intersect, e);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (hitObject && intersect) {
|
|
188
|
+
this._dispatchToTarget("pointermove", hitObject, intersect, e);
|
|
189
|
+
}
|
|
142
190
|
};
|
|
143
191
|
/**
|
|
144
192
|
* Cleans up event listeners.
|
|
145
193
|
*/
|
|
146
194
|
dispose() {
|
|
147
|
-
this.domElement.removeEventListener("click", this._onClick)
|
|
195
|
+
this.domElement.removeEventListener("click", this._onClick);
|
|
196
|
+
this.domElement.removeEventListener("dblclick", this._onDblClick);
|
|
197
|
+
this.domElement.removeEventListener("contextmenu", this._onContextMenu);
|
|
198
|
+
this.domElement.removeEventListener("pointerdown", this._onPointerDown);
|
|
199
|
+
this.domElement.removeEventListener("pointerup", this._onPointerUp);
|
|
200
|
+
this.domElement.removeEventListener("pointermove", this._onPointerMove);
|
|
201
|
+
this.hoveredObject = null;
|
|
148
202
|
}
|
|
149
203
|
}
|
|
150
|
-
class
|
|
204
|
+
class ObjectManager {
|
|
151
205
|
idMap = /* @__PURE__ */ new Map();
|
|
152
206
|
nameMap = /* @__PURE__ */ new Map();
|
|
153
207
|
typeMap = /* @__PURE__ */ new Map();
|
|
@@ -157,85 +211,117 @@ class Mt {
|
|
|
157
211
|
* Add an object with a custom id.
|
|
158
212
|
* One object can have multiple ids.
|
|
159
213
|
*/
|
|
160
|
-
add(
|
|
161
|
-
if (this.idMap.has(
|
|
162
|
-
console.warn(`ObjectManager: Object with id ${
|
|
214
|
+
add(id, object) {
|
|
215
|
+
if (this.idMap.has(id)) {
|
|
216
|
+
console.warn(`ObjectManager: Object with id ${id} already exists.`);
|
|
163
217
|
return;
|
|
164
218
|
}
|
|
165
|
-
this.idMap.set(
|
|
219
|
+
this.idMap.set(id, object);
|
|
220
|
+
this.allObjects.add(object);
|
|
221
|
+
if (!this.objectIds.has(object)) {
|
|
222
|
+
this.objectIds.set(object, /* @__PURE__ */ new Set());
|
|
223
|
+
}
|
|
224
|
+
this.objectIds.get(object).add(id);
|
|
225
|
+
if (object.name) {
|
|
226
|
+
if (!this.nameMap.has(object.name)) {
|
|
227
|
+
this.nameMap.set(object.name, /* @__PURE__ */ new Set());
|
|
228
|
+
}
|
|
229
|
+
this.nameMap.get(object.name).add(object);
|
|
230
|
+
}
|
|
231
|
+
if (object.type) {
|
|
232
|
+
if (!this.typeMap.has(object.type)) {
|
|
233
|
+
this.typeMap.set(object.type, /* @__PURE__ */ new Set());
|
|
234
|
+
}
|
|
235
|
+
this.typeMap.get(object.type).add(object);
|
|
236
|
+
}
|
|
166
237
|
}
|
|
167
238
|
/**
|
|
168
239
|
* Get an object by its custom id.
|
|
169
240
|
*/
|
|
170
|
-
getById(
|
|
171
|
-
return this.idMap.get(
|
|
241
|
+
getById(id) {
|
|
242
|
+
return this.idMap.get(id);
|
|
172
243
|
}
|
|
173
244
|
/**
|
|
174
245
|
* Get all objects with a given name.
|
|
175
246
|
*/
|
|
176
|
-
getByName(
|
|
177
|
-
return this.nameMap.get(
|
|
247
|
+
getByName(name) {
|
|
248
|
+
return this.nameMap.get(name) || /* @__PURE__ */ new Set();
|
|
178
249
|
}
|
|
179
250
|
/**
|
|
180
251
|
* Get all objects with a given type.
|
|
181
252
|
*/
|
|
182
|
-
getByType(
|
|
183
|
-
return this.typeMap.get(
|
|
253
|
+
getByType(type) {
|
|
254
|
+
return this.typeMap.get(type) || /* @__PURE__ */ new Set();
|
|
184
255
|
}
|
|
185
256
|
/**
|
|
186
257
|
* Get all ids associated with an object.
|
|
187
258
|
*/
|
|
188
|
-
getObjectIds(
|
|
189
|
-
return this.objectIds.get(
|
|
259
|
+
getObjectIds(object) {
|
|
260
|
+
return this.objectIds.get(object) || /* @__PURE__ */ new Set();
|
|
190
261
|
}
|
|
191
262
|
/**
|
|
192
263
|
* Remove an object and all its associated ids.
|
|
193
264
|
*/
|
|
194
|
-
remove(
|
|
195
|
-
const
|
|
196
|
-
if (
|
|
197
|
-
for (const
|
|
198
|
-
this.idMap.delete(
|
|
199
|
-
|
|
265
|
+
remove(object) {
|
|
266
|
+
const ids = this.objectIds.get(object);
|
|
267
|
+
if (ids) {
|
|
268
|
+
for (const id of ids) {
|
|
269
|
+
this.idMap.delete(id);
|
|
270
|
+
}
|
|
271
|
+
this.objectIds.delete(object);
|
|
200
272
|
}
|
|
201
|
-
if (
|
|
202
|
-
const
|
|
203
|
-
|
|
273
|
+
if (object.name && this.nameMap.has(object.name)) {
|
|
274
|
+
const set = this.nameMap.get(object.name);
|
|
275
|
+
set.delete(object);
|
|
276
|
+
if (set.size === 0) {
|
|
277
|
+
this.nameMap.delete(object.name);
|
|
278
|
+
}
|
|
204
279
|
}
|
|
205
|
-
if (
|
|
206
|
-
const
|
|
207
|
-
|
|
280
|
+
if (object.type && this.typeMap.has(object.type)) {
|
|
281
|
+
const set = this.typeMap.get(object.type);
|
|
282
|
+
set.delete(object);
|
|
283
|
+
if (set.size === 0) {
|
|
284
|
+
this.typeMap.delete(object.type);
|
|
285
|
+
}
|
|
208
286
|
}
|
|
209
|
-
this.allObjects.delete(
|
|
287
|
+
this.allObjects.delete(object);
|
|
210
288
|
}
|
|
211
289
|
/**
|
|
212
290
|
* Remove an object by its id.
|
|
213
291
|
*/
|
|
214
|
-
removeById(
|
|
215
|
-
const
|
|
216
|
-
|
|
292
|
+
removeById(id) {
|
|
293
|
+
const object = this.idMap.get(id);
|
|
294
|
+
if (object) {
|
|
295
|
+
this.remove(object);
|
|
296
|
+
}
|
|
217
297
|
}
|
|
218
298
|
/**
|
|
219
299
|
* Remove all objects with a given name.
|
|
220
300
|
*/
|
|
221
|
-
removeByName(
|
|
222
|
-
const
|
|
223
|
-
for (const
|
|
224
|
-
this.remove(
|
|
301
|
+
removeByName(name) {
|
|
302
|
+
const objects = this.getByName(name);
|
|
303
|
+
for (const object of objects) {
|
|
304
|
+
this.remove(object);
|
|
305
|
+
}
|
|
225
306
|
}
|
|
226
307
|
/**
|
|
227
308
|
* Remove all objects with a given type.
|
|
228
309
|
*/
|
|
229
|
-
removeByType(
|
|
230
|
-
const
|
|
231
|
-
for (const
|
|
232
|
-
this.remove(
|
|
310
|
+
removeByType(type) {
|
|
311
|
+
const objects = this.getByType(type);
|
|
312
|
+
for (const object of objects) {
|
|
313
|
+
this.remove(object);
|
|
314
|
+
}
|
|
233
315
|
}
|
|
234
316
|
/**
|
|
235
317
|
* Clear all cached objects.
|
|
236
318
|
*/
|
|
237
319
|
clear() {
|
|
238
|
-
this.idMap.clear()
|
|
320
|
+
this.idMap.clear();
|
|
321
|
+
this.nameMap.clear();
|
|
322
|
+
this.typeMap.clear();
|
|
323
|
+
this.objectIds.clear();
|
|
324
|
+
this.allObjects.clear();
|
|
239
325
|
}
|
|
240
326
|
/**
|
|
241
327
|
* Get all cached objects as a Set.
|
|
@@ -250,148 +336,202 @@ class Mt {
|
|
|
250
336
|
return this.allObjects.size;
|
|
251
337
|
}
|
|
252
338
|
}
|
|
253
|
-
class
|
|
339
|
+
class RenderPipeline extends RenderPipeline$1 {
|
|
254
340
|
scene;
|
|
255
341
|
camera;
|
|
256
342
|
currentOutputNode;
|
|
257
343
|
needsUpdateOutputNode;
|
|
258
|
-
#
|
|
259
|
-
constructor(...
|
|
260
|
-
const [
|
|
261
|
-
super(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
this
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
this.
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
this.
|
|
344
|
+
#overlayPasses;
|
|
345
|
+
constructor(...args) {
|
|
346
|
+
const [renderer, scene, camera] = args;
|
|
347
|
+
super(renderer);
|
|
348
|
+
this.scene = scene;
|
|
349
|
+
this.camera = camera;
|
|
350
|
+
this.currentOutputNode = pass(scene, camera);
|
|
351
|
+
this.needsUpdateOutputNode = true;
|
|
352
|
+
this.#overlayPasses = /* @__PURE__ */ new Set();
|
|
353
|
+
}
|
|
354
|
+
addOverlayPass(pass2) {
|
|
355
|
+
this.#overlayPasses.add(pass2);
|
|
356
|
+
this.needsUpdateOutputNode = true;
|
|
357
|
+
}
|
|
358
|
+
removeOverlayPass(pass2) {
|
|
359
|
+
this.#overlayPasses.delete(pass2);
|
|
360
|
+
this.needsUpdateOutputNode = true;
|
|
361
|
+
}
|
|
362
|
+
setCamera(camera) {
|
|
363
|
+
this.camera = camera;
|
|
364
|
+
this.needsUpdateOutputNode = true;
|
|
365
|
+
}
|
|
366
|
+
setCurrentOutputNode(node) {
|
|
367
|
+
this.currentOutputNode.dispose();
|
|
368
|
+
this.currentOutputNode = node;
|
|
369
|
+
this.needsUpdateOutputNode = true;
|
|
274
370
|
}
|
|
275
371
|
resetCurrentOutputNode() {
|
|
276
|
-
this.currentOutputNode.dispose()
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
372
|
+
this.currentOutputNode.dispose();
|
|
373
|
+
this.currentOutputNode = pass(this.scene, this.camera);
|
|
374
|
+
this.needsUpdateOutputNode = true;
|
|
375
|
+
}
|
|
376
|
+
#updateOutputNode() {
|
|
377
|
+
let currentNode = this.currentOutputNode;
|
|
378
|
+
if (this.#overlayPasses.size > 0) {
|
|
379
|
+
this.#overlayPasses.forEach((overlayPass) => {
|
|
380
|
+
currentNode = blendColor(currentNode, overlayPass);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
this.outputNode = currentNode;
|
|
384
|
+
this.needsUpdate = true;
|
|
283
385
|
}
|
|
284
386
|
render() {
|
|
285
|
-
|
|
387
|
+
if (this.needsUpdateOutputNode) {
|
|
388
|
+
this.#updateOutputNode();
|
|
389
|
+
this.needsUpdateOutputNode = false;
|
|
390
|
+
}
|
|
391
|
+
super.render();
|
|
286
392
|
}
|
|
287
393
|
}
|
|
288
|
-
const
|
|
289
|
-
Vector2
|
|
290
|
-
Vector3
|
|
291
|
-
Vector4
|
|
292
|
-
Quaternion
|
|
293
|
-
Matrix4
|
|
294
|
-
Spherical
|
|
295
|
-
Box3
|
|
296
|
-
Sphere
|
|
297
|
-
Raycaster
|
|
394
|
+
const subsetOfTHREE = {
|
|
395
|
+
Vector2,
|
|
396
|
+
Vector3,
|
|
397
|
+
Vector4,
|
|
398
|
+
Quaternion,
|
|
399
|
+
Matrix4,
|
|
400
|
+
Spherical,
|
|
401
|
+
Box3,
|
|
402
|
+
Sphere,
|
|
403
|
+
Raycaster
|
|
298
404
|
};
|
|
299
|
-
|
|
300
|
-
class
|
|
301
|
-
constructor(...
|
|
302
|
-
super(...
|
|
405
|
+
CameraControlsBase.install({ THREE: subsetOfTHREE });
|
|
406
|
+
class CameraControls extends CameraControlsBase {
|
|
407
|
+
constructor(...args) {
|
|
408
|
+
super(...args);
|
|
409
|
+
this.minDistance = 0.2;
|
|
410
|
+
this.smoothTime = 0.2;
|
|
411
|
+
this.dollySpeed = 0.2;
|
|
303
412
|
}
|
|
304
413
|
/**
|
|
305
414
|
* Set absolute angle for azimuth to avoid spinning
|
|
306
415
|
*/
|
|
307
416
|
absoluteRotations() {
|
|
308
|
-
this.azimuthAngle =
|
|
417
|
+
this.azimuthAngle = absoluteAngle(this.azimuthAngle, 0);
|
|
309
418
|
}
|
|
310
|
-
async flyToBox(
|
|
311
|
-
if (
|
|
312
|
-
|
|
419
|
+
async flyToBox(box, options = {}) {
|
|
420
|
+
if (box.isEmpty()) {
|
|
421
|
+
console.warn("CameraControls.flyToBox: The provided Box3 is empty.");
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
313
424
|
this.absoluteRotations();
|
|
314
|
-
const { viewpoint
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
425
|
+
const { viewpoint = "frontTop", enableTransition = true, padding = 0.1, cover = false } = options;
|
|
426
|
+
const promises = [];
|
|
427
|
+
if (viewpoint !== "current") {
|
|
428
|
+
const spherical = viewpointsSpherical[viewpoint.toUpperCase()] ?? viewpointsSpherical.FRONTTOP;
|
|
429
|
+
const azimuthAngle = spherical.theta, polarAngle = spherical.phi;
|
|
430
|
+
promises.push(
|
|
431
|
+
this.fitToBox(box, enableTransition, {
|
|
432
|
+
paddingLeft: padding,
|
|
433
|
+
paddingRight: padding,
|
|
434
|
+
paddingTop: padding,
|
|
435
|
+
paddingBottom: padding,
|
|
436
|
+
cover
|
|
324
437
|
})
|
|
325
|
-
)
|
|
438
|
+
);
|
|
439
|
+
promises.push(this.rotateTo(azimuthAngle, polarAngle, enableTransition));
|
|
326
440
|
} else {
|
|
327
|
-
const
|
|
328
|
-
|
|
441
|
+
const sphere = new Sphere();
|
|
442
|
+
box.getBoundingSphere(sphere);
|
|
443
|
+
sphere.radius += padding;
|
|
444
|
+
promises.push(this.fitToSphere(sphere, enableTransition));
|
|
329
445
|
}
|
|
330
|
-
|
|
446
|
+
await Promise.all(promises);
|
|
447
|
+
return true;
|
|
331
448
|
}
|
|
332
|
-
async flyToObject(
|
|
333
|
-
const
|
|
334
|
-
return this.flyToBox(
|
|
449
|
+
async flyToObject(object, options = {}) {
|
|
450
|
+
const box = new Box3().setFromObject(object);
|
|
451
|
+
return this.flyToBox(box, options);
|
|
335
452
|
}
|
|
336
453
|
}
|
|
337
|
-
const
|
|
338
|
-
LEFT: new
|
|
339
|
-
RIGHT: new
|
|
340
|
-
FRONT: new
|
|
341
|
-
BACK: new
|
|
342
|
-
TOP: new
|
|
343
|
-
BOTTOM: new
|
|
344
|
-
FRONTTOP: new
|
|
345
|
-
BACKTOP: new
|
|
346
|
-
LEFTTOP: new
|
|
347
|
-
RIGHTTOP: new
|
|
348
|
-
LEFTFRONTTOP: new
|
|
349
|
-
RIGHTFRONTTOP: new
|
|
350
|
-
LEFTBACKTOP: new
|
|
351
|
-
RIGHTBACKTOP: new
|
|
454
|
+
const viewpointsSpherical = {
|
|
455
|
+
LEFT: new Spherical(0, Math.PI / 2, -Math.PI / 2),
|
|
456
|
+
RIGHT: new Spherical(0, Math.PI / 2, Math.PI / 2),
|
|
457
|
+
FRONT: new Spherical(0, Math.PI / 2, 0),
|
|
458
|
+
BACK: new Spherical(0, Math.PI / 2, Math.PI),
|
|
459
|
+
TOP: new Spherical(0, 0, 0),
|
|
460
|
+
BOTTOM: new Spherical(0, Math.PI, 0),
|
|
461
|
+
FRONTTOP: new Spherical(0, Math.PI / 4, 0),
|
|
462
|
+
BACKTOP: new Spherical(0, Math.PI / 4, Math.PI),
|
|
463
|
+
LEFTTOP: new Spherical(0, Math.PI / 4, -Math.PI / 2),
|
|
464
|
+
RIGHTTOP: new Spherical(0, Math.PI / 4, Math.PI / 2),
|
|
465
|
+
LEFTFRONTTOP: new Spherical(0, Math.PI / 4, -Math.PI / 4),
|
|
466
|
+
RIGHTFRONTTOP: new Spherical(0, Math.PI / 4, Math.PI / 4),
|
|
467
|
+
LEFTBACKTOP: new Spherical(0, Math.PI / 4, -Math.PI / 4 * 3),
|
|
468
|
+
RIGHTBACKTOP: new Spherical(0, Math.PI / 4, Math.PI / 4 * 3)
|
|
352
469
|
};
|
|
353
|
-
function
|
|
354
|
-
const
|
|
355
|
-
return
|
|
470
|
+
function absoluteAngle(targetAngle, sourceAngle) {
|
|
471
|
+
const angle = targetAngle - sourceAngle;
|
|
472
|
+
return MathUtils.euclideanModulo(angle + Math.PI, Math.PI * 2) - Math.PI;
|
|
356
473
|
}
|
|
357
|
-
class
|
|
358
|
-
environment = new
|
|
474
|
+
class RoomEnvironment {
|
|
475
|
+
environment = new RoomEnvironment$1();
|
|
359
476
|
renderer;
|
|
360
477
|
pmremGenerator;
|
|
361
478
|
envMap = null;
|
|
362
|
-
constructor(
|
|
363
|
-
this.renderer =
|
|
479
|
+
constructor(renderer) {
|
|
480
|
+
this.renderer = renderer;
|
|
481
|
+
this.pmremGenerator = new PMREMGenerator(renderer);
|
|
364
482
|
}
|
|
365
483
|
update() {
|
|
366
|
-
|
|
484
|
+
if (!this.envMap) {
|
|
485
|
+
this.envMap = this.pmremGenerator.fromScene(this.environment).texture;
|
|
486
|
+
}
|
|
367
487
|
}
|
|
368
488
|
dispose() {
|
|
369
|
-
this.pmremGenerator.dispose()
|
|
489
|
+
this.pmremGenerator.dispose();
|
|
490
|
+
this.envMap?.dispose();
|
|
370
491
|
}
|
|
371
492
|
}
|
|
372
|
-
const
|
|
373
|
-
class
|
|
493
|
+
const dim = 128;
|
|
494
|
+
class ViewerHelper extends ViewHelper {
|
|
374
495
|
viewer;
|
|
375
496
|
overlayPass;
|
|
376
|
-
constructor(
|
|
377
|
-
super(
|
|
378
|
-
|
|
379
|
-
|
|
497
|
+
constructor(viewer) {
|
|
498
|
+
super(viewer.camera, viewer.el);
|
|
499
|
+
this.viewer = viewer;
|
|
500
|
+
const orthoCamera = new OrthographicCamera(-2, 2, 2, -2, 0, 4);
|
|
501
|
+
orthoCamera.position.set(0, 0, 2);
|
|
502
|
+
this.overlayPass = pass(this, orthoCamera);
|
|
503
|
+
this.setLabels("x", "y", "z");
|
|
380
504
|
}
|
|
381
505
|
_update = () => {
|
|
382
506
|
this.quaternion.copy(this.viewer.camera.quaternion).invert();
|
|
383
|
-
const
|
|
384
|
-
let
|
|
385
|
-
|
|
507
|
+
const location = this.location;
|
|
508
|
+
let x, y;
|
|
509
|
+
if (location.left !== null) {
|
|
510
|
+
x = location.left;
|
|
511
|
+
} else {
|
|
512
|
+
x = this.viewer.el.offsetWidth - dim - location.right;
|
|
513
|
+
}
|
|
514
|
+
if (location.top !== null) {
|
|
515
|
+
y = location.top;
|
|
516
|
+
} else {
|
|
517
|
+
y = this.viewer.el.offsetHeight - dim - location.bottom;
|
|
518
|
+
}
|
|
519
|
+
this.overlayPass.setViewport(x, y, dim, dim);
|
|
386
520
|
};
|
|
387
521
|
enable() {
|
|
388
|
-
|
|
522
|
+
this.viewer.renderPipeline.addOverlayPass(this.overlayPass);
|
|
523
|
+
this.viewer.addEventListener("beforeRender", this._update);
|
|
524
|
+
this.viewer.render();
|
|
525
|
+
return this;
|
|
389
526
|
}
|
|
390
527
|
disable() {
|
|
391
|
-
|
|
528
|
+
this.viewer.renderPipeline.removeOverlayPass(this.overlayPass);
|
|
529
|
+
this.viewer.removeEventListener("beforeRender", this._update);
|
|
530
|
+
this.viewer.render();
|
|
531
|
+
return this;
|
|
392
532
|
}
|
|
393
533
|
}
|
|
394
|
-
class
|
|
534
|
+
class Info {
|
|
395
535
|
el;
|
|
396
536
|
renderer;
|
|
397
537
|
container;
|
|
@@ -401,40 +541,79 @@ class Bt {
|
|
|
401
541
|
pointsText;
|
|
402
542
|
linesText;
|
|
403
543
|
timestampText;
|
|
404
|
-
_enabled =
|
|
405
|
-
constructor(
|
|
406
|
-
this.el =
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
544
|
+
_enabled = false;
|
|
545
|
+
constructor(el, renderer) {
|
|
546
|
+
this.el = el;
|
|
547
|
+
this.renderer = renderer;
|
|
548
|
+
const container = document.createElement("div");
|
|
549
|
+
container.style.position = "absolute";
|
|
550
|
+
container.style.left = "12px";
|
|
551
|
+
container.style.bottom = "12px";
|
|
552
|
+
container.style.fontSize = "12px";
|
|
553
|
+
container.style.color = "#fff";
|
|
554
|
+
container.style.pointerEvents = "none";
|
|
555
|
+
this.container = container;
|
|
556
|
+
function createTextEl(text, isMarginLeft = true) {
|
|
557
|
+
const el2 = document.createElement("span");
|
|
558
|
+
if (isMarginLeft) el2.style.marginLeft = "6px";
|
|
559
|
+
el2.innerText = text;
|
|
560
|
+
return el2;
|
|
561
|
+
}
|
|
562
|
+
function createBr() {
|
|
414
563
|
return document.createElement("br");
|
|
415
564
|
}
|
|
416
|
-
this.drawCallsText =
|
|
565
|
+
this.drawCallsText = createTextEl("0");
|
|
566
|
+
this.frameCallsText = createTextEl("0");
|
|
567
|
+
this.trianglesText = createTextEl("0");
|
|
568
|
+
this.linesText = createTextEl("0");
|
|
569
|
+
this.pointsText = createTextEl("0");
|
|
570
|
+
this.timestampText = createTextEl("0");
|
|
571
|
+
this.container.appendChild(createTextEl("draw calls", false));
|
|
572
|
+
this.container.appendChild(this.drawCallsText);
|
|
573
|
+
this.container.appendChild(createBr());
|
|
574
|
+
this.container.appendChild(createTextEl("frame calls", false));
|
|
575
|
+
this.container.appendChild(this.frameCallsText);
|
|
576
|
+
this.container.appendChild(createBr());
|
|
577
|
+
this.container.appendChild(createTextEl("triangles", false));
|
|
578
|
+
this.container.appendChild(this.trianglesText);
|
|
579
|
+
this.container.appendChild(createBr());
|
|
580
|
+
this.container.appendChild(createTextEl("points", false));
|
|
581
|
+
this.container.appendChild(this.pointsText);
|
|
582
|
+
this.container.appendChild(createBr());
|
|
583
|
+
this.container.appendChild(createTextEl("lines", false));
|
|
584
|
+
this.container.appendChild(this.linesText);
|
|
585
|
+
this.container.appendChild(createBr());
|
|
586
|
+
this.container.appendChild(createTextEl("timestamp", false));
|
|
587
|
+
this.container.appendChild(this.timestampText);
|
|
588
|
+
this.container.appendChild(createBr());
|
|
417
589
|
}
|
|
418
590
|
update() {
|
|
419
591
|
if (this._enabled) {
|
|
420
|
-
const { render
|
|
421
|
-
this.drawCallsText.innerText =
|
|
422
|
-
|
|
592
|
+
const { render } = this.renderer.info;
|
|
593
|
+
this.drawCallsText.innerText = render.drawCalls.toString();
|
|
594
|
+
this.frameCallsText.innerText = render.frameCalls.toString();
|
|
595
|
+
this.trianglesText.innerText = render.triangles.toString();
|
|
596
|
+
this.pointsText.innerText = render.points.toString();
|
|
597
|
+
this.linesText.innerText = render.lines.toString();
|
|
598
|
+
this.renderer.resolveTimestampsAsync(TimestampQuery.RENDER).then((timestamps) => {
|
|
599
|
+
this.timestampText.innerText = timestamps?.toFixed(2) ?? "0";
|
|
423
600
|
});
|
|
424
601
|
}
|
|
425
602
|
}
|
|
426
603
|
enable() {
|
|
427
|
-
this._enabled =
|
|
604
|
+
this._enabled = true;
|
|
605
|
+
this.el.appendChild(this.container);
|
|
428
606
|
}
|
|
429
607
|
disable() {
|
|
430
|
-
this._enabled =
|
|
608
|
+
this._enabled = false;
|
|
609
|
+
this.container.remove();
|
|
431
610
|
}
|
|
432
611
|
dispose() {
|
|
433
612
|
this.disable();
|
|
434
613
|
}
|
|
435
614
|
}
|
|
436
|
-
|
|
437
|
-
class
|
|
615
|
+
Cache.enabled = true;
|
|
616
|
+
class Viewer extends EventDispatcher {
|
|
438
617
|
el;
|
|
439
618
|
renderer;
|
|
440
619
|
scene;
|
|
@@ -449,55 +628,119 @@ class ys extends ke {
|
|
|
449
628
|
viewerHelper;
|
|
450
629
|
frameCount = 1;
|
|
451
630
|
frameloop = "demand";
|
|
452
|
-
constructor({ el
|
|
453
|
-
super()
|
|
631
|
+
constructor({ el, rendererOptions }) {
|
|
632
|
+
super();
|
|
633
|
+
this.el = el;
|
|
634
|
+
this.renderer = this._initRenderer(rendererOptions);
|
|
635
|
+
this.scene = this.createScene();
|
|
636
|
+
this.camera = this.createPerspectiveCamera();
|
|
637
|
+
this.info = new Info(this.el, this.renderer);
|
|
638
|
+
this.controls = new CameraControls(this.camera, this.renderer.domElement);
|
|
639
|
+
this.renderPipeline = new RenderPipeline(this.renderer, this.scene, this.camera);
|
|
640
|
+
this.timer = new Timer();
|
|
641
|
+
this.roomEnvironment = new RoomEnvironment(this.renderer);
|
|
642
|
+
this.interactionManager = new InteractionManager(this.renderer.domElement, this.scene, this.camera);
|
|
643
|
+
this.objectManager = new ObjectManager();
|
|
644
|
+
this.viewerHelper = new ViewerHelper(this);
|
|
645
|
+
window.addEventListener("resize", this.onWindowResize);
|
|
454
646
|
}
|
|
455
647
|
onWindowResize = () => {
|
|
456
|
-
const
|
|
457
|
-
|
|
648
|
+
const width = this.el.clientWidth;
|
|
649
|
+
const height = this.el.clientHeight;
|
|
650
|
+
if (this.camera instanceof PerspectiveCamera) {
|
|
651
|
+
this.camera.aspect = width / height;
|
|
652
|
+
this.camera.updateProjectionMatrix();
|
|
653
|
+
} else if (this.camera instanceof OrthographicCamera) {
|
|
654
|
+
this.camera.left = -width / 2;
|
|
655
|
+
this.camera.right = width / 2;
|
|
656
|
+
this.camera.top = height / 2;
|
|
657
|
+
this.camera.bottom = -height / 2;
|
|
658
|
+
this.camera.updateProjectionMatrix();
|
|
659
|
+
}
|
|
660
|
+
this.renderer.setSize(width, height);
|
|
661
|
+
this.render();
|
|
458
662
|
};
|
|
459
663
|
async init() {
|
|
460
|
-
await this.renderer.init()
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
664
|
+
await this.renderer.init();
|
|
665
|
+
this.roomEnvironment.update();
|
|
666
|
+
this.scene.environment = this.roomEnvironment.envMap;
|
|
667
|
+
}
|
|
668
|
+
setCamera(camera) {
|
|
669
|
+
this.camera = camera;
|
|
670
|
+
this.controls.camera = camera;
|
|
671
|
+
this.interactionManager.setCamera(camera);
|
|
672
|
+
this.renderPipeline.setCamera(camera);
|
|
673
|
+
this.onWindowResize();
|
|
674
|
+
this.dispatchEvent({ type: "cameraChange", camera });
|
|
675
|
+
}
|
|
676
|
+
setCameraByType(type) {
|
|
677
|
+
if (type === "perspective" && !(this.camera instanceof PerspectiveCamera)) {
|
|
678
|
+
this.setCamera(this.createPerspectiveCamera());
|
|
679
|
+
} else if (type === "orthographic" && !(this.camera instanceof OrthographicCamera)) {
|
|
680
|
+
this.setCamera(this.createOrthographicCamera());
|
|
681
|
+
}
|
|
467
682
|
}
|
|
468
|
-
render(
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
683
|
+
render(frame = 1) {
|
|
684
|
+
this.frameCount += frame;
|
|
685
|
+
return new Promise((resolve) => {
|
|
686
|
+
const fn = () => {
|
|
687
|
+
this.removeEventListener("afterRender", fn);
|
|
688
|
+
resolve();
|
|
472
689
|
};
|
|
473
|
-
this.addEventListener("afterRender",
|
|
690
|
+
this.addEventListener("afterRender", fn);
|
|
474
691
|
});
|
|
475
692
|
}
|
|
476
|
-
animate = (
|
|
477
|
-
this.timer.update(
|
|
478
|
-
|
|
479
|
-
|
|
693
|
+
animate = (time2) => {
|
|
694
|
+
this.timer.update(time2);
|
|
695
|
+
this.dispatchEvent({ type: "beforeControlsUpdate", time: time2 });
|
|
696
|
+
let needsRender = this.controls.update(this.timer.getDelta());
|
|
697
|
+
this.dispatchEvent({ type: "afterControlsUpdate", time: time2 });
|
|
698
|
+
if (this.frameCount > 0) {
|
|
699
|
+
this.frameCount--;
|
|
700
|
+
needsRender = true;
|
|
701
|
+
}
|
|
702
|
+
if (this.frameloop === "always") {
|
|
703
|
+
needsRender = true;
|
|
704
|
+
}
|
|
705
|
+
if (needsRender) {
|
|
706
|
+
this.dispatchEvent({ type: "beforeRender", time: time2 });
|
|
707
|
+
this.renderer.info.reset();
|
|
708
|
+
this.renderPipeline.render();
|
|
709
|
+
this.info.update();
|
|
710
|
+
this.dispatchEvent({ type: "afterRender", time: time2 });
|
|
711
|
+
}
|
|
480
712
|
};
|
|
481
|
-
_initRenderer(
|
|
482
|
-
const
|
|
483
|
-
logarithmicDepthBuffer:
|
|
484
|
-
antialias:
|
|
485
|
-
depth:
|
|
486
|
-
trackTimestamp:
|
|
487
|
-
...
|
|
713
|
+
_initRenderer(rendererOptions) {
|
|
714
|
+
const renderer = new WebGPURenderer({
|
|
715
|
+
logarithmicDepthBuffer: true,
|
|
716
|
+
antialias: false,
|
|
717
|
+
depth: false,
|
|
718
|
+
trackTimestamp: true,
|
|
719
|
+
...rendererOptions
|
|
488
720
|
});
|
|
489
|
-
|
|
721
|
+
renderer.setSize(this.el.clientWidth, this.el.clientHeight);
|
|
722
|
+
renderer.shadowMap.enabled = true;
|
|
723
|
+
renderer.shadowMap.type = PCFShadowMap;
|
|
724
|
+
renderer.toneMapping = AgXToneMapping;
|
|
725
|
+
renderer.toneMappingExposure = 1;
|
|
726
|
+
renderer.info.autoReset = false;
|
|
727
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
728
|
+
renderer.setAnimationLoop(this.animate);
|
|
729
|
+
this.el.appendChild(renderer.domElement);
|
|
730
|
+
return renderer;
|
|
490
731
|
}
|
|
491
732
|
createScene() {
|
|
492
|
-
const
|
|
493
|
-
|
|
733
|
+
const scene = new Scene();
|
|
734
|
+
scene.background = new Color(0);
|
|
735
|
+
return scene;
|
|
494
736
|
}
|
|
495
737
|
createPerspectiveCamera() {
|
|
496
|
-
const
|
|
497
|
-
|
|
738
|
+
const camera = new PerspectiveCamera(50, this.el.clientWidth / this.el.clientHeight, 0.1, 1e5);
|
|
739
|
+
camera.position.setScalar(5);
|
|
740
|
+
return camera;
|
|
498
741
|
}
|
|
499
742
|
createOrthographicCamera() {
|
|
500
|
-
const
|
|
743
|
+
const camera = new OrthographicCamera(
|
|
501
744
|
-this.el.clientWidth / 2,
|
|
502
745
|
this.el.clientWidth / 2,
|
|
503
746
|
this.el.clientHeight / 2,
|
|
@@ -505,415 +748,621 @@ class ys extends ke {
|
|
|
505
748
|
0.1,
|
|
506
749
|
1e5
|
|
507
750
|
);
|
|
508
|
-
|
|
751
|
+
camera.position.setScalar(5);
|
|
752
|
+
return camera;
|
|
509
753
|
}
|
|
510
754
|
dispose() {
|
|
511
|
-
this.el.removeChild(this.renderer.domElement)
|
|
755
|
+
this.el.removeChild(this.renderer.domElement);
|
|
756
|
+
window.removeEventListener("resize", this.onWindowResize);
|
|
757
|
+
this.info.dispose();
|
|
758
|
+
this.interactionManager.dispose();
|
|
759
|
+
this.roomEnvironment.dispose();
|
|
760
|
+
this.renderPipeline.dispose();
|
|
761
|
+
this.renderer.dispose();
|
|
512
762
|
}
|
|
513
763
|
}
|
|
514
|
-
class
|
|
515
|
-
constructor(
|
|
516
|
-
super(
|
|
517
|
-
}
|
|
518
|
-
parse(
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
764
|
+
class SBMXLoader extends GLTFLoader {
|
|
765
|
+
constructor(manager) {
|
|
766
|
+
super(manager);
|
|
767
|
+
}
|
|
768
|
+
parse(data, path, onLoad, onError) {
|
|
769
|
+
const formatName = getAsciiString(data.slice(0, 8));
|
|
770
|
+
switch (formatName) {
|
|
771
|
+
case "SBMG----": {
|
|
772
|
+
const swappedBuffer = swapBytes(data.slice(8));
|
|
773
|
+
return super.parse(swappedBuffer, path, onLoad, onError);
|
|
774
|
+
}
|
|
775
|
+
default: {
|
|
776
|
+
return super.parse(data, path, onLoad, onError);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
524
779
|
}
|
|
525
780
|
}
|
|
526
|
-
function
|
|
527
|
-
|
|
781
|
+
function getAsciiString(buf) {
|
|
782
|
+
const decoder = new TextDecoder();
|
|
783
|
+
return decoder.decode(new Uint8Array(buf));
|
|
528
784
|
}
|
|
529
|
-
function
|
|
530
|
-
const
|
|
531
|
-
for (let
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
785
|
+
function swapBytes(buffer) {
|
|
786
|
+
const view = new DataView(buffer);
|
|
787
|
+
for (let i = 0; i < view.byteLength; i++) {
|
|
788
|
+
const byte = view.getUint8(i);
|
|
789
|
+
const swappedByte = (byte >> 4 & 15) + (byte << 4 & 240);
|
|
790
|
+
view.setUint8(i, swappedByte);
|
|
791
|
+
}
|
|
792
|
+
return view.buffer;
|
|
536
793
|
}
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
794
|
+
const BYTE_LENGTH_MAGIC = 8;
|
|
795
|
+
const BYTE_LENGTH_HEAD = 130;
|
|
796
|
+
const BYTE_LENGTH_ATTACH = 66;
|
|
797
|
+
const MAGIC_SBK = "SBK-----";
|
|
798
|
+
const MAGIC_SBM = "SBM-----";
|
|
799
|
+
const textDecoder = new TextDecoder();
|
|
800
|
+
const textEncoder = new TextEncoder();
|
|
801
|
+
function decodeSbk(buffer) {
|
|
802
|
+
let read = BYTE_LENGTH_MAGIC;
|
|
803
|
+
const magicBytes = new Uint8Array(buffer, 0, read);
|
|
804
|
+
const magic = textDecoder.decode(magicBytes);
|
|
805
|
+
if (magic !== MAGIC_SBK) {
|
|
806
|
+
return buffer;
|
|
807
|
+
}
|
|
808
|
+
read += BYTE_LENGTH_HEAD;
|
|
809
|
+
const headBytes = new Uint8Array(buffer, 0, read);
|
|
810
|
+
const version = headBytes[40];
|
|
811
|
+
if (version === 1) {
|
|
812
|
+
const magicBuf = textEncoder.encode(MAGIC_SBM);
|
|
813
|
+
const versionBuf = new Uint8Array([headBytes[41]]);
|
|
814
|
+
const sbmKey = getSbmKey(headBytes);
|
|
815
|
+
const encodeKey = getLeftKey(sbmKey);
|
|
816
|
+
let j = BYTE_LENGTH_ATTACH % encodeKey.length;
|
|
817
|
+
const offset = read + BYTE_LENGTH_ATTACH;
|
|
818
|
+
const bodyBytes = new Uint8Array(buffer, offset);
|
|
819
|
+
const bodyBuf = new Uint8Array(bodyBytes.length);
|
|
820
|
+
for (let i = 0; i < bodyBytes.length; i++) {
|
|
821
|
+
bodyBuf[i] = bodyBytes[i] ^ encodeKey[j];
|
|
822
|
+
j = (j + 1) % encodeKey.length;
|
|
823
|
+
}
|
|
824
|
+
const buf = new Uint8Array(magicBuf.length + versionBuf.length + bodyBuf.length);
|
|
825
|
+
buf.set(magicBuf);
|
|
826
|
+
buf.set(versionBuf, magicBuf.length);
|
|
827
|
+
buf.set(bodyBuf, magicBuf.length + versionBuf.length);
|
|
828
|
+
return buf.buffer;
|
|
553
829
|
}
|
|
554
830
|
}
|
|
555
|
-
function
|
|
556
|
-
const
|
|
557
|
-
|
|
831
|
+
function getSbmKey(headBuffer) {
|
|
832
|
+
const bytes = new Uint8Array(128);
|
|
833
|
+
bytes.set(headBuffer.slice(42, 42 + 32), 0);
|
|
834
|
+
bytes.set(headBuffer.slice(74, 74 + 64), 32);
|
|
835
|
+
bytes.set(headBuffer.slice(8, 8 + 32), 96);
|
|
836
|
+
return bytes;
|
|
558
837
|
}
|
|
559
|
-
function
|
|
560
|
-
const
|
|
561
|
-
|
|
838
|
+
function getLeftKey(sbmKey) {
|
|
839
|
+
const bytes = new Uint8Array(64);
|
|
840
|
+
bytes.set(sbmKey.slice(0, 8), 0);
|
|
841
|
+
bytes.set(sbmKey.slice(24, 24 + 8), 8);
|
|
842
|
+
bytes.set(sbmKey.slice(40, 40 + 16), 16);
|
|
843
|
+
bytes.set(sbmKey.slice(64, 64 + 24), 32);
|
|
844
|
+
bytes.set(sbmKey.slice(96, 96 + 8), 56);
|
|
845
|
+
return bytes;
|
|
562
846
|
}
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
let
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
847
|
+
const getTextureName = (dataView, texnamelen, offset) => {
|
|
848
|
+
const temp = dataView.buffer.slice(offset, offset + texnamelen);
|
|
849
|
+
let textureName = textDecoder.decode(temp);
|
|
850
|
+
textureName = textureName.replace("\\", "/");
|
|
851
|
+
if (!textureName.startsWith("Maps/")) {
|
|
852
|
+
textureName = `Maps/${textureName}`;
|
|
853
|
+
}
|
|
854
|
+
return textureName;
|
|
855
|
+
};
|
|
856
|
+
const generateSbmMaterial = (materialId, arr, png) => {
|
|
857
|
+
const [d0, d1, d2, d3, side] = arr;
|
|
858
|
+
const trans = png || d3 < 1;
|
|
859
|
+
const material = new MeshStandardNodeMaterial({
|
|
860
|
+
name: materialId,
|
|
861
|
+
color: new Color(d0, d1, d2),
|
|
862
|
+
opacity: d3,
|
|
863
|
+
transparent: trans,
|
|
574
864
|
alphaTest: 0.01,
|
|
575
|
-
side
|
|
865
|
+
side
|
|
576
866
|
});
|
|
577
|
-
|
|
578
|
-
|
|
867
|
+
return material;
|
|
868
|
+
};
|
|
869
|
+
const isPNG = (textureUrl) => {
|
|
870
|
+
return textureUrl?.toLowerCase().endsWith(".png") ?? false;
|
|
871
|
+
};
|
|
872
|
+
class SBMParser {
|
|
579
873
|
data;
|
|
580
874
|
options;
|
|
581
875
|
textureLoader;
|
|
582
|
-
littleEndian =
|
|
876
|
+
littleEndian = true;
|
|
583
877
|
materials = /* @__PURE__ */ new Map();
|
|
584
|
-
constructor(
|
|
585
|
-
this.data =
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
let
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
878
|
+
constructor(data, options) {
|
|
879
|
+
this.data = data;
|
|
880
|
+
this.options = options;
|
|
881
|
+
this.textureLoader = new TextureLoader(options.manager);
|
|
882
|
+
this.textureLoader.setCrossOrigin(options.crossOrigin);
|
|
883
|
+
this.textureLoader.setRequestHeader(options.requestHeader);
|
|
884
|
+
}
|
|
885
|
+
parse(onLoad, onError) {
|
|
886
|
+
let buffer = this.data;
|
|
887
|
+
const decodeBuffer = decodeSbk(this.data);
|
|
888
|
+
if (decodeBuffer) {
|
|
889
|
+
buffer = decodeBuffer;
|
|
890
|
+
}
|
|
891
|
+
const dataView = new DataView(buffer);
|
|
892
|
+
let offset = 8;
|
|
893
|
+
const version = dataView.getUint8(offset);
|
|
894
|
+
offset += 1;
|
|
895
|
+
if (version === 1) {
|
|
896
|
+
this._parseV2(dataView, offset, onLoad);
|
|
897
|
+
} else if (version === 2) {
|
|
898
|
+
this._parseV2(dataView, offset, onLoad);
|
|
899
|
+
} else if (version === 3) {
|
|
900
|
+
this._parseV3(dataView, offset, onLoad);
|
|
901
|
+
} else {
|
|
902
|
+
onError?.(new Error(`SBMLoader: Unsupported SBM version: ${version}`));
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
_parseV2(dataView, offset, onLoad) {
|
|
906
|
+
const sbm = new Group();
|
|
907
|
+
const materialCount = dataView.getUint16(offset, this.littleEndian);
|
|
908
|
+
offset += 2;
|
|
909
|
+
const meshCount = dataView.getUint16(offset, this.littleEndian);
|
|
910
|
+
offset += 2;
|
|
911
|
+
for (let i = 0; i < materialCount; ++i) {
|
|
912
|
+
const materialId = dataView.getUint16(offset, this.littleEndian).toString();
|
|
913
|
+
offset += 2;
|
|
914
|
+
offset += 4;
|
|
915
|
+
offset += 4;
|
|
916
|
+
offset += 4;
|
|
917
|
+
offset += 4;
|
|
918
|
+
const d0 = dataView.getFloat32(offset, this.littleEndian);
|
|
919
|
+
offset += 4;
|
|
920
|
+
const d1 = dataView.getFloat32(offset, this.littleEndian);
|
|
921
|
+
offset += 4;
|
|
922
|
+
const d2 = dataView.getFloat32(offset, this.littleEndian);
|
|
923
|
+
offset += 4;
|
|
924
|
+
const d3 = dataView.getFloat32(offset, this.littleEndian);
|
|
925
|
+
offset += 4;
|
|
926
|
+
offset += 4;
|
|
927
|
+
offset += 4;
|
|
928
|
+
offset += 4;
|
|
929
|
+
offset += 4;
|
|
930
|
+
let side = dataView.getUint8(offset);
|
|
931
|
+
offset += 1;
|
|
932
|
+
if (side === 0) side = FrontSide;
|
|
933
|
+
else if (side === 1) side = BackSide;
|
|
934
|
+
else if (side === 2) side = DoubleSide;
|
|
935
|
+
const textureNameLength = dataView.getUint16(offset, this.littleEndian);
|
|
936
|
+
offset += 2;
|
|
937
|
+
const textureName = textureNameLength > 0 ? getTextureName(dataView, textureNameLength, offset) : "";
|
|
938
|
+
offset += textureNameLength;
|
|
939
|
+
if (!this.materials.has(materialId)) {
|
|
940
|
+
const materialInfo = [d0, d1, d2, d3, side];
|
|
941
|
+
const material = generateSbmMaterial(materialId, materialInfo, isPNG(textureName));
|
|
942
|
+
this.materials.set(materialId, material);
|
|
943
|
+
if (textureName && this.options.path) {
|
|
944
|
+
this.textureLoader.load(LoaderUtils.resolveURL(textureName, this.options.path), (texture) => {
|
|
945
|
+
texture.colorSpace = SRGBColorSpace;
|
|
946
|
+
texture.wrapS = RepeatWrapping;
|
|
947
|
+
texture.wrapT = RepeatWrapping;
|
|
948
|
+
texture.flipY = false;
|
|
949
|
+
texture.anisotropy = 16;
|
|
950
|
+
material.map = texture;
|
|
951
|
+
});
|
|
952
|
+
}
|
|
622
953
|
}
|
|
623
954
|
}
|
|
624
|
-
for (let
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
955
|
+
for (let i = 0; i < meshCount; ++i) {
|
|
956
|
+
const meshId = dataView.getUint16(offset, this.littleEndian).toString();
|
|
957
|
+
offset += 2;
|
|
958
|
+
const materialId = dataView.getUint16(offset, this.littleEndian).toString();
|
|
959
|
+
offset += 2;
|
|
960
|
+
const vertices = [];
|
|
961
|
+
const texcoords = [];
|
|
962
|
+
const faces = [];
|
|
963
|
+
const faceVertexUvs = [];
|
|
964
|
+
const verticesCount = dataView.getUint16(offset, this.littleEndian);
|
|
965
|
+
offset += 2;
|
|
966
|
+
if (verticesCount > 0) {
|
|
967
|
+
for (let j = 0; j < verticesCount; j++) {
|
|
968
|
+
const vector3 = new Vector3();
|
|
969
|
+
vector3.setX(dataView.getFloat32(offset, this.littleEndian));
|
|
970
|
+
offset += 4;
|
|
971
|
+
vector3.setY(dataView.getFloat32(offset, this.littleEndian));
|
|
972
|
+
offset += 4;
|
|
973
|
+
vector3.setZ(dataView.getFloat32(offset, this.littleEndian));
|
|
974
|
+
offset += 4;
|
|
975
|
+
vertices.push(vector3);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
const normalsCount = dataView.getUint16(offset, this.littleEndian);
|
|
979
|
+
offset += 2;
|
|
980
|
+
if (normalsCount > 0) {
|
|
981
|
+
for (let j = 0; j < normalsCount; j++) {
|
|
982
|
+
offset += 4;
|
|
983
|
+
offset += 4;
|
|
984
|
+
offset += 4;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
const texcoordCount = dataView.getUint16(offset, this.littleEndian);
|
|
988
|
+
offset += 2;
|
|
989
|
+
if (texcoordCount > 0) {
|
|
990
|
+
for (let j = 0; j < texcoordCount; j++) {
|
|
991
|
+
const texcoord = new Vector2();
|
|
992
|
+
texcoord.setX(dataView.getFloat32(offset, this.littleEndian));
|
|
993
|
+
offset += 4;
|
|
994
|
+
texcoord.setY(dataView.getFloat32(offset, this.littleEndian));
|
|
995
|
+
offset += 4;
|
|
996
|
+
texcoords.push(texcoord);
|
|
634
997
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
|
|
998
|
+
}
|
|
999
|
+
const idxCount = dataView.getUint16(offset, this.littleEndian);
|
|
1000
|
+
offset += 2;
|
|
1001
|
+
if (idxCount > 0) {
|
|
1002
|
+
for (let j = 0; j < idxCount; j++) {
|
|
1003
|
+
const a = dataView.getUint16(offset, this.littleEndian);
|
|
1004
|
+
offset += 2;
|
|
1005
|
+
const b = dataView.getUint16(offset, this.littleEndian);
|
|
1006
|
+
offset += 2;
|
|
1007
|
+
const c = dataView.getUint16(offset, this.littleEndian);
|
|
1008
|
+
offset += 2;
|
|
1009
|
+
const face = [a, b, c];
|
|
1010
|
+
faces.push(face);
|
|
1011
|
+
if (texcoords.length > 0) {
|
|
1012
|
+
faceVertexUvs.push([texcoords[face[0]], texcoords[face[1]], texcoords[face[2]]]);
|
|
1013
|
+
}
|
|
644
1014
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1015
|
+
}
|
|
1016
|
+
const positions = [], uvs = [];
|
|
1017
|
+
for (let i2 = 0; i2 < faces.length; i2++) {
|
|
1018
|
+
const face = faces[i2];
|
|
1019
|
+
const vertex = vertices[face[0]], vertex2 = vertices[face[1]], vertex3 = vertices[face[2]];
|
|
1020
|
+
positions.push(...vertex.toArray(), ...vertex2.toArray(), ...vertex3.toArray());
|
|
1021
|
+
let uv2 = new Vector2(), uv22 = new Vector2(), uv3 = new Vector2();
|
|
1022
|
+
const vertexUvs = faceVertexUvs[i2];
|
|
1023
|
+
if (vertexUvs !== void 0) {
|
|
1024
|
+
uv2 = vertexUvs[0];
|
|
1025
|
+
uv22 = vertexUvs[1];
|
|
1026
|
+
uv3 = vertexUvs[2];
|
|
656
1027
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
k !== void 0 && (ee = k[0], te = k[1], se = k[2]), x.push(...ee.toArray(), ...te.toArray(), ...se.toArray());
|
|
1028
|
+
uvs.push(...uv2.toArray(), ...uv22.toArray(), ...uv3.toArray());
|
|
1029
|
+
}
|
|
1030
|
+
const positionArray = new Float32Array(positions), uvArray = new Float32Array(uvs);
|
|
1031
|
+
const bufferGeometry = new BufferGeometry();
|
|
1032
|
+
if (positionArray.length > 0) {
|
|
1033
|
+
bufferGeometry.setAttribute("position", new BufferAttribute(positionArray, 3));
|
|
664
1034
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1035
|
+
if (uvArray.length > 0) {
|
|
1036
|
+
bufferGeometry.setAttribute("uv", new BufferAttribute(uvArray, 2));
|
|
1037
|
+
}
|
|
1038
|
+
bufferGeometry.computeVertexNormals();
|
|
1039
|
+
if (this.materials.has(materialId)) {
|
|
1040
|
+
const sbmChild = new Mesh(bufferGeometry, this.materials.get(materialId));
|
|
1041
|
+
sbmChild.name = meshId;
|
|
1042
|
+
sbmChild.castShadow = true;
|
|
1043
|
+
sbmChild.receiveShadow = true;
|
|
1044
|
+
sbm.add(sbmChild);
|
|
669
1045
|
}
|
|
670
1046
|
}
|
|
671
|
-
|
|
1047
|
+
onLoad(sbm);
|
|
672
1048
|
}
|
|
673
|
-
_parseV3(
|
|
674
|
-
const
|
|
675
|
-
console.warn("SBMLoader: SBM version 3 is not supported yet.")
|
|
1049
|
+
_parseV3(_dataView, _offset, onLoad) {
|
|
1050
|
+
const sbm = new Group();
|
|
1051
|
+
console.warn("SBMLoader: SBM version 3 is not supported yet.");
|
|
1052
|
+
onLoad(sbm);
|
|
676
1053
|
}
|
|
677
1054
|
}
|
|
678
|
-
class
|
|
679
|
-
load(
|
|
680
|
-
let
|
|
681
|
-
if (this.resourcePath !== "")
|
|
682
|
-
|
|
683
|
-
else if (this.path !== "") {
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
} else
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1055
|
+
class SBMLoader extends Loader {
|
|
1056
|
+
load(url, onLoad, onProgress, onError) {
|
|
1057
|
+
let resourcePath;
|
|
1058
|
+
if (this.resourcePath !== "") {
|
|
1059
|
+
resourcePath = this.resourcePath;
|
|
1060
|
+
} else if (this.path !== "") {
|
|
1061
|
+
const relativeUrl = LoaderUtils.extractUrlBase(url);
|
|
1062
|
+
resourcePath = LoaderUtils.resolveURL(relativeUrl, this.path);
|
|
1063
|
+
} else {
|
|
1064
|
+
resourcePath = LoaderUtils.extractUrlBase(url);
|
|
1065
|
+
}
|
|
1066
|
+
this.manager.itemStart(url);
|
|
1067
|
+
const _onError = (e) => {
|
|
1068
|
+
if (onError) {
|
|
1069
|
+
onError(e);
|
|
1070
|
+
} else {
|
|
1071
|
+
console.error(e);
|
|
1072
|
+
}
|
|
1073
|
+
this.manager.itemError(url);
|
|
1074
|
+
this.manager.itemEnd(url);
|
|
1075
|
+
};
|
|
1076
|
+
const loader = new FileLoader(this.manager);
|
|
1077
|
+
loader.setPath(this.path);
|
|
1078
|
+
loader.setResponseType("arraybuffer");
|
|
1079
|
+
loader.setRequestHeader(this.requestHeader);
|
|
1080
|
+
loader.setWithCredentials(this.withCredentials);
|
|
1081
|
+
loader.load(
|
|
1082
|
+
url,
|
|
1083
|
+
(data) => {
|
|
695
1084
|
try {
|
|
696
1085
|
this.parse(
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
(
|
|
700
|
-
|
|
1086
|
+
data,
|
|
1087
|
+
resourcePath,
|
|
1088
|
+
(sbm) => {
|
|
1089
|
+
onLoad(sbm);
|
|
1090
|
+
this.manager.itemEnd(url);
|
|
701
1091
|
},
|
|
702
|
-
|
|
1092
|
+
_onError
|
|
703
1093
|
);
|
|
704
|
-
} catch (
|
|
705
|
-
|
|
1094
|
+
} catch (e) {
|
|
1095
|
+
_onError(e);
|
|
706
1096
|
}
|
|
707
1097
|
},
|
|
708
|
-
|
|
709
|
-
|
|
1098
|
+
onProgress,
|
|
1099
|
+
_onError
|
|
710
1100
|
);
|
|
711
1101
|
}
|
|
712
|
-
parse(
|
|
713
|
-
const
|
|
714
|
-
if (
|
|
715
|
-
|
|
1102
|
+
parse(data, path, onLoad, onError) {
|
|
1103
|
+
const formatName = new TextDecoder().decode(data.slice(0, 8));
|
|
1104
|
+
if (formatName !== "SBK-----" && formatName !== "SBM-----") {
|
|
1105
|
+
onError?.(new Error(`SBMLoader: Invalid SBM format: ${formatName}`));
|
|
716
1106
|
return;
|
|
717
1107
|
}
|
|
718
|
-
new
|
|
719
|
-
path
|
|
1108
|
+
const parser = new SBMParser(data, {
|
|
1109
|
+
path,
|
|
720
1110
|
crossOrigin: this.crossOrigin,
|
|
721
1111
|
requestHeader: this.requestHeader,
|
|
722
1112
|
manager: this.manager
|
|
723
|
-
})
|
|
1113
|
+
});
|
|
1114
|
+
parser.parse(onLoad, onError);
|
|
724
1115
|
}
|
|
725
|
-
parseAsync(
|
|
726
|
-
return new Promise((
|
|
1116
|
+
parseAsync(data, path) {
|
|
1117
|
+
return new Promise((resolve, reject) => {
|
|
727
1118
|
this.parse(
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
(
|
|
731
|
-
|
|
1119
|
+
data,
|
|
1120
|
+
path,
|
|
1121
|
+
(sbm) => {
|
|
1122
|
+
resolve(sbm);
|
|
732
1123
|
},
|
|
733
|
-
(
|
|
734
|
-
|
|
1124
|
+
(err) => {
|
|
1125
|
+
reject(err);
|
|
735
1126
|
}
|
|
736
1127
|
);
|
|
737
1128
|
});
|
|
738
1129
|
}
|
|
739
1130
|
}
|
|
740
|
-
class
|
|
1131
|
+
class ModelLoaderManager {
|
|
741
1132
|
static CACHE_NAME = "u-space-model-loader-cache";
|
|
742
|
-
static fileLoader = new
|
|
743
|
-
static dracoLoader = new
|
|
744
|
-
`https://cdn.jsdelivr.net/npm/three@0.${
|
|
1133
|
+
static fileLoader = new FileLoader().setResponseType("arraybuffer");
|
|
1134
|
+
static dracoLoader = new DRACOLoader().setDecoderPath(
|
|
1135
|
+
`https://cdn.jsdelivr.net/npm/three@0.${REVISION}.0/examples/jsm/libs/draco/`
|
|
745
1136
|
);
|
|
746
|
-
static ktx2Loader = new
|
|
747
|
-
`https://cdn.jsdelivr.net/npm/three@0.${
|
|
1137
|
+
static ktx2Loader = new KTX2Loader().setTranscoderPath(
|
|
1138
|
+
`https://cdn.jsdelivr.net/npm/three@0.${REVISION}.0/examples/jsm/libs/basis/`
|
|
748
1139
|
);
|
|
749
|
-
static gltfLoader = new
|
|
750
|
-
static sbmxLoader = new
|
|
751
|
-
static sbmLoader = new
|
|
752
|
-
static fbxLoader = new
|
|
753
|
-
static objLoader = new
|
|
754
|
-
static stlLoader = new
|
|
755
|
-
static setLoadingManager(
|
|
756
|
-
this.fileLoader.manager =
|
|
1140
|
+
static gltfLoader = new GLTFLoader().setDRACOLoader(ModelLoaderManager.dracoLoader).setKTX2Loader(ModelLoaderManager.ktx2Loader).setMeshoptDecoder(MeshoptDecoder);
|
|
1141
|
+
static sbmxLoader = new SBMXLoader().setDRACOLoader(ModelLoaderManager.dracoLoader).setKTX2Loader(ModelLoaderManager.ktx2Loader).setMeshoptDecoder(MeshoptDecoder);
|
|
1142
|
+
static sbmLoader = new SBMLoader();
|
|
1143
|
+
static fbxLoader = new FBXLoader();
|
|
1144
|
+
static objLoader = new OBJLoader();
|
|
1145
|
+
static stlLoader = new STLLoader();
|
|
1146
|
+
static setLoadingManager(loadingManager) {
|
|
1147
|
+
this.fileLoader.manager = loadingManager;
|
|
1148
|
+
this.dracoLoader.manager = loadingManager;
|
|
1149
|
+
this.ktx2Loader.manager = loadingManager;
|
|
1150
|
+
this.gltfLoader.manager = loadingManager;
|
|
1151
|
+
this.sbmxLoader.manager = loadingManager;
|
|
1152
|
+
this.sbmLoader.manager = loadingManager;
|
|
1153
|
+
this.fbxLoader.manager = loadingManager;
|
|
1154
|
+
this.objLoader.manager = loadingManager;
|
|
1155
|
+
this.stlLoader.manager = loadingManager;
|
|
757
1156
|
}
|
|
758
1157
|
/**
|
|
759
1158
|
* Set the renderer to enable KTX2 support and other renderer-dependent features.
|
|
760
1159
|
*/
|
|
761
|
-
static async setRenderer(
|
|
762
|
-
await
|
|
1160
|
+
static async setRenderer(renderer) {
|
|
1161
|
+
await renderer.init();
|
|
1162
|
+
this.ktx2Loader.detectSupport(renderer);
|
|
763
1163
|
}
|
|
764
1164
|
/**
|
|
765
1165
|
* Configure Draco decoder path.
|
|
766
1166
|
*/
|
|
767
|
-
static setDracoPath(
|
|
768
|
-
this.dracoLoader.setDecoderPath(
|
|
1167
|
+
static setDracoPath(path) {
|
|
1168
|
+
this.dracoLoader.setDecoderPath(path);
|
|
769
1169
|
}
|
|
770
1170
|
/**
|
|
771
1171
|
* Configure KTX2 transcoder path.
|
|
772
1172
|
*/
|
|
773
|
-
static setKTX2Path(
|
|
774
|
-
this.ktx2Loader.setTranscoderPath(
|
|
775
|
-
}
|
|
776
|
-
static async loadAsync(
|
|
777
|
-
const
|
|
778
|
-
let
|
|
779
|
-
|
|
1173
|
+
static setKTX2Path(path) {
|
|
1174
|
+
this.ktx2Loader.setTranscoderPath(path);
|
|
1175
|
+
}
|
|
1176
|
+
static async loadAsync(url, parameters = { persistent: true }) {
|
|
1177
|
+
const extension = url.split(".").pop()?.split("?")[0].toLowerCase();
|
|
1178
|
+
let buffer;
|
|
1179
|
+
if (parameters.persistent && typeof caches !== "undefined") {
|
|
1180
|
+
buffer = await this.getPersistentData(url);
|
|
1181
|
+
} else {
|
|
1182
|
+
buffer = await this.fileLoader.loadAsync(url);
|
|
1183
|
+
}
|
|
1184
|
+
switch (extension) {
|
|
780
1185
|
case "gltf":
|
|
781
1186
|
case "glb":
|
|
782
|
-
|
|
1187
|
+
const gltf = await this.gltfLoader.parseAsync(buffer, LoaderUtils.extractUrlBase(url));
|
|
1188
|
+
return gltf.scene;
|
|
783
1189
|
case "sbmx":
|
|
784
|
-
|
|
1190
|
+
const sbmx = await this.sbmxLoader.parseAsync(buffer, LoaderUtils.extractUrlBase(url));
|
|
1191
|
+
return sbmx.scene;
|
|
785
1192
|
case "sbm":
|
|
786
|
-
|
|
1193
|
+
const sbm = await this.sbmLoader.parseAsync(buffer, LoaderUtils.extractUrlBase(url));
|
|
1194
|
+
return sbm;
|
|
787
1195
|
case "fbx":
|
|
788
|
-
|
|
1196
|
+
const fbx = this.fbxLoader.parse(buffer, LoaderUtils.extractUrlBase(url));
|
|
1197
|
+
return fbx;
|
|
789
1198
|
case "obj":
|
|
790
|
-
const
|
|
791
|
-
|
|
1199
|
+
const text = new TextDecoder().decode(buffer);
|
|
1200
|
+
const obj = this.objLoader.parse(text);
|
|
1201
|
+
return obj;
|
|
792
1202
|
case "stl":
|
|
793
|
-
const
|
|
794
|
-
|
|
1203
|
+
const geometry = this.stlLoader.parse(buffer);
|
|
1204
|
+
const mesh = new Mesh(geometry, new MeshStandardNodeMaterial());
|
|
1205
|
+
return mesh;
|
|
795
1206
|
default:
|
|
796
|
-
throw new Error(`Unsupported model format: ${
|
|
1207
|
+
throw new Error(`Unsupported model format: ${extension}`);
|
|
797
1208
|
}
|
|
798
1209
|
}
|
|
799
|
-
static async getPersistentData(
|
|
1210
|
+
static async getPersistentData(url) {
|
|
800
1211
|
try {
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1212
|
+
const cache = await caches.open(this.CACHE_NAME);
|
|
1213
|
+
const cachedResponse = await cache.match(url);
|
|
1214
|
+
if (cachedResponse) {
|
|
1215
|
+
return await cachedResponse.arrayBuffer();
|
|
1216
|
+
}
|
|
1217
|
+
const buffer = await this.fileLoader.loadAsync(url);
|
|
1218
|
+
const response = new Response(buffer);
|
|
1219
|
+
await cache.put(url, response);
|
|
1220
|
+
return buffer;
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
console.warn(`[LoaderManager] Failed to use Cache API for ${url}:`, error);
|
|
1223
|
+
return await this.fileLoader.loadAsync(url);
|
|
808
1224
|
}
|
|
809
1225
|
}
|
|
810
1226
|
}
|
|
811
|
-
class
|
|
812
|
-
static textureLoader = new
|
|
813
|
-
static hdrLoader = new
|
|
814
|
-
static cubeTextureLoader = new
|
|
815
|
-
static setLoadingManager(
|
|
816
|
-
this.textureLoader.manager =
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1227
|
+
class TextureLoaderManager {
|
|
1228
|
+
static textureLoader = new TextureLoader();
|
|
1229
|
+
static hdrLoader = new HDRLoader();
|
|
1230
|
+
static cubeTextureLoader = new CubeTextureLoader();
|
|
1231
|
+
static setLoadingManager(loadingManager) {
|
|
1232
|
+
this.textureLoader.manager = loadingManager;
|
|
1233
|
+
this.hdrLoader.manager = loadingManager;
|
|
1234
|
+
this.cubeTextureLoader.manager = loadingManager;
|
|
1235
|
+
}
|
|
1236
|
+
static async loadAsyncTexture(url) {
|
|
1237
|
+
const texture = await this.textureLoader.loadAsync(url);
|
|
1238
|
+
return texture;
|
|
1239
|
+
}
|
|
1240
|
+
static async loadAsyncHDR(url) {
|
|
1241
|
+
const dataTexture = await this.hdrLoader.loadAsync(url);
|
|
1242
|
+
dataTexture.mapping = EquirectangularReflectionMapping;
|
|
1243
|
+
return dataTexture;
|
|
1244
|
+
}
|
|
1245
|
+
static async loadAsyncCubeTexture(path, urls) {
|
|
1246
|
+
this.cubeTextureLoader.setPath(path);
|
|
1247
|
+
const cubeTexture = await this.cubeTextureLoader.loadAsync(urls);
|
|
1248
|
+
return cubeTexture;
|
|
827
1249
|
}
|
|
828
1250
|
}
|
|
829
|
-
class
|
|
830
|
-
isBaseMesh =
|
|
1251
|
+
class BaseMesh extends Mesh {
|
|
1252
|
+
isBaseMesh = true;
|
|
831
1253
|
type = "BaseMesh";
|
|
832
|
-
ignoreInvisibleWhenRaycast =
|
|
833
|
-
constructor(...
|
|
834
|
-
super(...
|
|
1254
|
+
ignoreInvisibleWhenRaycast = true;
|
|
1255
|
+
constructor(...args) {
|
|
1256
|
+
super(...args);
|
|
835
1257
|
}
|
|
836
|
-
raycast(
|
|
837
|
-
|
|
1258
|
+
raycast(raycaster, intersects) {
|
|
1259
|
+
if (this.visible === false && this.ignoreInvisibleWhenRaycast === true) {
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
return super.raycast(raycaster, intersects);
|
|
838
1263
|
}
|
|
839
1264
|
}
|
|
840
|
-
class
|
|
841
|
-
isBaseGroup =
|
|
1265
|
+
class BaseGroup extends Group {
|
|
1266
|
+
isBaseGroup = true;
|
|
842
1267
|
type = "BaseGroup";
|
|
843
|
-
ignoreInvisibleWhenRaycast =
|
|
844
|
-
constructor(...
|
|
845
|
-
super(...
|
|
1268
|
+
ignoreInvisibleWhenRaycast = true;
|
|
1269
|
+
constructor(...args) {
|
|
1270
|
+
super(...args);
|
|
846
1271
|
}
|
|
847
|
-
raycast(
|
|
848
|
-
|
|
1272
|
+
raycast(raycaster, intersects) {
|
|
1273
|
+
if (this.visible === false && this.ignoreInvisibleWhenRaycast === true) {
|
|
1274
|
+
return false;
|
|
1275
|
+
}
|
|
1276
|
+
return super.raycast(raycaster, intersects);
|
|
849
1277
|
}
|
|
850
1278
|
}
|
|
851
|
-
class
|
|
852
|
-
isBaseSprite =
|
|
1279
|
+
class BaseSprite extends Sprite {
|
|
1280
|
+
isBaseSprite = true;
|
|
853
1281
|
type = "BaseSprite";
|
|
854
|
-
ignoreInvisibleWhenRaycast =
|
|
855
|
-
constructor(...
|
|
856
|
-
super(...
|
|
1282
|
+
ignoreInvisibleWhenRaycast = true;
|
|
1283
|
+
constructor(...args) {
|
|
1284
|
+
super(...args);
|
|
857
1285
|
}
|
|
858
|
-
raycast(
|
|
859
|
-
|
|
1286
|
+
raycast(raycaster, intersects) {
|
|
1287
|
+
if (this.visible === false && this.ignoreInvisibleWhenRaycast === true) {
|
|
1288
|
+
return false;
|
|
1289
|
+
}
|
|
1290
|
+
return super.raycast(raycaster, intersects);
|
|
860
1291
|
}
|
|
861
1292
|
}
|
|
862
|
-
class
|
|
863
|
-
isModel =
|
|
1293
|
+
class Model extends BaseGroup {
|
|
1294
|
+
isModel = true;
|
|
864
1295
|
type = "Model";
|
|
865
1296
|
static memoryCache = /* @__PURE__ */ new Map();
|
|
866
1297
|
constructor() {
|
|
867
1298
|
super();
|
|
868
1299
|
}
|
|
869
|
-
async loadAsync(
|
|
870
|
-
const { url
|
|
1300
|
+
async loadAsync(parameters) {
|
|
1301
|
+
const { url, cache = true, persistent = true } = parameters;
|
|
871
1302
|
try {
|
|
872
|
-
let
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1303
|
+
let objectPromise;
|
|
1304
|
+
if (cache) {
|
|
1305
|
+
if (Model.memoryCache.has(url)) {
|
|
1306
|
+
objectPromise = Model.memoryCache.get(url);
|
|
1307
|
+
} else {
|
|
1308
|
+
objectPromise = ModelLoaderManager.loadAsync(url, { persistent });
|
|
1309
|
+
Model.memoryCache.set(url, objectPromise);
|
|
1310
|
+
}
|
|
1311
|
+
} else {
|
|
1312
|
+
objectPromise = ModelLoaderManager.loadAsync(url, { persistent });
|
|
1313
|
+
}
|
|
1314
|
+
const object = await objectPromise;
|
|
1315
|
+
const clonedObject = clone(object);
|
|
1316
|
+
this.add(clonedObject);
|
|
1317
|
+
return clonedObject;
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
console.error(`[Model] Failed to load model from ${url}:`, error);
|
|
1320
|
+
return null;
|
|
878
1321
|
}
|
|
879
1322
|
}
|
|
880
1323
|
static clearMemoryCache() {
|
|
881
1324
|
this.memoryCache.clear();
|
|
882
1325
|
}
|
|
883
1326
|
static async clearPersistentCache() {
|
|
884
|
-
typeof caches
|
|
1327
|
+
if (typeof caches !== "undefined") {
|
|
1328
|
+
await caches.delete("u-space-loader-cache");
|
|
1329
|
+
}
|
|
885
1330
|
}
|
|
886
1331
|
}
|
|
887
|
-
class
|
|
888
|
-
isSphereMesh =
|
|
1332
|
+
class SphereMesh extends BaseMesh {
|
|
1333
|
+
isSphereMesh = true;
|
|
889
1334
|
type = "SphereMesh";
|
|
890
|
-
constructor({ geometryParameters
|
|
891
|
-
super()
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1335
|
+
constructor({ geometryParameters, materialParameters } = {}) {
|
|
1336
|
+
super();
|
|
1337
|
+
this.geometry = new SphereGeometry(
|
|
1338
|
+
geometryParameters?.radius,
|
|
1339
|
+
geometryParameters?.widthSegments,
|
|
1340
|
+
geometryParameters?.heightSegments,
|
|
1341
|
+
geometryParameters?.phiStart,
|
|
1342
|
+
geometryParameters?.phiLength,
|
|
1343
|
+
geometryParameters?.thetaStart,
|
|
1344
|
+
geometryParameters?.thetaLength
|
|
1345
|
+
);
|
|
1346
|
+
this.material = new MeshStandardNodeMaterial(materialParameters);
|
|
900
1347
|
}
|
|
901
1348
|
}
|
|
902
|
-
class
|
|
903
|
-
isTubeMesh =
|
|
1349
|
+
class TubeMesh extends BaseMesh {
|
|
1350
|
+
isTubeMesh = true;
|
|
904
1351
|
type = "TubeMesh";
|
|
905
|
-
constructor({ geometryParameters
|
|
906
|
-
super()
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1352
|
+
constructor({ geometryParameters, materialParameters } = {}) {
|
|
1353
|
+
super();
|
|
1354
|
+
this.geometry = new TubeGeometry(
|
|
1355
|
+
geometryParameters?.path,
|
|
1356
|
+
geometryParameters?.tubularSegments,
|
|
1357
|
+
geometryParameters?.radius,
|
|
1358
|
+
geometryParameters?.radialSegments,
|
|
1359
|
+
geometryParameters?.closed
|
|
1360
|
+
);
|
|
1361
|
+
this.material = new MeshStandardNodeMaterial(materialParameters);
|
|
913
1362
|
}
|
|
914
1363
|
}
|
|
915
|
-
class
|
|
916
|
-
isTopology =
|
|
1364
|
+
class Topology extends BaseGroup {
|
|
1365
|
+
isTopology = true;
|
|
917
1366
|
type = "Topology";
|
|
918
1367
|
// Visuals
|
|
919
1368
|
parameters = {
|
|
@@ -928,27 +1377,37 @@ class bs extends Ee {
|
|
|
928
1377
|
adjacencyMap = /* @__PURE__ */ new Map();
|
|
929
1378
|
graphGroup = null;
|
|
930
1379
|
pathMeshes = [];
|
|
931
|
-
constructor(
|
|
932
|
-
super()
|
|
1380
|
+
constructor(parameters) {
|
|
1381
|
+
super();
|
|
1382
|
+
if (parameters) {
|
|
1383
|
+
Object.assign(this.parameters, parameters);
|
|
1384
|
+
}
|
|
933
1385
|
}
|
|
934
1386
|
/**
|
|
935
1387
|
* Add a node to the topology graph.
|
|
936
1388
|
* @param id Unique identifier for the node
|
|
937
1389
|
* @param position 3D position of the node
|
|
938
1390
|
*/
|
|
939
|
-
addNode(
|
|
940
|
-
|
|
1391
|
+
addNode(id, position) {
|
|
1392
|
+
if (this.nodes.has(id)) {
|
|
1393
|
+
console.warn(`[Topology] Node ${id} already exists. Overwriting.`);
|
|
1394
|
+
}
|
|
1395
|
+
this.nodes.set(id, position);
|
|
1396
|
+
if (!this.adjacencyMap.has(id)) {
|
|
1397
|
+
this.adjacencyMap.set(id, /* @__PURE__ */ new Map());
|
|
1398
|
+
}
|
|
941
1399
|
}
|
|
942
1400
|
/**
|
|
943
1401
|
* Remove a node from the topology graph.
|
|
944
1402
|
* @param id Unique identifier for the node
|
|
945
1403
|
*/
|
|
946
|
-
removeNode(
|
|
947
|
-
if (!this.nodes.has(
|
|
948
|
-
console.warn(`[Topology] Node ${
|
|
1404
|
+
removeNode(id) {
|
|
1405
|
+
if (!this.nodes.has(id)) {
|
|
1406
|
+
console.warn(`[Topology] Node ${id} does not exist.`);
|
|
949
1407
|
return;
|
|
950
1408
|
}
|
|
951
|
-
this.nodes.delete(
|
|
1409
|
+
this.nodes.delete(id);
|
|
1410
|
+
this.adjacencyMap.delete(id);
|
|
952
1411
|
}
|
|
953
1412
|
/**
|
|
954
1413
|
* Add an edge between two nodes.
|
|
@@ -957,14 +1416,18 @@ class bs extends Ee {
|
|
|
957
1416
|
* @param weight Cost of the edge. Defaults to Euclidean distance.
|
|
958
1417
|
* @param bidirectional If true, adds the reverse edge as well. Default true.
|
|
959
1418
|
*/
|
|
960
|
-
addEdge(
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
1419
|
+
addEdge(from, to, weight, bidirectional = true) {
|
|
1420
|
+
const fromPos = this.nodes.get(from);
|
|
1421
|
+
const toPos = this.nodes.get(to);
|
|
1422
|
+
if (!fromPos || !toPos) {
|
|
1423
|
+
console.error(`[Topology] Cannot add edge. One or both nodes not found: ${from}, ${to}`);
|
|
964
1424
|
return;
|
|
965
1425
|
}
|
|
966
|
-
const
|
|
967
|
-
this.getNeighbors(
|
|
1426
|
+
const dist = weight ?? fromPos.distanceTo(toPos);
|
|
1427
|
+
this.getNeighbors(from)?.set(to, dist);
|
|
1428
|
+
if (bidirectional) {
|
|
1429
|
+
this.getNeighbors(to)?.set(from, dist);
|
|
1430
|
+
}
|
|
968
1431
|
}
|
|
969
1432
|
/**
|
|
970
1433
|
* Remove an edge between two nodes.
|
|
@@ -972,51 +1435,80 @@ class bs extends Ee {
|
|
|
972
1435
|
* @param to Node ID
|
|
973
1436
|
* @param bidirectional If true, removes the reverse edge as well. Default true.
|
|
974
1437
|
*/
|
|
975
|
-
removeEdge(
|
|
976
|
-
|
|
1438
|
+
removeEdge(from, to, bidirectional = true) {
|
|
1439
|
+
const fromNeighbors = this.getNeighbors(from);
|
|
1440
|
+
fromNeighbors?.delete(to);
|
|
1441
|
+
if (bidirectional) {
|
|
1442
|
+
const toNeighbors = this.getNeighbors(to);
|
|
1443
|
+
toNeighbors?.delete(from);
|
|
1444
|
+
}
|
|
977
1445
|
}
|
|
978
1446
|
/**
|
|
979
1447
|
* Find the shortest path between start and end nodes using Dijkstra's algorithm.
|
|
980
1448
|
*/
|
|
981
|
-
getShortestPath(
|
|
982
|
-
if (!this.nodes.has(
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1449
|
+
getShortestPath(startId, endId) {
|
|
1450
|
+
if (!this.nodes.has(startId) || !this.nodes.has(endId)) {
|
|
1451
|
+
console.warn(`[Topology] Start or End node not found.`);
|
|
1452
|
+
return [];
|
|
1453
|
+
}
|
|
1454
|
+
const distances = /* @__PURE__ */ new Map();
|
|
1455
|
+
const previous = /* @__PURE__ */ new Map();
|
|
1456
|
+
const unvisited = /* @__PURE__ */ new Set();
|
|
1457
|
+
for (const [id] of this.nodes) {
|
|
1458
|
+
distances.set(id, Infinity);
|
|
1459
|
+
previous.set(id, null);
|
|
1460
|
+
unvisited.add(id);
|
|
1461
|
+
}
|
|
1462
|
+
distances.set(startId, 0);
|
|
1463
|
+
while (unvisited.size > 0) {
|
|
1464
|
+
let currentId = null;
|
|
1465
|
+
let minDist = Infinity;
|
|
1466
|
+
for (const id of unvisited) {
|
|
1467
|
+
const d = distances.get(id);
|
|
1468
|
+
if (d < minDist) {
|
|
1469
|
+
minDist = d;
|
|
1470
|
+
currentId = id;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (currentId === null || currentId === endId) {
|
|
1474
|
+
break;
|
|
992
1475
|
}
|
|
993
|
-
if (
|
|
1476
|
+
if (minDist === Infinity) {
|
|
994
1477
|
break;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1478
|
+
}
|
|
1479
|
+
unvisited.delete(currentId);
|
|
1480
|
+
const neighbors = this.adjacencyMap?.get(currentId);
|
|
1481
|
+
if (!neighbors) {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
for (const [to, weight] of neighbors) {
|
|
1485
|
+
if (!unvisited.has(to)) continue;
|
|
1486
|
+
const alt = distances.get(currentId) + weight;
|
|
1487
|
+
if (alt < distances.get(to)) {
|
|
1488
|
+
distances.set(to, alt);
|
|
1489
|
+
previous.set(to, currentId);
|
|
1002
1490
|
}
|
|
1491
|
+
}
|
|
1003
1492
|
}
|
|
1004
|
-
const
|
|
1005
|
-
let
|
|
1006
|
-
if (
|
|
1493
|
+
const path = [];
|
|
1494
|
+
let curr = endId;
|
|
1495
|
+
if (previous.get(endId) === null && startId !== endId) {
|
|
1007
1496
|
return [];
|
|
1008
|
-
for (; c !== null; ) {
|
|
1009
|
-
const r = this.nodes.get(c);
|
|
1010
|
-
r && a.unshift(r), c = n.get(c) || null;
|
|
1011
1497
|
}
|
|
1012
|
-
|
|
1498
|
+
while (curr !== null) {
|
|
1499
|
+
const pos = this.nodes.get(curr);
|
|
1500
|
+
if (pos) path.unshift(pos);
|
|
1501
|
+
curr = previous.get(curr) || null;
|
|
1502
|
+
}
|
|
1503
|
+
return path;
|
|
1013
1504
|
}
|
|
1014
1505
|
/**
|
|
1015
1506
|
* Renders the entire topology graph structure using TubeGeometry and Spheres.
|
|
1016
1507
|
*/
|
|
1017
1508
|
renderGraph() {
|
|
1018
|
-
this.clearGraph()
|
|
1019
|
-
|
|
1509
|
+
this.clearGraph();
|
|
1510
|
+
this.graphGroup = new Group();
|
|
1511
|
+
const _dummySphere = new SphereMesh({
|
|
1020
1512
|
geometryParameters: {
|
|
1021
1513
|
radius: this.parameters.nodeRadius,
|
|
1022
1514
|
widthSegments: 16,
|
|
@@ -1026,97 +1518,128 @@ class bs extends Ee {
|
|
|
1026
1518
|
color: this.parameters.nodeColor
|
|
1027
1519
|
}
|
|
1028
1520
|
});
|
|
1029
|
-
this.nodes.forEach((
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1521
|
+
this.nodes.forEach((pos, id) => {
|
|
1522
|
+
const nodeMesh = _dummySphere.clone();
|
|
1523
|
+
nodeMesh.position.copy(pos);
|
|
1524
|
+
Object.assign(nodeMesh.userData, { id, type: "node" });
|
|
1525
|
+
this.graphGroup.add(nodeMesh);
|
|
1032
1526
|
});
|
|
1033
|
-
const
|
|
1034
|
-
this.adjacencyMap.forEach((
|
|
1035
|
-
|
|
1036
|
-
const
|
|
1037
|
-
if (
|
|
1038
|
-
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1527
|
+
const processedEdges = /* @__PURE__ */ new Set();
|
|
1528
|
+
this.adjacencyMap.forEach((edges, from) => {
|
|
1529
|
+
edges.forEach((weight, to) => {
|
|
1530
|
+
const key = [from, to].sort().join("-");
|
|
1531
|
+
if (processedEdges.has(key)) return;
|
|
1532
|
+
processedEdges.add(key);
|
|
1533
|
+
const p1 = this.nodes.get(from);
|
|
1534
|
+
const p2 = this.nodes.get(to);
|
|
1535
|
+
if (p1 && p2) {
|
|
1536
|
+
const path = new LineCurve3(p1, p2);
|
|
1537
|
+
const tubeMesh = new TubeMesh({
|
|
1042
1538
|
geometryParameters: {
|
|
1043
|
-
path
|
|
1539
|
+
path,
|
|
1044
1540
|
tubularSegments: 1,
|
|
1045
1541
|
radius: this.parameters.edgeRadius,
|
|
1046
1542
|
radialSegments: 8,
|
|
1047
|
-
closed:
|
|
1543
|
+
closed: false
|
|
1048
1544
|
},
|
|
1049
1545
|
materialParameters: {
|
|
1050
1546
|
color: this.parameters.edgeColor
|
|
1051
1547
|
}
|
|
1052
1548
|
});
|
|
1053
|
-
Object.assign(
|
|
1549
|
+
Object.assign(tubeMesh.userData, { from, to, weight, type: "edge" });
|
|
1550
|
+
this.graphGroup.add(tubeMesh);
|
|
1054
1551
|
}
|
|
1055
1552
|
});
|
|
1056
|
-
})
|
|
1553
|
+
});
|
|
1554
|
+
this.add(this.graphGroup);
|
|
1057
1555
|
}
|
|
1058
1556
|
clearGraph() {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1557
|
+
if (this.graphGroup) {
|
|
1558
|
+
this.remove(this.graphGroup);
|
|
1559
|
+
this.graphGroup.traverse((child) => {
|
|
1560
|
+
if (child instanceof Mesh) {
|
|
1561
|
+
child.geometry.dispose();
|
|
1562
|
+
child.material.dispose();
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
this.graphGroup = null;
|
|
1566
|
+
}
|
|
1062
1567
|
}
|
|
1063
1568
|
/**
|
|
1064
1569
|
* Visualizes a path using TubeGeometry (CatmullRomCurve3).
|
|
1065
1570
|
*/
|
|
1066
|
-
renderPath(
|
|
1067
|
-
if (
|
|
1068
|
-
const
|
|
1571
|
+
renderPath(points, color2 = this.parameters.pathColor) {
|
|
1572
|
+
if (points.length < 2) return;
|
|
1573
|
+
const curve = new CatmullRomCurve3(points, false, "catmullrom", 0);
|
|
1574
|
+
const mesh = new TubeMesh({
|
|
1069
1575
|
geometryParameters: {
|
|
1070
|
-
path:
|
|
1071
|
-
tubularSegments:
|
|
1576
|
+
path: curve,
|
|
1577
|
+
tubularSegments: points.length * 10,
|
|
1072
1578
|
radius: this.parameters.pathRadius,
|
|
1073
|
-
closed:
|
|
1579
|
+
closed: false
|
|
1074
1580
|
},
|
|
1075
1581
|
materialParameters: {
|
|
1076
|
-
color:
|
|
1582
|
+
color: color2
|
|
1077
1583
|
}
|
|
1078
1584
|
});
|
|
1079
|
-
|
|
1585
|
+
this.add(mesh);
|
|
1586
|
+
this.pathMeshes.push(mesh);
|
|
1587
|
+
return mesh;
|
|
1080
1588
|
}
|
|
1081
1589
|
clearPaths() {
|
|
1082
|
-
this.pathMeshes.forEach((
|
|
1083
|
-
this.remove(
|
|
1084
|
-
|
|
1590
|
+
this.pathMeshes.forEach((m) => {
|
|
1591
|
+
this.remove(m);
|
|
1592
|
+
m.geometry.dispose();
|
|
1593
|
+
m.material.dispose();
|
|
1594
|
+
});
|
|
1595
|
+
this.pathMeshes = [];
|
|
1085
1596
|
}
|
|
1086
1597
|
/** Export nodes and edges as a JSON-serializable object. */
|
|
1087
1598
|
exportData() {
|
|
1088
|
-
const
|
|
1089
|
-
this.nodes.forEach((
|
|
1090
|
-
|
|
1599
|
+
const nodes = {};
|
|
1600
|
+
this.nodes.forEach((pos, id) => {
|
|
1601
|
+
nodes[id] = { x: pos.x, y: pos.y, z: pos.z };
|
|
1091
1602
|
});
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1603
|
+
const edges = [];
|
|
1604
|
+
const processed = /* @__PURE__ */ new Set();
|
|
1605
|
+
this.adjacencyMap.forEach((neighbors, from) => {
|
|
1606
|
+
neighbors.forEach((weight, to) => {
|
|
1607
|
+
const key = [from, to].sort().join("-");
|
|
1608
|
+
if (!processed.has(key)) {
|
|
1609
|
+
processed.add(key);
|
|
1610
|
+
edges.push({ from, to, weight });
|
|
1611
|
+
}
|
|
1097
1612
|
});
|
|
1098
|
-
})
|
|
1613
|
+
});
|
|
1614
|
+
return { nodes, edges };
|
|
1099
1615
|
}
|
|
1100
1616
|
/**
|
|
1101
1617
|
* Load topology from a previously exported data object.
|
|
1102
1618
|
* Replaces all current nodes, edges, and path meshes, then re-renders the graph.
|
|
1103
1619
|
*/
|
|
1104
|
-
importData(
|
|
1105
|
-
this.clearGraph()
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1620
|
+
importData(data) {
|
|
1621
|
+
this.clearGraph();
|
|
1622
|
+
this.clearPaths();
|
|
1623
|
+
this.nodes.clear();
|
|
1624
|
+
this.adjacencyMap.clear();
|
|
1625
|
+
Object.entries(data.nodes).forEach(([id, pos]) => {
|
|
1626
|
+
this.addNode(id, new Vector3(pos.x, pos.y, pos.z));
|
|
1627
|
+
});
|
|
1628
|
+
data.edges.forEach(({ from, to, weight }) => {
|
|
1629
|
+
this.addEdge(from, to, weight);
|
|
1630
|
+
});
|
|
1631
|
+
this.renderGraph();
|
|
1110
1632
|
}
|
|
1111
1633
|
dispose() {
|
|
1112
|
-
this.clearGraph()
|
|
1634
|
+
this.clearGraph();
|
|
1635
|
+
this.clearPaths();
|
|
1113
1636
|
}
|
|
1114
|
-
getNeighbors(
|
|
1115
|
-
return this.adjacencyMap.get(
|
|
1637
|
+
getNeighbors(id) {
|
|
1638
|
+
return this.adjacencyMap.get(id);
|
|
1116
1639
|
}
|
|
1117
1640
|
}
|
|
1118
|
-
class
|
|
1119
|
-
isPoi =
|
|
1641
|
+
class Poi extends BaseSprite {
|
|
1642
|
+
isPoi = true;
|
|
1120
1643
|
type = "Poi";
|
|
1121
1644
|
parameters = {
|
|
1122
1645
|
img: "",
|
|
@@ -1126,277 +1649,426 @@ class Ts extends Xt {
|
|
|
1126
1649
|
color: "#ffffff",
|
|
1127
1650
|
iconSize: 64,
|
|
1128
1651
|
padding: 10,
|
|
1129
|
-
backgroundColor: "rgba(0, 0, 0, 0
|
|
1652
|
+
backgroundColor: "rgba(0, 0, 0, 0)",
|
|
1130
1653
|
borderRadius: 8,
|
|
1131
|
-
textPosition: "right"
|
|
1654
|
+
textPosition: "right",
|
|
1655
|
+
scaleFactor: 0.01
|
|
1132
1656
|
};
|
|
1133
|
-
|
|
1134
|
-
|
|
1657
|
+
_canvas = document.createElement("canvas");
|
|
1658
|
+
_lastParametersJSON = "";
|
|
1659
|
+
constructor(parameters = {}) {
|
|
1660
|
+
super(new SpriteNodeMaterial());
|
|
1661
|
+
Object.assign(this.parameters, parameters);
|
|
1662
|
+
this.updateAsync();
|
|
1135
1663
|
}
|
|
1136
1664
|
/**
|
|
1137
1665
|
* Update the POI with new parameters
|
|
1138
1666
|
* @param parameters Partial POI parameters
|
|
1139
1667
|
*/
|
|
1140
|
-
async updateAsync(
|
|
1141
|
-
Object.assign(this.parameters,
|
|
1142
|
-
const
|
|
1143
|
-
if (
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
if (
|
|
1151
|
-
|
|
1668
|
+
async updateAsync(parameters = {}) {
|
|
1669
|
+
Object.assign(this.parameters, parameters);
|
|
1670
|
+
const json = JSON.stringify(this.parameters);
|
|
1671
|
+
if (json === this._lastParametersJSON) return;
|
|
1672
|
+
this._lastParametersJSON = json;
|
|
1673
|
+
const canvas = this._canvas;
|
|
1674
|
+
const ctx = canvas.getContext("2d");
|
|
1675
|
+
if (!ctx) return;
|
|
1676
|
+
ctx.font = `${this.parameters.fontSize}px ${this.parameters.fontFamily}`;
|
|
1677
|
+
let image = null;
|
|
1678
|
+
if (this.parameters.img) {
|
|
1679
|
+
image = await this._loadImage(this.parameters.img);
|
|
1680
|
+
}
|
|
1681
|
+
const { text, fontSize, color: color2, iconSize, padding, backgroundColor, borderRadius, textPosition, scaleFactor } = this.parameters;
|
|
1682
|
+
const textMetrics = ctx.measureText(text);
|
|
1683
|
+
const textWidth = text ? textMetrics.width : 0;
|
|
1684
|
+
const textHeight = text ? fontSize : 0;
|
|
1685
|
+
let contentWidth = 0;
|
|
1686
|
+
let contentHeight = 0;
|
|
1687
|
+
let iconX = 0, iconY = 0;
|
|
1688
|
+
let textX = 0, textY = 0;
|
|
1689
|
+
const hasImage = !!image;
|
|
1690
|
+
const hasText = !!text;
|
|
1691
|
+
if (hasImage && hasText) {
|
|
1692
|
+
switch (textPosition) {
|
|
1152
1693
|
case "top":
|
|
1153
|
-
|
|
1694
|
+
contentWidth = Math.max(iconSize, textWidth);
|
|
1695
|
+
contentHeight = iconSize + padding + textHeight;
|
|
1696
|
+
textX = (contentWidth - textWidth) / 2;
|
|
1697
|
+
textY = textHeight / 2;
|
|
1698
|
+
iconX = (contentWidth - iconSize) / 2;
|
|
1699
|
+
iconY = textHeight + padding;
|
|
1154
1700
|
break;
|
|
1155
1701
|
case "bottom":
|
|
1156
|
-
|
|
1702
|
+
contentWidth = Math.max(iconSize, textWidth);
|
|
1703
|
+
contentHeight = iconSize + padding + textHeight;
|
|
1704
|
+
iconX = (contentWidth - iconSize) / 2;
|
|
1705
|
+
iconY = 0;
|
|
1706
|
+
textX = (contentWidth - textWidth) / 2;
|
|
1707
|
+
textY = iconSize + padding + textHeight / 2;
|
|
1157
1708
|
break;
|
|
1158
1709
|
case "left":
|
|
1159
|
-
|
|
1710
|
+
contentWidth = iconSize + padding + textWidth;
|
|
1711
|
+
contentHeight = Math.max(iconSize, textHeight);
|
|
1712
|
+
textX = 0;
|
|
1713
|
+
textY = contentHeight / 2;
|
|
1714
|
+
iconX = textWidth + padding;
|
|
1715
|
+
iconY = (contentHeight - iconSize) / 2;
|
|
1160
1716
|
break;
|
|
1717
|
+
case "right":
|
|
1161
1718
|
default:
|
|
1162
|
-
|
|
1719
|
+
contentWidth = iconSize + padding + textWidth;
|
|
1720
|
+
contentHeight = Math.max(iconSize, textHeight);
|
|
1721
|
+
iconX = 0;
|
|
1722
|
+
iconY = (contentHeight - iconSize) / 2;
|
|
1723
|
+
textX = iconSize + padding;
|
|
1724
|
+
textY = contentHeight / 2;
|
|
1163
1725
|
break;
|
|
1164
1726
|
}
|
|
1165
|
-
else
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1727
|
+
} else if (hasImage) {
|
|
1728
|
+
contentWidth = iconSize;
|
|
1729
|
+
contentHeight = iconSize;
|
|
1730
|
+
iconX = 0;
|
|
1731
|
+
iconY = 0;
|
|
1732
|
+
} else if (hasText) {
|
|
1733
|
+
contentWidth = textWidth;
|
|
1734
|
+
contentHeight = textHeight;
|
|
1735
|
+
textX = 0;
|
|
1736
|
+
textY = textHeight / 2;
|
|
1737
|
+
}
|
|
1738
|
+
const canvasWidth = contentWidth + padding * 2;
|
|
1739
|
+
const canvasHeight = contentHeight + padding * 2;
|
|
1740
|
+
canvas.width = canvasWidth;
|
|
1741
|
+
canvas.height = canvasHeight;
|
|
1742
|
+
ctx.font = `${fontSize}px ${this.parameters.fontFamily}`;
|
|
1743
|
+
if (backgroundColor) {
|
|
1744
|
+
ctx.fillStyle = backgroundColor;
|
|
1745
|
+
this._drawRoundedRect(ctx, 0, 0, canvasWidth, canvasHeight, borderRadius);
|
|
1746
|
+
ctx.fill();
|
|
1747
|
+
}
|
|
1748
|
+
if (hasImage) {
|
|
1749
|
+
ctx.drawImage(image, iconX + padding, iconY + padding, iconSize, iconSize);
|
|
1750
|
+
}
|
|
1751
|
+
if (hasText) {
|
|
1752
|
+
ctx.fillStyle = color2;
|
|
1753
|
+
const metrics = ctx.measureText(text);
|
|
1754
|
+
const actualTextHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
|
1755
|
+
const baselineOffset = metrics.actualBoundingBoxAscent - actualTextHeight / 2;
|
|
1756
|
+
ctx.textBaseline = "alphabetic";
|
|
1757
|
+
ctx.fillText(text, textX + padding, textY + padding + baselineOffset);
|
|
1758
|
+
}
|
|
1759
|
+
const oldTexture = this.material.map;
|
|
1760
|
+
const texture = new CanvasTexture(canvas);
|
|
1761
|
+
texture.colorSpace = SRGBColorSpace;
|
|
1762
|
+
this.material.map = texture;
|
|
1763
|
+
oldTexture?.dispose();
|
|
1764
|
+
this.scale.set(canvasWidth * scaleFactor, canvasHeight * scaleFactor, 1);
|
|
1765
|
+
}
|
|
1766
|
+
_loadImage(img) {
|
|
1767
|
+
return new Promise((resolve, reject) => {
|
|
1768
|
+
if (typeof img === "string") {
|
|
1769
|
+
const _img = new Image();
|
|
1770
|
+
_img.crossOrigin = "Anonymous";
|
|
1771
|
+
_img.onload = () => resolve(_img);
|
|
1772
|
+
_img.onerror = () => reject(new Error(`Failed to load image: ${img}`));
|
|
1773
|
+
_img.src = img;
|
|
1774
|
+
} else {
|
|
1775
|
+
resolve(img);
|
|
1776
|
+
}
|
|
1180
1777
|
});
|
|
1181
1778
|
}
|
|
1182
|
-
_drawRoundedRect(
|
|
1183
|
-
|
|
1779
|
+
_drawRoundedRect(ctx, x, y, width, height, radius) {
|
|
1780
|
+
ctx.beginPath();
|
|
1781
|
+
ctx.moveTo(x + radius, y);
|
|
1782
|
+
ctx.lineTo(x + width - radius, y);
|
|
1783
|
+
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
1784
|
+
ctx.lineTo(x + width, y + height - radius);
|
|
1785
|
+
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
1786
|
+
ctx.lineTo(x + radius, y + height);
|
|
1787
|
+
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
1788
|
+
ctx.lineTo(x, y + radius);
|
|
1789
|
+
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
1790
|
+
ctx.closePath();
|
|
1184
1791
|
}
|
|
1185
1792
|
dispose() {
|
|
1186
|
-
this.material.map?.dispose()
|
|
1793
|
+
this.material.map?.dispose();
|
|
1794
|
+
this.material.dispose();
|
|
1187
1795
|
}
|
|
1188
1796
|
}
|
|
1189
|
-
class
|
|
1190
|
-
isShapeMesh =
|
|
1797
|
+
class ShapeMesh extends BaseMesh {
|
|
1798
|
+
isShapeMesh = true;
|
|
1191
1799
|
type = "ShapeMesh";
|
|
1192
|
-
constructor({ geometryParameters
|
|
1193
|
-
super()
|
|
1800
|
+
constructor({ geometryParameters, materialParameters } = {}) {
|
|
1801
|
+
super();
|
|
1802
|
+
this.geometry = new ShapeGeometry(geometryParameters?.shape, geometryParameters?.curveSegments);
|
|
1803
|
+
this.material = new MeshStandardNodeMaterial(materialParameters);
|
|
1804
|
+
this.geometry.rotateX(-Math.PI / 2);
|
|
1194
1805
|
}
|
|
1195
|
-
static createFromPoints(
|
|
1196
|
-
return new
|
|
1197
|
-
...
|
|
1806
|
+
static createFromPoints(points, parameters = {}) {
|
|
1807
|
+
return new ShapeMesh({
|
|
1808
|
+
...parameters,
|
|
1198
1809
|
geometryParameters: {
|
|
1199
|
-
...
|
|
1200
|
-
shape: new
|
|
1810
|
+
...parameters.geometryParameters,
|
|
1811
|
+
shape: new Shape(points.map((point) => new Vector2(point.x, -point.z)))
|
|
1201
1812
|
}
|
|
1202
1813
|
});
|
|
1203
1814
|
}
|
|
1204
1815
|
}
|
|
1205
|
-
class
|
|
1206
|
-
isExtrudeMesh =
|
|
1816
|
+
class ExtrudeMesh extends BaseMesh {
|
|
1817
|
+
isExtrudeMesh = true;
|
|
1207
1818
|
type = "ExtrudeMesh";
|
|
1208
|
-
constructor({ geometryParameters
|
|
1209
|
-
super()
|
|
1819
|
+
constructor({ geometryParameters, materialParameters } = {}) {
|
|
1820
|
+
super();
|
|
1821
|
+
this.geometry = new ExtrudeGeometry(geometryParameters?.shape, geometryParameters?.options);
|
|
1822
|
+
this.material = new MeshStandardNodeMaterial(materialParameters);
|
|
1823
|
+
this.geometry.rotateX(-Math.PI / 2);
|
|
1210
1824
|
}
|
|
1211
|
-
static createFromPoints(
|
|
1212
|
-
return new
|
|
1213
|
-
...
|
|
1825
|
+
static createFromPoints(points, parameters = {}) {
|
|
1826
|
+
return new ExtrudeMesh({
|
|
1827
|
+
...parameters,
|
|
1214
1828
|
geometryParameters: {
|
|
1215
|
-
...
|
|
1216
|
-
shape: new
|
|
1829
|
+
...parameters.geometryParameters,
|
|
1830
|
+
shape: new Shape(points.map((point) => new Vector2(point.x, -point.z)))
|
|
1217
1831
|
}
|
|
1218
1832
|
});
|
|
1219
1833
|
}
|
|
1220
1834
|
}
|
|
1221
|
-
class
|
|
1222
|
-
isPlaneMesh =
|
|
1835
|
+
class PlaneMesh extends BaseMesh {
|
|
1836
|
+
isPlaneMesh = true;
|
|
1223
1837
|
type = "PlaneMesh";
|
|
1224
|
-
constructor({ geometryParameters
|
|
1225
|
-
super()
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1838
|
+
constructor({ geometryParameters, materialParameters } = {}) {
|
|
1839
|
+
super();
|
|
1840
|
+
this.geometry = new PlaneGeometry(
|
|
1841
|
+
geometryParameters?.width,
|
|
1842
|
+
geometryParameters?.height,
|
|
1843
|
+
geometryParameters?.widthSegments,
|
|
1844
|
+
geometryParameters?.heightSegments
|
|
1845
|
+
);
|
|
1846
|
+
this.material = new MeshStandardNodeMaterial(materialParameters);
|
|
1231
1847
|
}
|
|
1232
1848
|
}
|
|
1233
|
-
class
|
|
1234
|
-
isCircleMesh =
|
|
1849
|
+
class CircleMesh extends BaseMesh {
|
|
1850
|
+
isCircleMesh = true;
|
|
1235
1851
|
type = "CircleMesh";
|
|
1236
|
-
constructor({ geometryParameters
|
|
1852
|
+
constructor({ geometryParameters, materialParameters } = {}) {
|
|
1237
1853
|
super(
|
|
1238
|
-
new
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1854
|
+
new CircleGeometry(
|
|
1855
|
+
geometryParameters?.radius,
|
|
1856
|
+
geometryParameters?.segments,
|
|
1857
|
+
geometryParameters?.thetaStart,
|
|
1858
|
+
geometryParameters?.thetaLength
|
|
1243
1859
|
),
|
|
1244
|
-
new
|
|
1245
|
-
)
|
|
1860
|
+
new MeshStandardNodeMaterial(materialParameters)
|
|
1861
|
+
);
|
|
1862
|
+
this.geometry.rotateX(-Math.PI / 2);
|
|
1246
1863
|
}
|
|
1247
1864
|
}
|
|
1248
|
-
class
|
|
1249
|
-
static traverseMeshes(
|
|
1250
|
-
|
|
1251
|
-
|
|
1865
|
+
class ObjectUtils {
|
|
1866
|
+
static traverseMeshes(object, callback) {
|
|
1867
|
+
object.traverse((child) => {
|
|
1868
|
+
if (child instanceof Mesh) {
|
|
1869
|
+
callback(child);
|
|
1870
|
+
}
|
|
1252
1871
|
});
|
|
1253
1872
|
}
|
|
1254
1873
|
}
|
|
1255
|
-
class
|
|
1256
|
-
static flow(
|
|
1257
|
-
const { baseColor
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
const
|
|
1262
|
-
return
|
|
1263
|
-
}
|
|
1264
|
-
static
|
|
1874
|
+
class TSLEffects {
|
|
1875
|
+
static flow(parameters = {}) {
|
|
1876
|
+
const { baseColor = 16777215, flowColor = 65280, speed = 1, scale = 3, intensity = 4 } = parameters;
|
|
1877
|
+
const baseColorNode = color(new Color$1(baseColor));
|
|
1878
|
+
const flowColorNode = color(new Color$1(flowColor));
|
|
1879
|
+
const flow = uv().x.mul(scale).sub(time.mul(speed));
|
|
1880
|
+
const pattern = pow(sin(flow).add(1).mul(0.5), intensity);
|
|
1881
|
+
return mix(baseColorNode, flowColorNode, pattern);
|
|
1882
|
+
}
|
|
1883
|
+
static breathe(parameters = {}) {
|
|
1884
|
+
const { baseColor = 16777215, breathColor = 65280, speed = 1, intensity = 2 } = parameters;
|
|
1885
|
+
const baseColorNode = color(new Color$1(baseColor));
|
|
1886
|
+
const breathColorNode = color(new Color$1(breathColor));
|
|
1887
|
+
const osc = sin(time.mul(speed)).add(1).mul(0.5);
|
|
1888
|
+
const pattern = pow(osc, intensity);
|
|
1889
|
+
return mix(baseColorNode, breathColorNode, pattern);
|
|
1890
|
+
}
|
|
1891
|
+
static fluid(parameters = {}) {
|
|
1265
1892
|
const {
|
|
1266
|
-
baseColor
|
|
1267
|
-
flowColor
|
|
1268
|
-
speed
|
|
1269
|
-
scale
|
|
1270
|
-
intensity
|
|
1271
|
-
distortion
|
|
1272
|
-
} =
|
|
1273
|
-
|
|
1893
|
+
baseColor = 16777215,
|
|
1894
|
+
flowColor = 255,
|
|
1895
|
+
speed = 1,
|
|
1896
|
+
scale = 1,
|
|
1897
|
+
intensity = 1,
|
|
1898
|
+
distortion = 0.5
|
|
1899
|
+
} = parameters;
|
|
1900
|
+
const baseColorNode = color(new Color$1(baseColor));
|
|
1901
|
+
const flowColorNode = color(new Color$1(flowColor));
|
|
1902
|
+
const uvNode = uv().mul(scale);
|
|
1903
|
+
const timeNode = time.mul(speed);
|
|
1904
|
+
const noiseNode = mx_noise_float(uvNode.add(timeNode), 1, 0);
|
|
1905
|
+
const distortedUV = uvNode.add(noiseNode.mul(distortion));
|
|
1906
|
+
const pattern = mx_noise_float(distortedUV, 1, 0).add(1).mul(0.5);
|
|
1907
|
+
const fluidPattern = pow(pattern, intensity);
|
|
1908
|
+
return mix(baseColorNode, flowColorNode, fluidPattern);
|
|
1274
1909
|
}
|
|
1275
1910
|
}
|
|
1276
|
-
function
|
|
1911
|
+
function reinterpretType(_value) {
|
|
1277
1912
|
}
|
|
1278
|
-
const
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1913
|
+
const _highlightKey = "uSpaceHighlight";
|
|
1914
|
+
const _highlightEnabledKey = `${_highlightKey}/enabled`;
|
|
1915
|
+
const _highlightOpacityKey = `${_highlightKey}/opacity`;
|
|
1916
|
+
const _highlightColorKey = `${_highlightKey}/color`;
|
|
1917
|
+
const _highlightOverwriteKey = `${_highlightKey}/overwrite`;
|
|
1918
|
+
const _highlightColorNode = Fn(() => {
|
|
1919
|
+
const enabled = userData(_highlightEnabledKey, "uint");
|
|
1920
|
+
const colorUint = userData(_highlightColorKey, "uint");
|
|
1921
|
+
const overwrite = userData(_highlightOverwriteKey, "uint");
|
|
1922
|
+
const hlColorRaw = uintToColor(colorUint);
|
|
1923
|
+
const tintedColor = materialColor.mul(hlColorRaw);
|
|
1924
|
+
const finalHlColor = select(overwrite.greaterThan(0), hlColorRaw, tintedColor);
|
|
1925
|
+
const hlColor = select(enabled.greaterThan(0), finalHlColor, materialColor);
|
|
1926
|
+
return hlColor;
|
|
1284
1927
|
})();
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1928
|
+
const _highlightOpacityNode = Fn(() => {
|
|
1929
|
+
const enabled = userData(_highlightEnabledKey, "uint");
|
|
1930
|
+
const opacity = userData(_highlightOpacityKey, "float");
|
|
1931
|
+
const hlOpacity = select(enabled.greaterThan(0), opacity, materialOpacity);
|
|
1932
|
+
return hlOpacity;
|
|
1933
|
+
})();
|
|
1934
|
+
class MaterialEffects {
|
|
1935
|
+
static highlight(object, options = {}) {
|
|
1936
|
+
const highlightOptions = Object.assign(
|
|
1288
1937
|
{
|
|
1289
|
-
enabled:
|
|
1938
|
+
enabled: true,
|
|
1290
1939
|
opacity: 0.5,
|
|
1291
1940
|
color: 16711680,
|
|
1292
|
-
overwrite:
|
|
1941
|
+
overwrite: false
|
|
1293
1942
|
},
|
|
1294
|
-
|
|
1943
|
+
options
|
|
1295
1944
|
);
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1945
|
+
highlightOptions.color = new Color(highlightOptions.color).getHex();
|
|
1946
|
+
ObjectUtils.traverseMeshes(object, (mesh) => {
|
|
1947
|
+
mesh.userData[_highlightEnabledKey] = highlightOptions.enabled ? 1 : 0;
|
|
1948
|
+
mesh.userData[_highlightOpacityKey] = highlightOptions.opacity;
|
|
1949
|
+
mesh.userData[_highlightColorKey] = highlightOptions.color;
|
|
1950
|
+
mesh.userData[_highlightOverwriteKey] = highlightOptions.overwrite ? 1 : 0;
|
|
1951
|
+
const material = mesh.material;
|
|
1952
|
+
if (material && !Array.isArray(material)) {
|
|
1953
|
+
material.transparent = true;
|
|
1954
|
+
material.colorNode = _highlightColorNode;
|
|
1955
|
+
material.opacityNode = _highlightOpacityNode;
|
|
1956
|
+
material.needsUpdate = true;
|
|
1957
|
+
}
|
|
1300
1958
|
});
|
|
1301
1959
|
}
|
|
1302
1960
|
}
|
|
1303
|
-
const
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1961
|
+
const uintToColor = (colorUint) => {
|
|
1962
|
+
const r = colorUint.shiftRight(16).bitAnd(255).toFloat().div(255);
|
|
1963
|
+
const g = colorUint.shiftRight(8).bitAnd(255).toFloat().div(255);
|
|
1964
|
+
const b = colorUint.bitAnd(255).toFloat().div(255);
|
|
1965
|
+
return vec3(r, g, b);
|
|
1306
1966
|
};
|
|
1307
|
-
class
|
|
1967
|
+
class Tween extends Tween$1 {
|
|
1308
1968
|
viewer;
|
|
1309
|
-
constructor(
|
|
1310
|
-
super(
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1969
|
+
constructor(viewer, source) {
|
|
1970
|
+
super(source);
|
|
1971
|
+
this.viewer = viewer;
|
|
1972
|
+
}
|
|
1973
|
+
_update = ({ time: time2 }) => {
|
|
1974
|
+
const needsRender = this.update(time2);
|
|
1975
|
+
if (needsRender) {
|
|
1976
|
+
this.viewer.render();
|
|
1977
|
+
}
|
|
1314
1978
|
};
|
|
1315
|
-
easingByMode(
|
|
1316
|
-
const
|
|
1317
|
-
return super.easing(
|
|
1979
|
+
easingByMode(mode) {
|
|
1980
|
+
const tweenModeFunc = animationModeEnum[mode];
|
|
1981
|
+
return super.easing(tweenModeFunc);
|
|
1318
1982
|
}
|
|
1319
|
-
start(
|
|
1320
|
-
|
|
1983
|
+
start(time2, overrideStartingValues) {
|
|
1984
|
+
this.viewer.addEventListener("afterControlsUpdate", this._update);
|
|
1985
|
+
return super.start(time2, overrideStartingValues);
|
|
1321
1986
|
}
|
|
1322
1987
|
stop() {
|
|
1323
|
-
|
|
1988
|
+
this.viewer.removeEventListener("afterControlsUpdate", this._update);
|
|
1989
|
+
return super.stop();
|
|
1324
1990
|
}
|
|
1325
1991
|
}
|
|
1326
|
-
function
|
|
1327
|
-
return new Promise((
|
|
1328
|
-
const { duration
|
|
1329
|
-
|
|
1992
|
+
function tweenAnimation(viewer, source, target, options = {}, onUpdate, onStart) {
|
|
1993
|
+
return new Promise((resolve, reject) => {
|
|
1994
|
+
const { duration = 1e3, delay = 0, repeat = false, mode = "Linear.None", yoyo = false } = options;
|
|
1995
|
+
const tween = new Tween(viewer, source).to(target, duration).easingByMode(mode).delay(delay).onUpdate((e) => {
|
|
1996
|
+
onUpdate?.(e, tween);
|
|
1330
1997
|
}).onComplete(() => {
|
|
1331
|
-
|
|
1998
|
+
resolve();
|
|
1332
1999
|
}).onStop(() => {
|
|
1333
|
-
|
|
2000
|
+
reject("animation stop");
|
|
1334
2001
|
}).onStart(() => {
|
|
1335
|
-
|
|
2002
|
+
onStart?.(tween);
|
|
1336
2003
|
});
|
|
1337
|
-
typeof
|
|
2004
|
+
if (typeof repeat === "number") tween.repeat(repeat);
|
|
2005
|
+
else if (typeof repeat === "boolean" && repeat) tween.repeat(Infinity);
|
|
2006
|
+
if (yoyo) tween.repeatDelay(20);
|
|
2007
|
+
tween.yoyo(yoyo);
|
|
2008
|
+
tween.start();
|
|
1338
2009
|
});
|
|
1339
2010
|
}
|
|
1340
|
-
const
|
|
1341
|
-
"Linear.None":
|
|
1342
|
-
"Quadratic.In":
|
|
1343
|
-
"Quadratic.Out":
|
|
1344
|
-
"Quadratic.InOut":
|
|
1345
|
-
"Cubic.In":
|
|
1346
|
-
"Cubic.Out":
|
|
1347
|
-
"Cubic.InOut":
|
|
1348
|
-
"Quartic.In":
|
|
1349
|
-
"Quartic.Out":
|
|
1350
|
-
"Quartic.InOut":
|
|
1351
|
-
"Quintic.In":
|
|
1352
|
-
"Quintic.Out":
|
|
1353
|
-
"Quintic.InOut":
|
|
1354
|
-
"Sinusoidal.In":
|
|
1355
|
-
"Sinusoidal.Out":
|
|
1356
|
-
"Sinusoidal.InOut":
|
|
1357
|
-
"Exponential.In":
|
|
1358
|
-
"Exponential.Out":
|
|
1359
|
-
"Exponential.InOut":
|
|
1360
|
-
"Circular.In":
|
|
1361
|
-
"Circular.Out":
|
|
1362
|
-
"Circular.InOut":
|
|
1363
|
-
"Elastic.In":
|
|
1364
|
-
"Elastic.Out":
|
|
1365
|
-
"Elastic.InOut":
|
|
1366
|
-
"Back.In":
|
|
1367
|
-
"Back.Out":
|
|
1368
|
-
"Back.InOut":
|
|
1369
|
-
"Bounce.In":
|
|
1370
|
-
"Bounce.Out":
|
|
1371
|
-
"Bounce.InOut":
|
|
2011
|
+
const animationModeEnum = {
|
|
2012
|
+
"Linear.None": Easing.Linear.None,
|
|
2013
|
+
"Quadratic.In": Easing.Quadratic.In,
|
|
2014
|
+
"Quadratic.Out": Easing.Quadratic.Out,
|
|
2015
|
+
"Quadratic.InOut": Easing.Quadratic.InOut,
|
|
2016
|
+
"Cubic.In": Easing.Cubic.In,
|
|
2017
|
+
"Cubic.Out": Easing.Cubic.Out,
|
|
2018
|
+
"Cubic.InOut": Easing.Cubic.InOut,
|
|
2019
|
+
"Quartic.In": Easing.Quartic.In,
|
|
2020
|
+
"Quartic.Out": Easing.Quartic.Out,
|
|
2021
|
+
"Quartic.InOut": Easing.Quartic.InOut,
|
|
2022
|
+
"Quintic.In": Easing.Quintic.In,
|
|
2023
|
+
"Quintic.Out": Easing.Quintic.Out,
|
|
2024
|
+
"Quintic.InOut": Easing.Quintic.InOut,
|
|
2025
|
+
"Sinusoidal.In": Easing.Sinusoidal.In,
|
|
2026
|
+
"Sinusoidal.Out": Easing.Sinusoidal.Out,
|
|
2027
|
+
"Sinusoidal.InOut": Easing.Sinusoidal.InOut,
|
|
2028
|
+
"Exponential.In": Easing.Exponential.In,
|
|
2029
|
+
"Exponential.Out": Easing.Exponential.Out,
|
|
2030
|
+
"Exponential.InOut": Easing.Exponential.InOut,
|
|
2031
|
+
"Circular.In": Easing.Circular.In,
|
|
2032
|
+
"Circular.Out": Easing.Circular.Out,
|
|
2033
|
+
"Circular.InOut": Easing.Circular.InOut,
|
|
2034
|
+
"Elastic.In": Easing.Elastic.In,
|
|
2035
|
+
"Elastic.Out": Easing.Elastic.Out,
|
|
2036
|
+
"Elastic.InOut": Easing.Elastic.InOut,
|
|
2037
|
+
"Back.In": Easing.Back.In,
|
|
2038
|
+
"Back.Out": Easing.Back.Out,
|
|
2039
|
+
"Back.InOut": Easing.Back.InOut,
|
|
2040
|
+
"Bounce.In": Easing.Bounce.In,
|
|
2041
|
+
"Bounce.Out": Easing.Bounce.Out,
|
|
2042
|
+
"Bounce.InOut": Easing.Bounce.InOut
|
|
1372
2043
|
};
|
|
1373
2044
|
export {
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
2045
|
+
BaseGroup,
|
|
2046
|
+
BaseMesh,
|
|
2047
|
+
BaseSprite,
|
|
2048
|
+
CameraControls,
|
|
2049
|
+
CircleMesh,
|
|
2050
|
+
ExtrudeMesh,
|
|
2051
|
+
InteractionEvent,
|
|
2052
|
+
InteractionManager,
|
|
2053
|
+
MaterialEffects,
|
|
2054
|
+
Model,
|
|
2055
|
+
ModelLoaderManager,
|
|
2056
|
+
ObjectManager,
|
|
2057
|
+
ObjectUtils,
|
|
2058
|
+
PlaneMesh,
|
|
2059
|
+
Poi,
|
|
2060
|
+
RoomEnvironment,
|
|
2061
|
+
SBMLoader,
|
|
2062
|
+
SBMXLoader,
|
|
2063
|
+
ShapeMesh,
|
|
2064
|
+
SphereMesh,
|
|
2065
|
+
TSLEffects,
|
|
2066
|
+
TextureLoaderManager,
|
|
2067
|
+
Topology,
|
|
2068
|
+
TubeMesh,
|
|
2069
|
+
Tween,
|
|
2070
|
+
Viewer,
|
|
2071
|
+
reinterpretType,
|
|
2072
|
+
tweenAnimation
|
|
1402
2073
|
};
|
|
2074
|
+
//# sourceMappingURL=index.js.map
|