u-space 0.0.1 → 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 +1528 -828
- package/dist/index.js.map +1 -0
- package/dist/plugins/object-controls/ObjectControls.d.ts +69 -0
- package/dist/plugins/object-controls/index.cjs +1 -0
- package/dist/plugins/object-controls/index.d.ts +1 -0
- package/dist/plugins/object-controls/index.js +44 -0
- package/dist/plugins/topology-drawer/TopologyDrawer.d.ts +97 -0
- package/dist/plugins/topology-drawer/index.cjs +1 -0
- package/dist/plugins/topology-drawer/index.d.ts +1 -0
- package/dist/plugins/topology-drawer/index.js +182 -0
- package/dist/src/interactions/InteractionEvent.d.ts +1 -1
- package/dist/src/interactions/InteractionManager.d.ts +2 -3
- package/dist/src/objects/Topology.d.ts +20 -1
- package/docs/api-interactions.md +7 -7
- package/docs/api-objects.md +25 -3
- package/docs/api-plugins.md +150 -9
- package/docs/api-viewer.md +46 -0
- package/docs/getting-started.md +2 -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,109 +46,162 @@ 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;
|
|
57
61
|
/**
|
|
58
|
-
* The objects to check for intersections.
|
|
59
|
-
* If not set, it defaults to the children of the scene.
|
|
62
|
+
* The objects to check for intersections. default to all children of the scene (including nested).
|
|
60
63
|
*/
|
|
61
|
-
targetObjects
|
|
64
|
+
targetObjects;
|
|
62
65
|
/**
|
|
63
66
|
* Whether to enable pointer move events (pointermove, pointerenter, pointerleave).
|
|
64
67
|
* Disabled by default for performance optimization.
|
|
65
68
|
*/
|
|
66
|
-
pointerMoveEventsEnabled =
|
|
67
|
-
constructor(
|
|
68
|
-
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();
|
|
69
78
|
}
|
|
70
79
|
/**
|
|
71
80
|
* Updates the camera reference (useful when switching cameras).
|
|
72
81
|
*/
|
|
73
|
-
setCamera(
|
|
74
|
-
this.camera =
|
|
82
|
+
setCamera(camera) {
|
|
83
|
+
this.camera = camera;
|
|
75
84
|
}
|
|
76
85
|
_bindEvents() {
|
|
77
|
-
this.domElement.addEventListener("click", this._onClick)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
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;
|
|
82
97
|
}
|
|
83
98
|
_getIntersects() {
|
|
84
99
|
this.raycaster.setFromCamera(this.pointer, this.camera);
|
|
85
|
-
|
|
86
|
-
return this.raycaster.intersectObjects(e, !0);
|
|
100
|
+
return this.raycaster.intersectObjects(this.targetObjects, true);
|
|
87
101
|
}
|
|
88
102
|
/**
|
|
89
103
|
* Dispatches an event to the target object and bubbles up the parent chain.
|
|
90
104
|
*/
|
|
91
|
-
_dispatchToTarget(
|
|
92
|
-
const
|
|
93
|
-
type
|
|
94
|
-
target
|
|
95
|
-
intersect
|
|
96
|
-
originalEvent
|
|
105
|
+
_dispatchToTarget(type, target, intersect, originalEvent) {
|
|
106
|
+
const event3d = new InteractionEvent({
|
|
107
|
+
type,
|
|
108
|
+
target,
|
|
109
|
+
intersect,
|
|
110
|
+
originalEvent
|
|
97
111
|
});
|
|
98
|
-
let
|
|
99
|
-
|
|
100
|
-
|
|
112
|
+
let current = target;
|
|
113
|
+
while (current && !event3d.propagationStopped) {
|
|
114
|
+
event3d.currentTarget = current;
|
|
115
|
+
current.dispatchEvent({ type, event: event3d });
|
|
116
|
+
current = current.parent;
|
|
117
|
+
}
|
|
101
118
|
}
|
|
102
119
|
/**
|
|
103
120
|
* Handles a DOM event by raycasting and dispatching to the first intersected object.
|
|
104
121
|
*/
|
|
105
|
-
_handleEvent(
|
|
106
|
-
this._updatePointer(
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
const
|
|
110
|
-
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);
|
|
111
128
|
}
|
|
112
129
|
_onClick = (e) => {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
this.clickTimer = setTimeout(() => {
|
|
120
|
-
this._handleEvent("click", e), this.clickTimer = null;
|
|
121
|
-
}, 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;
|
|
122
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);
|
|
123
144
|
};
|
|
124
145
|
_onDblClick = (e) => {
|
|
125
|
-
|
|
146
|
+
if (this.clickTimer) {
|
|
147
|
+
clearTimeout(this.clickTimer);
|
|
148
|
+
this.clickTimer = null;
|
|
149
|
+
}
|
|
150
|
+
this._handleEvent("dblclick", e);
|
|
126
151
|
};
|
|
127
152
|
_onContextMenu = (e) => {
|
|
128
|
-
e.preventDefault()
|
|
153
|
+
e.preventDefault();
|
|
129
154
|
};
|
|
130
155
|
_onPointerDown = (e) => {
|
|
131
|
-
this.pointerDownTime = Date.now()
|
|
156
|
+
this.pointerDownTime = Date.now();
|
|
157
|
+
this._updatePointer(e);
|
|
158
|
+
this.pointerDownPos.copy(this.pointer);
|
|
159
|
+
this._handleEvent("pointerdown", e);
|
|
160
|
+
};
|
|
161
|
+
_onPointerUp = (e) => {
|
|
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
|
+
}
|
|
169
|
+
}
|
|
170
|
+
this._handleEvent("pointerup", e);
|
|
132
171
|
};
|
|
133
|
-
_onPointerUp = (e) => this._handleEvent("pointerup", e);
|
|
134
172
|
_onPointerMove = (e) => {
|
|
135
173
|
if (!this.pointerMoveEventsEnabled) return;
|
|
136
174
|
this._updatePointer(e);
|
|
137
|
-
const
|
|
138
|
-
|
|
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
|
+
}
|
|
139
190
|
};
|
|
140
191
|
/**
|
|
141
192
|
* Cleans up event listeners.
|
|
142
193
|
*/
|
|
143
194
|
dispose() {
|
|
144
|
-
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;
|
|
145
202
|
}
|
|
146
203
|
}
|
|
147
|
-
class
|
|
204
|
+
class ObjectManager {
|
|
148
205
|
idMap = /* @__PURE__ */ new Map();
|
|
149
206
|
nameMap = /* @__PURE__ */ new Map();
|
|
150
207
|
typeMap = /* @__PURE__ */ new Map();
|
|
@@ -154,85 +211,117 @@ class xt {
|
|
|
154
211
|
* Add an object with a custom id.
|
|
155
212
|
* One object can have multiple ids.
|
|
156
213
|
*/
|
|
157
|
-
add(
|
|
158
|
-
if (this.idMap.has(
|
|
159
|
-
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.`);
|
|
160
217
|
return;
|
|
161
218
|
}
|
|
162
|
-
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
|
+
}
|
|
163
237
|
}
|
|
164
238
|
/**
|
|
165
239
|
* Get an object by its custom id.
|
|
166
240
|
*/
|
|
167
|
-
getById(
|
|
168
|
-
return this.idMap.get(
|
|
241
|
+
getById(id) {
|
|
242
|
+
return this.idMap.get(id);
|
|
169
243
|
}
|
|
170
244
|
/**
|
|
171
245
|
* Get all objects with a given name.
|
|
172
246
|
*/
|
|
173
|
-
getByName(
|
|
174
|
-
return this.nameMap.get(
|
|
247
|
+
getByName(name) {
|
|
248
|
+
return this.nameMap.get(name) || /* @__PURE__ */ new Set();
|
|
175
249
|
}
|
|
176
250
|
/**
|
|
177
251
|
* Get all objects with a given type.
|
|
178
252
|
*/
|
|
179
|
-
getByType(
|
|
180
|
-
return this.typeMap.get(
|
|
253
|
+
getByType(type) {
|
|
254
|
+
return this.typeMap.get(type) || /* @__PURE__ */ new Set();
|
|
181
255
|
}
|
|
182
256
|
/**
|
|
183
257
|
* Get all ids associated with an object.
|
|
184
258
|
*/
|
|
185
|
-
getObjectIds(
|
|
186
|
-
return this.objectIds.get(
|
|
259
|
+
getObjectIds(object) {
|
|
260
|
+
return this.objectIds.get(object) || /* @__PURE__ */ new Set();
|
|
187
261
|
}
|
|
188
262
|
/**
|
|
189
263
|
* Remove an object and all its associated ids.
|
|
190
264
|
*/
|
|
191
|
-
remove(
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
for (const
|
|
195
|
-
this.idMap.delete(
|
|
196
|
-
|
|
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);
|
|
197
272
|
}
|
|
198
|
-
if (
|
|
199
|
-
const
|
|
200
|
-
|
|
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
|
+
}
|
|
201
279
|
}
|
|
202
|
-
if (
|
|
203
|
-
const
|
|
204
|
-
|
|
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
|
+
}
|
|
205
286
|
}
|
|
206
|
-
this.allObjects.delete(
|
|
287
|
+
this.allObjects.delete(object);
|
|
207
288
|
}
|
|
208
289
|
/**
|
|
209
290
|
* Remove an object by its id.
|
|
210
291
|
*/
|
|
211
|
-
removeById(
|
|
212
|
-
const
|
|
213
|
-
|
|
292
|
+
removeById(id) {
|
|
293
|
+
const object = this.idMap.get(id);
|
|
294
|
+
if (object) {
|
|
295
|
+
this.remove(object);
|
|
296
|
+
}
|
|
214
297
|
}
|
|
215
298
|
/**
|
|
216
299
|
* Remove all objects with a given name.
|
|
217
300
|
*/
|
|
218
|
-
removeByName(
|
|
219
|
-
const
|
|
220
|
-
for (const
|
|
221
|
-
this.remove(
|
|
301
|
+
removeByName(name) {
|
|
302
|
+
const objects = this.getByName(name);
|
|
303
|
+
for (const object of objects) {
|
|
304
|
+
this.remove(object);
|
|
305
|
+
}
|
|
222
306
|
}
|
|
223
307
|
/**
|
|
224
308
|
* Remove all objects with a given type.
|
|
225
309
|
*/
|
|
226
|
-
removeByType(
|
|
227
|
-
const
|
|
228
|
-
for (const
|
|
229
|
-
this.remove(
|
|
310
|
+
removeByType(type) {
|
|
311
|
+
const objects = this.getByType(type);
|
|
312
|
+
for (const object of objects) {
|
|
313
|
+
this.remove(object);
|
|
314
|
+
}
|
|
230
315
|
}
|
|
231
316
|
/**
|
|
232
317
|
* Clear all cached objects.
|
|
233
318
|
*/
|
|
234
319
|
clear() {
|
|
235
|
-
this.idMap.clear()
|
|
320
|
+
this.idMap.clear();
|
|
321
|
+
this.nameMap.clear();
|
|
322
|
+
this.typeMap.clear();
|
|
323
|
+
this.objectIds.clear();
|
|
324
|
+
this.allObjects.clear();
|
|
236
325
|
}
|
|
237
326
|
/**
|
|
238
327
|
* Get all cached objects as a Set.
|
|
@@ -247,148 +336,202 @@ class xt {
|
|
|
247
336
|
return this.allObjects.size;
|
|
248
337
|
}
|
|
249
338
|
}
|
|
250
|
-
class
|
|
339
|
+
class RenderPipeline extends RenderPipeline$1 {
|
|
251
340
|
scene;
|
|
252
341
|
camera;
|
|
253
342
|
currentOutputNode;
|
|
254
343
|
needsUpdateOutputNode;
|
|
255
|
-
#
|
|
256
|
-
constructor(...
|
|
257
|
-
const [
|
|
258
|
-
super(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
this
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
this.
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
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;
|
|
271
370
|
}
|
|
272
371
|
resetCurrentOutputNode() {
|
|
273
|
-
this.currentOutputNode.dispose()
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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;
|
|
280
385
|
}
|
|
281
386
|
render() {
|
|
282
|
-
|
|
387
|
+
if (this.needsUpdateOutputNode) {
|
|
388
|
+
this.#updateOutputNode();
|
|
389
|
+
this.needsUpdateOutputNode = false;
|
|
390
|
+
}
|
|
391
|
+
super.render();
|
|
283
392
|
}
|
|
284
393
|
}
|
|
285
|
-
const
|
|
286
|
-
Vector2
|
|
287
|
-
Vector3
|
|
288
|
-
Vector4
|
|
289
|
-
Quaternion
|
|
290
|
-
Matrix4
|
|
291
|
-
Spherical
|
|
292
|
-
Box3
|
|
293
|
-
Sphere
|
|
294
|
-
Raycaster
|
|
394
|
+
const subsetOfTHREE = {
|
|
395
|
+
Vector2,
|
|
396
|
+
Vector3,
|
|
397
|
+
Vector4,
|
|
398
|
+
Quaternion,
|
|
399
|
+
Matrix4,
|
|
400
|
+
Spherical,
|
|
401
|
+
Box3,
|
|
402
|
+
Sphere,
|
|
403
|
+
Raycaster
|
|
295
404
|
};
|
|
296
|
-
|
|
297
|
-
class
|
|
298
|
-
constructor(...
|
|
299
|
-
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;
|
|
300
412
|
}
|
|
301
413
|
/**
|
|
302
414
|
* Set absolute angle for azimuth to avoid spinning
|
|
303
415
|
*/
|
|
304
416
|
absoluteRotations() {
|
|
305
|
-
this.azimuthAngle =
|
|
417
|
+
this.azimuthAngle = absoluteAngle(this.azimuthAngle, 0);
|
|
306
418
|
}
|
|
307
|
-
async flyToBox(
|
|
308
|
-
if (
|
|
309
|
-
|
|
419
|
+
async flyToBox(box, options = {}) {
|
|
420
|
+
if (box.isEmpty()) {
|
|
421
|
+
console.warn("CameraControls.flyToBox: The provided Box3 is empty.");
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
310
424
|
this.absoluteRotations();
|
|
311
|
-
const { viewpoint
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
|
321
437
|
})
|
|
322
|
-
)
|
|
438
|
+
);
|
|
439
|
+
promises.push(this.rotateTo(azimuthAngle, polarAngle, enableTransition));
|
|
323
440
|
} else {
|
|
324
|
-
const
|
|
325
|
-
|
|
441
|
+
const sphere = new Sphere();
|
|
442
|
+
box.getBoundingSphere(sphere);
|
|
443
|
+
sphere.radius += padding;
|
|
444
|
+
promises.push(this.fitToSphere(sphere, enableTransition));
|
|
326
445
|
}
|
|
327
|
-
|
|
446
|
+
await Promise.all(promises);
|
|
447
|
+
return true;
|
|
328
448
|
}
|
|
329
|
-
async flyToObject(
|
|
330
|
-
const
|
|
331
|
-
return this.flyToBox(
|
|
449
|
+
async flyToObject(object, options = {}) {
|
|
450
|
+
const box = new Box3().setFromObject(object);
|
|
451
|
+
return this.flyToBox(box, options);
|
|
332
452
|
}
|
|
333
453
|
}
|
|
334
|
-
const
|
|
335
|
-
LEFT: new
|
|
336
|
-
RIGHT: new
|
|
337
|
-
FRONT: new
|
|
338
|
-
BACK: new
|
|
339
|
-
TOP: new
|
|
340
|
-
BOTTOM: new
|
|
341
|
-
FRONTTOP: new
|
|
342
|
-
BACKTOP: new
|
|
343
|
-
LEFTTOP: new
|
|
344
|
-
RIGHTTOP: new
|
|
345
|
-
LEFTFRONTTOP: new
|
|
346
|
-
RIGHTFRONTTOP: new
|
|
347
|
-
LEFTBACKTOP: new
|
|
348
|
-
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)
|
|
349
469
|
};
|
|
350
|
-
function
|
|
351
|
-
const
|
|
352
|
-
return
|
|
470
|
+
function absoluteAngle(targetAngle, sourceAngle) {
|
|
471
|
+
const angle = targetAngle - sourceAngle;
|
|
472
|
+
return MathUtils.euclideanModulo(angle + Math.PI, Math.PI * 2) - Math.PI;
|
|
353
473
|
}
|
|
354
|
-
class
|
|
355
|
-
environment = new
|
|
474
|
+
class RoomEnvironment {
|
|
475
|
+
environment = new RoomEnvironment$1();
|
|
356
476
|
renderer;
|
|
357
477
|
pmremGenerator;
|
|
358
478
|
envMap = null;
|
|
359
|
-
constructor(
|
|
360
|
-
this.renderer =
|
|
479
|
+
constructor(renderer) {
|
|
480
|
+
this.renderer = renderer;
|
|
481
|
+
this.pmremGenerator = new PMREMGenerator(renderer);
|
|
361
482
|
}
|
|
362
483
|
update() {
|
|
363
|
-
|
|
484
|
+
if (!this.envMap) {
|
|
485
|
+
this.envMap = this.pmremGenerator.fromScene(this.environment).texture;
|
|
486
|
+
}
|
|
364
487
|
}
|
|
365
488
|
dispose() {
|
|
366
|
-
this.pmremGenerator.dispose()
|
|
489
|
+
this.pmremGenerator.dispose();
|
|
490
|
+
this.envMap?.dispose();
|
|
367
491
|
}
|
|
368
492
|
}
|
|
369
|
-
const
|
|
370
|
-
class
|
|
493
|
+
const dim = 128;
|
|
494
|
+
class ViewerHelper extends ViewHelper {
|
|
371
495
|
viewer;
|
|
372
496
|
overlayPass;
|
|
373
|
-
constructor(
|
|
374
|
-
super(
|
|
375
|
-
|
|
376
|
-
|
|
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");
|
|
377
504
|
}
|
|
378
505
|
_update = () => {
|
|
379
506
|
this.quaternion.copy(this.viewer.camera.quaternion).invert();
|
|
380
|
-
const
|
|
381
|
-
let
|
|
382
|
-
|
|
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);
|
|
383
520
|
};
|
|
384
521
|
enable() {
|
|
385
|
-
|
|
522
|
+
this.viewer.renderPipeline.addOverlayPass(this.overlayPass);
|
|
523
|
+
this.viewer.addEventListener("beforeRender", this._update);
|
|
524
|
+
this.viewer.render();
|
|
525
|
+
return this;
|
|
386
526
|
}
|
|
387
527
|
disable() {
|
|
388
|
-
|
|
528
|
+
this.viewer.renderPipeline.removeOverlayPass(this.overlayPass);
|
|
529
|
+
this.viewer.removeEventListener("beforeRender", this._update);
|
|
530
|
+
this.viewer.render();
|
|
531
|
+
return this;
|
|
389
532
|
}
|
|
390
533
|
}
|
|
391
|
-
class
|
|
534
|
+
class Info {
|
|
392
535
|
el;
|
|
393
536
|
renderer;
|
|
394
537
|
container;
|
|
@@ -398,40 +541,79 @@ class Bt {
|
|
|
398
541
|
pointsText;
|
|
399
542
|
linesText;
|
|
400
543
|
timestampText;
|
|
401
|
-
_enabled =
|
|
402
|
-
constructor(
|
|
403
|
-
this.el =
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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() {
|
|
411
563
|
return document.createElement("br");
|
|
412
564
|
}
|
|
413
|
-
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());
|
|
414
589
|
}
|
|
415
590
|
update() {
|
|
416
591
|
if (this._enabled) {
|
|
417
|
-
const { render
|
|
418
|
-
this.drawCallsText.innerText =
|
|
419
|
-
|
|
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";
|
|
420
600
|
});
|
|
421
601
|
}
|
|
422
602
|
}
|
|
423
603
|
enable() {
|
|
424
|
-
this._enabled =
|
|
604
|
+
this._enabled = true;
|
|
605
|
+
this.el.appendChild(this.container);
|
|
425
606
|
}
|
|
426
607
|
disable() {
|
|
427
|
-
this._enabled =
|
|
608
|
+
this._enabled = false;
|
|
609
|
+
this.container.remove();
|
|
428
610
|
}
|
|
429
611
|
dispose() {
|
|
430
612
|
this.disable();
|
|
431
613
|
}
|
|
432
614
|
}
|
|
433
|
-
|
|
434
|
-
class
|
|
615
|
+
Cache.enabled = true;
|
|
616
|
+
class Viewer extends EventDispatcher {
|
|
435
617
|
el;
|
|
436
618
|
renderer;
|
|
437
619
|
scene;
|
|
@@ -446,55 +628,119 @@ class ys extends ke {
|
|
|
446
628
|
viewerHelper;
|
|
447
629
|
frameCount = 1;
|
|
448
630
|
frameloop = "demand";
|
|
449
|
-
constructor({ el
|
|
450
|
-
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);
|
|
451
646
|
}
|
|
452
647
|
onWindowResize = () => {
|
|
453
|
-
const
|
|
454
|
-
|
|
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();
|
|
455
662
|
};
|
|
456
663
|
async init() {
|
|
457
|
-
await this.renderer.init()
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
+
}
|
|
464
682
|
}
|
|
465
|
-
render(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
683
|
+
render(frame = 1) {
|
|
684
|
+
this.frameCount += frame;
|
|
685
|
+
return new Promise((resolve) => {
|
|
686
|
+
const fn = () => {
|
|
687
|
+
this.removeEventListener("afterRender", fn);
|
|
688
|
+
resolve();
|
|
469
689
|
};
|
|
470
|
-
this.addEventListener("afterRender",
|
|
690
|
+
this.addEventListener("afterRender", fn);
|
|
471
691
|
});
|
|
472
692
|
}
|
|
473
|
-
animate = (
|
|
474
|
-
this.timer.update(
|
|
475
|
-
|
|
476
|
-
|
|
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
|
+
}
|
|
477
712
|
};
|
|
478
|
-
_initRenderer(
|
|
479
|
-
const
|
|
480
|
-
logarithmicDepthBuffer:
|
|
481
|
-
antialias:
|
|
482
|
-
depth:
|
|
483
|
-
trackTimestamp:
|
|
484
|
-
...
|
|
713
|
+
_initRenderer(rendererOptions) {
|
|
714
|
+
const renderer = new WebGPURenderer({
|
|
715
|
+
logarithmicDepthBuffer: true,
|
|
716
|
+
antialias: false,
|
|
717
|
+
depth: false,
|
|
718
|
+
trackTimestamp: true,
|
|
719
|
+
...rendererOptions
|
|
485
720
|
});
|
|
486
|
-
|
|
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;
|
|
487
731
|
}
|
|
488
732
|
createScene() {
|
|
489
|
-
const
|
|
490
|
-
|
|
733
|
+
const scene = new Scene();
|
|
734
|
+
scene.background = new Color(0);
|
|
735
|
+
return scene;
|
|
491
736
|
}
|
|
492
737
|
createPerspectiveCamera() {
|
|
493
|
-
const
|
|
494
|
-
|
|
738
|
+
const camera = new PerspectiveCamera(50, this.el.clientWidth / this.el.clientHeight, 0.1, 1e5);
|
|
739
|
+
camera.position.setScalar(5);
|
|
740
|
+
return camera;
|
|
495
741
|
}
|
|
496
742
|
createOrthographicCamera() {
|
|
497
|
-
const
|
|
743
|
+
const camera = new OrthographicCamera(
|
|
498
744
|
-this.el.clientWidth / 2,
|
|
499
745
|
this.el.clientWidth / 2,
|
|
500
746
|
this.el.clientHeight / 2,
|
|
@@ -502,415 +748,621 @@ class ys extends ke {
|
|
|
502
748
|
0.1,
|
|
503
749
|
1e5
|
|
504
750
|
);
|
|
505
|
-
|
|
751
|
+
camera.position.setScalar(5);
|
|
752
|
+
return camera;
|
|
506
753
|
}
|
|
507
754
|
dispose() {
|
|
508
|
-
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();
|
|
509
762
|
}
|
|
510
763
|
}
|
|
511
|
-
class
|
|
512
|
-
constructor(
|
|
513
|
-
super(
|
|
514
|
-
}
|
|
515
|
-
parse(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
+
}
|
|
521
779
|
}
|
|
522
780
|
}
|
|
523
|
-
function
|
|
524
|
-
|
|
781
|
+
function getAsciiString(buf) {
|
|
782
|
+
const decoder = new TextDecoder();
|
|
783
|
+
return decoder.decode(new Uint8Array(buf));
|
|
525
784
|
}
|
|
526
|
-
function
|
|
527
|
-
const
|
|
528
|
-
for (let
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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;
|
|
533
793
|
}
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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;
|
|
550
829
|
}
|
|
551
830
|
}
|
|
552
|
-
function
|
|
553
|
-
const
|
|
554
|
-
|
|
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;
|
|
555
837
|
}
|
|
556
|
-
function
|
|
557
|
-
const
|
|
558
|
-
|
|
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;
|
|
559
846
|
}
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
let
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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,
|
|
571
864
|
alphaTest: 0.01,
|
|
572
|
-
side
|
|
865
|
+
side
|
|
573
866
|
});
|
|
574
|
-
|
|
575
|
-
|
|
867
|
+
return material;
|
|
868
|
+
};
|
|
869
|
+
const isPNG = (textureUrl) => {
|
|
870
|
+
return textureUrl?.toLowerCase().endsWith(".png") ?? false;
|
|
871
|
+
};
|
|
872
|
+
class SBMParser {
|
|
576
873
|
data;
|
|
577
874
|
options;
|
|
578
875
|
textureLoader;
|
|
579
|
-
littleEndian =
|
|
876
|
+
littleEndian = true;
|
|
580
877
|
materials = /* @__PURE__ */ new Map();
|
|
581
|
-
constructor(
|
|
582
|
-
this.data =
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
let
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
+
}
|
|
619
953
|
}
|
|
620
954
|
}
|
|
621
|
-
for (let
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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);
|
|
631
976
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
y.setX(e.getFloat32(t, this.littleEndian)), t += 4, y.setY(e.getFloat32(t, this.littleEndian)), t += 4, u.push(y);
|
|
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;
|
|
641
985
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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);
|
|
997
|
+
}
|
|
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
|
+
}
|
|
1014
|
+
}
|
|
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];
|
|
653
1027
|
}
|
|
654
|
-
|
|
655
|
-
for (let m = 0; m < f.length; m++) {
|
|
656
|
-
const y = f[m], B = l[y[0]], _ = l[y[1]], O = l[y[2]];
|
|
657
|
-
b.push(...B.toArray(), ..._.toArray(), ...O.toArray());
|
|
658
|
-
let V = new P(), ee = new P(), te = new P();
|
|
659
|
-
const k = p[m];
|
|
660
|
-
k !== void 0 && (V = k[0], ee = k[1], te = k[2]), M.push(...V.toArray(), ...ee.toArray(), ...te.toArray());
|
|
1028
|
+
uvs.push(...uv2.toArray(), ...uv22.toArray(), ...uv3.toArray());
|
|
661
1029
|
}
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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));
|
|
1034
|
+
}
|
|
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);
|
|
666
1045
|
}
|
|
667
1046
|
}
|
|
668
|
-
|
|
1047
|
+
onLoad(sbm);
|
|
669
1048
|
}
|
|
670
|
-
_parseV3(
|
|
671
|
-
const
|
|
672
|
-
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);
|
|
673
1053
|
}
|
|
674
1054
|
}
|
|
675
|
-
class
|
|
676
|
-
load(
|
|
677
|
-
let
|
|
678
|
-
if (this.resourcePath !== "")
|
|
679
|
-
|
|
680
|
-
else if (this.path !== "") {
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
} else
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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) => {
|
|
692
1084
|
try {
|
|
693
1085
|
this.parse(
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
(
|
|
697
|
-
|
|
1086
|
+
data,
|
|
1087
|
+
resourcePath,
|
|
1088
|
+
(sbm) => {
|
|
1089
|
+
onLoad(sbm);
|
|
1090
|
+
this.manager.itemEnd(url);
|
|
698
1091
|
},
|
|
699
|
-
|
|
1092
|
+
_onError
|
|
700
1093
|
);
|
|
701
|
-
} catch (
|
|
702
|
-
|
|
1094
|
+
} catch (e) {
|
|
1095
|
+
_onError(e);
|
|
703
1096
|
}
|
|
704
1097
|
},
|
|
705
|
-
|
|
706
|
-
|
|
1098
|
+
onProgress,
|
|
1099
|
+
_onError
|
|
707
1100
|
);
|
|
708
1101
|
}
|
|
709
|
-
parse(
|
|
710
|
-
const
|
|
711
|
-
if (
|
|
712
|
-
|
|
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}`));
|
|
713
1106
|
return;
|
|
714
1107
|
}
|
|
715
|
-
new
|
|
716
|
-
path
|
|
1108
|
+
const parser = new SBMParser(data, {
|
|
1109
|
+
path,
|
|
717
1110
|
crossOrigin: this.crossOrigin,
|
|
718
1111
|
requestHeader: this.requestHeader,
|
|
719
1112
|
manager: this.manager
|
|
720
|
-
})
|
|
1113
|
+
});
|
|
1114
|
+
parser.parse(onLoad, onError);
|
|
721
1115
|
}
|
|
722
|
-
parseAsync(
|
|
723
|
-
return new Promise((
|
|
1116
|
+
parseAsync(data, path) {
|
|
1117
|
+
return new Promise((resolve, reject) => {
|
|
724
1118
|
this.parse(
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
(
|
|
728
|
-
|
|
1119
|
+
data,
|
|
1120
|
+
path,
|
|
1121
|
+
(sbm) => {
|
|
1122
|
+
resolve(sbm);
|
|
729
1123
|
},
|
|
730
|
-
(
|
|
731
|
-
|
|
1124
|
+
(err) => {
|
|
1125
|
+
reject(err);
|
|
732
1126
|
}
|
|
733
1127
|
);
|
|
734
1128
|
});
|
|
735
1129
|
}
|
|
736
1130
|
}
|
|
737
|
-
class
|
|
1131
|
+
class ModelLoaderManager {
|
|
738
1132
|
static CACHE_NAME = "u-space-model-loader-cache";
|
|
739
|
-
static fileLoader = new
|
|
740
|
-
static dracoLoader = new
|
|
741
|
-
`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/`
|
|
742
1136
|
);
|
|
743
|
-
static ktx2Loader = new
|
|
744
|
-
`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/`
|
|
745
1139
|
);
|
|
746
|
-
static gltfLoader = new
|
|
747
|
-
static sbmxLoader = new
|
|
748
|
-
static sbmLoader = new
|
|
749
|
-
static fbxLoader = new
|
|
750
|
-
static objLoader = new
|
|
751
|
-
static stlLoader = new
|
|
752
|
-
static setLoadingManager(
|
|
753
|
-
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;
|
|
754
1156
|
}
|
|
755
1157
|
/**
|
|
756
1158
|
* Set the renderer to enable KTX2 support and other renderer-dependent features.
|
|
757
1159
|
*/
|
|
758
|
-
static async setRenderer(
|
|
759
|
-
await
|
|
1160
|
+
static async setRenderer(renderer) {
|
|
1161
|
+
await renderer.init();
|
|
1162
|
+
this.ktx2Loader.detectSupport(renderer);
|
|
760
1163
|
}
|
|
761
1164
|
/**
|
|
762
1165
|
* Configure Draco decoder path.
|
|
763
1166
|
*/
|
|
764
|
-
static setDracoPath(
|
|
765
|
-
this.dracoLoader.setDecoderPath(
|
|
1167
|
+
static setDracoPath(path) {
|
|
1168
|
+
this.dracoLoader.setDecoderPath(path);
|
|
766
1169
|
}
|
|
767
1170
|
/**
|
|
768
1171
|
* Configure KTX2 transcoder path.
|
|
769
1172
|
*/
|
|
770
|
-
static setKTX2Path(
|
|
771
|
-
this.ktx2Loader.setTranscoderPath(
|
|
772
|
-
}
|
|
773
|
-
static async loadAsync(
|
|
774
|
-
const
|
|
775
|
-
let
|
|
776
|
-
|
|
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) {
|
|
777
1185
|
case "gltf":
|
|
778
1186
|
case "glb":
|
|
779
|
-
|
|
1187
|
+
const gltf = await this.gltfLoader.parseAsync(buffer, LoaderUtils.extractUrlBase(url));
|
|
1188
|
+
return gltf.scene;
|
|
780
1189
|
case "sbmx":
|
|
781
|
-
|
|
1190
|
+
const sbmx = await this.sbmxLoader.parseAsync(buffer, LoaderUtils.extractUrlBase(url));
|
|
1191
|
+
return sbmx.scene;
|
|
782
1192
|
case "sbm":
|
|
783
|
-
|
|
1193
|
+
const sbm = await this.sbmLoader.parseAsync(buffer, LoaderUtils.extractUrlBase(url));
|
|
1194
|
+
return sbm;
|
|
784
1195
|
case "fbx":
|
|
785
|
-
|
|
1196
|
+
const fbx = this.fbxLoader.parse(buffer, LoaderUtils.extractUrlBase(url));
|
|
1197
|
+
return fbx;
|
|
786
1198
|
case "obj":
|
|
787
|
-
const
|
|
788
|
-
|
|
1199
|
+
const text = new TextDecoder().decode(buffer);
|
|
1200
|
+
const obj = this.objLoader.parse(text);
|
|
1201
|
+
return obj;
|
|
789
1202
|
case "stl":
|
|
790
|
-
const
|
|
791
|
-
|
|
1203
|
+
const geometry = this.stlLoader.parse(buffer);
|
|
1204
|
+
const mesh = new Mesh(geometry, new MeshStandardNodeMaterial());
|
|
1205
|
+
return mesh;
|
|
792
1206
|
default:
|
|
793
|
-
throw new Error(`Unsupported model format: ${
|
|
1207
|
+
throw new Error(`Unsupported model format: ${extension}`);
|
|
794
1208
|
}
|
|
795
1209
|
}
|
|
796
|
-
static async getPersistentData(
|
|
1210
|
+
static async getPersistentData(url) {
|
|
797
1211
|
try {
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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);
|
|
805
1224
|
}
|
|
806
1225
|
}
|
|
807
1226
|
}
|
|
808
|
-
class
|
|
809
|
-
static textureLoader = new
|
|
810
|
-
static hdrLoader = new
|
|
811
|
-
static cubeTextureLoader = new
|
|
812
|
-
static setLoadingManager(
|
|
813
|
-
this.textureLoader.manager =
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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;
|
|
824
1249
|
}
|
|
825
1250
|
}
|
|
826
|
-
class
|
|
827
|
-
isBaseMesh =
|
|
1251
|
+
class BaseMesh extends Mesh {
|
|
1252
|
+
isBaseMesh = true;
|
|
828
1253
|
type = "BaseMesh";
|
|
829
|
-
ignoreInvisibleWhenRaycast =
|
|
830
|
-
constructor(...
|
|
831
|
-
super(...
|
|
1254
|
+
ignoreInvisibleWhenRaycast = true;
|
|
1255
|
+
constructor(...args) {
|
|
1256
|
+
super(...args);
|
|
832
1257
|
}
|
|
833
|
-
raycast(
|
|
834
|
-
|
|
1258
|
+
raycast(raycaster, intersects) {
|
|
1259
|
+
if (this.visible === false && this.ignoreInvisibleWhenRaycast === true) {
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
return super.raycast(raycaster, intersects);
|
|
835
1263
|
}
|
|
836
1264
|
}
|
|
837
|
-
class
|
|
838
|
-
isBaseGroup =
|
|
1265
|
+
class BaseGroup extends Group {
|
|
1266
|
+
isBaseGroup = true;
|
|
839
1267
|
type = "BaseGroup";
|
|
840
|
-
ignoreInvisibleWhenRaycast =
|
|
841
|
-
constructor(...
|
|
842
|
-
super(...
|
|
1268
|
+
ignoreInvisibleWhenRaycast = true;
|
|
1269
|
+
constructor(...args) {
|
|
1270
|
+
super(...args);
|
|
843
1271
|
}
|
|
844
|
-
raycast(
|
|
845
|
-
|
|
1272
|
+
raycast(raycaster, intersects) {
|
|
1273
|
+
if (this.visible === false && this.ignoreInvisibleWhenRaycast === true) {
|
|
1274
|
+
return false;
|
|
1275
|
+
}
|
|
1276
|
+
return super.raycast(raycaster, intersects);
|
|
846
1277
|
}
|
|
847
1278
|
}
|
|
848
|
-
class
|
|
849
|
-
isBaseSprite =
|
|
1279
|
+
class BaseSprite extends Sprite {
|
|
1280
|
+
isBaseSprite = true;
|
|
850
1281
|
type = "BaseSprite";
|
|
851
|
-
ignoreInvisibleWhenRaycast =
|
|
852
|
-
constructor(...
|
|
853
|
-
super(...
|
|
1282
|
+
ignoreInvisibleWhenRaycast = true;
|
|
1283
|
+
constructor(...args) {
|
|
1284
|
+
super(...args);
|
|
854
1285
|
}
|
|
855
|
-
raycast(
|
|
856
|
-
|
|
1286
|
+
raycast(raycaster, intersects) {
|
|
1287
|
+
if (this.visible === false && this.ignoreInvisibleWhenRaycast === true) {
|
|
1288
|
+
return false;
|
|
1289
|
+
}
|
|
1290
|
+
return super.raycast(raycaster, intersects);
|
|
857
1291
|
}
|
|
858
1292
|
}
|
|
859
|
-
class
|
|
860
|
-
isModel =
|
|
1293
|
+
class Model extends BaseGroup {
|
|
1294
|
+
isModel = true;
|
|
861
1295
|
type = "Model";
|
|
862
1296
|
static memoryCache = /* @__PURE__ */ new Map();
|
|
863
1297
|
constructor() {
|
|
864
1298
|
super();
|
|
865
1299
|
}
|
|
866
|
-
async loadAsync(
|
|
867
|
-
const { url
|
|
1300
|
+
async loadAsync(parameters) {
|
|
1301
|
+
const { url, cache = true, persistent = true } = parameters;
|
|
868
1302
|
try {
|
|
869
|
-
let
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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;
|
|
875
1321
|
}
|
|
876
1322
|
}
|
|
877
1323
|
static clearMemoryCache() {
|
|
878
1324
|
this.memoryCache.clear();
|
|
879
1325
|
}
|
|
880
1326
|
static async clearPersistentCache() {
|
|
881
|
-
typeof caches
|
|
1327
|
+
if (typeof caches !== "undefined") {
|
|
1328
|
+
await caches.delete("u-space-loader-cache");
|
|
1329
|
+
}
|
|
882
1330
|
}
|
|
883
1331
|
}
|
|
884
|
-
class
|
|
885
|
-
isSphereMesh =
|
|
1332
|
+
class SphereMesh extends BaseMesh {
|
|
1333
|
+
isSphereMesh = true;
|
|
886
1334
|
type = "SphereMesh";
|
|
887
|
-
constructor({ geometryParameters
|
|
888
|
-
super()
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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);
|
|
897
1347
|
}
|
|
898
1348
|
}
|
|
899
|
-
class
|
|
900
|
-
isTubeMesh =
|
|
1349
|
+
class TubeMesh extends BaseMesh {
|
|
1350
|
+
isTubeMesh = true;
|
|
901
1351
|
type = "TubeMesh";
|
|
902
|
-
constructor({ geometryParameters
|
|
903
|
-
super()
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
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);
|
|
910
1362
|
}
|
|
911
1363
|
}
|
|
912
|
-
class
|
|
913
|
-
isTopology =
|
|
1364
|
+
class Topology extends BaseGroup {
|
|
1365
|
+
isTopology = true;
|
|
914
1366
|
type = "Topology";
|
|
915
1367
|
// Visuals
|
|
916
1368
|
parameters = {
|
|
@@ -925,27 +1377,37 @@ class bs extends Ee {
|
|
|
925
1377
|
adjacencyMap = /* @__PURE__ */ new Map();
|
|
926
1378
|
graphGroup = null;
|
|
927
1379
|
pathMeshes = [];
|
|
928
|
-
constructor(
|
|
929
|
-
super()
|
|
1380
|
+
constructor(parameters) {
|
|
1381
|
+
super();
|
|
1382
|
+
if (parameters) {
|
|
1383
|
+
Object.assign(this.parameters, parameters);
|
|
1384
|
+
}
|
|
930
1385
|
}
|
|
931
1386
|
/**
|
|
932
1387
|
* Add a node to the topology graph.
|
|
933
1388
|
* @param id Unique identifier for the node
|
|
934
1389
|
* @param position 3D position of the node
|
|
935
1390
|
*/
|
|
936
|
-
addNode(
|
|
937
|
-
|
|
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
|
+
}
|
|
938
1399
|
}
|
|
939
1400
|
/**
|
|
940
1401
|
* Remove a node from the topology graph.
|
|
941
1402
|
* @param id Unique identifier for the node
|
|
942
1403
|
*/
|
|
943
|
-
removeNode(
|
|
944
|
-
if (!this.nodes.has(
|
|
945
|
-
console.warn(`[Topology] Node ${
|
|
1404
|
+
removeNode(id) {
|
|
1405
|
+
if (!this.nodes.has(id)) {
|
|
1406
|
+
console.warn(`[Topology] Node ${id} does not exist.`);
|
|
946
1407
|
return;
|
|
947
1408
|
}
|
|
948
|
-
this.nodes.delete(
|
|
1409
|
+
this.nodes.delete(id);
|
|
1410
|
+
this.adjacencyMap.delete(id);
|
|
949
1411
|
}
|
|
950
1412
|
/**
|
|
951
1413
|
* Add an edge between two nodes.
|
|
@@ -954,14 +1416,18 @@ class bs extends Ee {
|
|
|
954
1416
|
* @param weight Cost of the edge. Defaults to Euclidean distance.
|
|
955
1417
|
* @param bidirectional If true, adds the reverse edge as well. Default true.
|
|
956
1418
|
*/
|
|
957
|
-
addEdge(
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
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}`);
|
|
961
1424
|
return;
|
|
962
1425
|
}
|
|
963
|
-
const
|
|
964
|
-
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
|
+
}
|
|
965
1431
|
}
|
|
966
1432
|
/**
|
|
967
1433
|
* Remove an edge between two nodes.
|
|
@@ -969,51 +1435,80 @@ class bs extends Ee {
|
|
|
969
1435
|
* @param to Node ID
|
|
970
1436
|
* @param bidirectional If true, removes the reverse edge as well. Default true.
|
|
971
1437
|
*/
|
|
972
|
-
removeEdge(
|
|
973
|
-
|
|
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
|
+
}
|
|
974
1445
|
}
|
|
975
1446
|
/**
|
|
976
1447
|
* Find the shortest path between start and end nodes using Dijkstra's algorithm.
|
|
977
1448
|
*/
|
|
978
|
-
getShortestPath(
|
|
979
|
-
if (!this.nodes.has(
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
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;
|
|
989
1475
|
}
|
|
990
|
-
if (
|
|
1476
|
+
if (minDist === Infinity) {
|
|
991
1477
|
break;
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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);
|
|
999
1490
|
}
|
|
1491
|
+
}
|
|
1000
1492
|
}
|
|
1001
|
-
const
|
|
1002
|
-
let
|
|
1003
|
-
if (
|
|
1493
|
+
const path = [];
|
|
1494
|
+
let curr = endId;
|
|
1495
|
+
if (previous.get(endId) === null && startId !== endId) {
|
|
1004
1496
|
return [];
|
|
1005
|
-
for (; c !== null; ) {
|
|
1006
|
-
const r = this.nodes.get(c);
|
|
1007
|
-
r && a.unshift(r), c = n.get(c) || null;
|
|
1008
1497
|
}
|
|
1009
|
-
|
|
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;
|
|
1010
1504
|
}
|
|
1011
1505
|
/**
|
|
1012
1506
|
* Renders the entire topology graph structure using TubeGeometry and Spheres.
|
|
1013
1507
|
*/
|
|
1014
1508
|
renderGraph() {
|
|
1015
|
-
this.clearGraph()
|
|
1016
|
-
|
|
1509
|
+
this.clearGraph();
|
|
1510
|
+
this.graphGroup = new Group();
|
|
1511
|
+
const _dummySphere = new SphereMesh({
|
|
1017
1512
|
geometryParameters: {
|
|
1018
1513
|
radius: this.parameters.nodeRadius,
|
|
1019
1514
|
widthSegments: 16,
|
|
@@ -1023,72 +1518,128 @@ class bs extends Ee {
|
|
|
1023
1518
|
color: this.parameters.nodeColor
|
|
1024
1519
|
}
|
|
1025
1520
|
});
|
|
1026
|
-
this.nodes.forEach((
|
|
1027
|
-
const
|
|
1028
|
-
|
|
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);
|
|
1029
1526
|
});
|
|
1030
|
-
const
|
|
1031
|
-
this.adjacencyMap.forEach((
|
|
1032
|
-
|
|
1033
|
-
const
|
|
1034
|
-
if (
|
|
1035
|
-
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
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({
|
|
1039
1538
|
geometryParameters: {
|
|
1040
|
-
path
|
|
1539
|
+
path,
|
|
1041
1540
|
tubularSegments: 1,
|
|
1042
1541
|
radius: this.parameters.edgeRadius,
|
|
1043
1542
|
radialSegments: 8,
|
|
1044
|
-
closed:
|
|
1543
|
+
closed: false
|
|
1045
1544
|
},
|
|
1046
1545
|
materialParameters: {
|
|
1047
1546
|
color: this.parameters.edgeColor
|
|
1048
1547
|
}
|
|
1049
1548
|
});
|
|
1050
|
-
Object.assign(
|
|
1549
|
+
Object.assign(tubeMesh.userData, { from, to, weight, type: "edge" });
|
|
1550
|
+
this.graphGroup.add(tubeMesh);
|
|
1051
1551
|
}
|
|
1052
1552
|
});
|
|
1053
|
-
})
|
|
1553
|
+
});
|
|
1554
|
+
this.add(this.graphGroup);
|
|
1054
1555
|
}
|
|
1055
1556
|
clearGraph() {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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
|
+
}
|
|
1059
1567
|
}
|
|
1060
1568
|
/**
|
|
1061
1569
|
* Visualizes a path using TubeGeometry (CatmullRomCurve3).
|
|
1062
1570
|
*/
|
|
1063
|
-
renderPath(
|
|
1064
|
-
if (
|
|
1065
|
-
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({
|
|
1066
1575
|
geometryParameters: {
|
|
1067
|
-
path:
|
|
1068
|
-
tubularSegments:
|
|
1576
|
+
path: curve,
|
|
1577
|
+
tubularSegments: points.length * 10,
|
|
1069
1578
|
radius: this.parameters.pathRadius,
|
|
1070
|
-
closed:
|
|
1579
|
+
closed: false
|
|
1071
1580
|
},
|
|
1072
1581
|
materialParameters: {
|
|
1073
|
-
color:
|
|
1582
|
+
color: color2
|
|
1074
1583
|
}
|
|
1075
1584
|
});
|
|
1076
|
-
|
|
1585
|
+
this.add(mesh);
|
|
1586
|
+
this.pathMeshes.push(mesh);
|
|
1587
|
+
return mesh;
|
|
1077
1588
|
}
|
|
1078
1589
|
clearPaths() {
|
|
1079
|
-
this.pathMeshes.forEach((
|
|
1080
|
-
this.remove(
|
|
1081
|
-
|
|
1590
|
+
this.pathMeshes.forEach((m) => {
|
|
1591
|
+
this.remove(m);
|
|
1592
|
+
m.geometry.dispose();
|
|
1593
|
+
m.material.dispose();
|
|
1594
|
+
});
|
|
1595
|
+
this.pathMeshes = [];
|
|
1596
|
+
}
|
|
1597
|
+
/** Export nodes and edges as a JSON-serializable object. */
|
|
1598
|
+
exportData() {
|
|
1599
|
+
const nodes = {};
|
|
1600
|
+
this.nodes.forEach((pos, id) => {
|
|
1601
|
+
nodes[id] = { x: pos.x, y: pos.y, z: pos.z };
|
|
1602
|
+
});
|
|
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
|
+
}
|
|
1612
|
+
});
|
|
1613
|
+
});
|
|
1614
|
+
return { nodes, edges };
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Load topology from a previously exported data object.
|
|
1618
|
+
* Replaces all current nodes, edges, and path meshes, then re-renders the graph.
|
|
1619
|
+
*/
|
|
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();
|
|
1082
1632
|
}
|
|
1083
1633
|
dispose() {
|
|
1084
|
-
this.clearGraph()
|
|
1634
|
+
this.clearGraph();
|
|
1635
|
+
this.clearPaths();
|
|
1085
1636
|
}
|
|
1086
|
-
getNeighbors(
|
|
1087
|
-
return this.adjacencyMap.get(
|
|
1637
|
+
getNeighbors(id) {
|
|
1638
|
+
return this.adjacencyMap.get(id);
|
|
1088
1639
|
}
|
|
1089
1640
|
}
|
|
1090
|
-
class
|
|
1091
|
-
isPoi =
|
|
1641
|
+
class Poi extends BaseSprite {
|
|
1642
|
+
isPoi = true;
|
|
1092
1643
|
type = "Poi";
|
|
1093
1644
|
parameters = {
|
|
1094
1645
|
img: "",
|
|
@@ -1098,277 +1649,426 @@ class Ts extends Xt {
|
|
|
1098
1649
|
color: "#ffffff",
|
|
1099
1650
|
iconSize: 64,
|
|
1100
1651
|
padding: 10,
|
|
1101
|
-
backgroundColor: "rgba(0, 0, 0, 0
|
|
1652
|
+
backgroundColor: "rgba(0, 0, 0, 0)",
|
|
1102
1653
|
borderRadius: 8,
|
|
1103
|
-
textPosition: "right"
|
|
1654
|
+
textPosition: "right",
|
|
1655
|
+
scaleFactor: 0.01
|
|
1104
1656
|
};
|
|
1105
|
-
|
|
1106
|
-
|
|
1657
|
+
_canvas = document.createElement("canvas");
|
|
1658
|
+
_lastParametersJSON = "";
|
|
1659
|
+
constructor(parameters = {}) {
|
|
1660
|
+
super(new SpriteNodeMaterial());
|
|
1661
|
+
Object.assign(this.parameters, parameters);
|
|
1662
|
+
this.updateAsync();
|
|
1107
1663
|
}
|
|
1108
1664
|
/**
|
|
1109
1665
|
* Update the POI with new parameters
|
|
1110
1666
|
* @param parameters Partial POI parameters
|
|
1111
1667
|
*/
|
|
1112
|
-
async updateAsync(
|
|
1113
|
-
Object.assign(this.parameters,
|
|
1114
|
-
const
|
|
1115
|
-
if (
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
if (
|
|
1123
|
-
|
|
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) {
|
|
1124
1693
|
case "top":
|
|
1125
|
-
|
|
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;
|
|
1126
1700
|
break;
|
|
1127
1701
|
case "bottom":
|
|
1128
|
-
|
|
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;
|
|
1129
1708
|
break;
|
|
1130
1709
|
case "left":
|
|
1131
|
-
|
|
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;
|
|
1132
1716
|
break;
|
|
1717
|
+
case "right":
|
|
1133
1718
|
default:
|
|
1134
|
-
|
|
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;
|
|
1135
1725
|
break;
|
|
1136
1726
|
}
|
|
1137
|
-
else
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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
|
+
}
|
|
1152
1777
|
});
|
|
1153
1778
|
}
|
|
1154
|
-
_drawRoundedRect(
|
|
1155
|
-
|
|
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();
|
|
1156
1791
|
}
|
|
1157
1792
|
dispose() {
|
|
1158
|
-
this.material.map?.dispose()
|
|
1793
|
+
this.material.map?.dispose();
|
|
1794
|
+
this.material.dispose();
|
|
1159
1795
|
}
|
|
1160
1796
|
}
|
|
1161
|
-
class
|
|
1162
|
-
isShapeMesh =
|
|
1797
|
+
class ShapeMesh extends BaseMesh {
|
|
1798
|
+
isShapeMesh = true;
|
|
1163
1799
|
type = "ShapeMesh";
|
|
1164
|
-
constructor({ geometryParameters
|
|
1165
|
-
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);
|
|
1166
1805
|
}
|
|
1167
|
-
static createFromPoints(
|
|
1168
|
-
return new
|
|
1169
|
-
...
|
|
1806
|
+
static createFromPoints(points, parameters = {}) {
|
|
1807
|
+
return new ShapeMesh({
|
|
1808
|
+
...parameters,
|
|
1170
1809
|
geometryParameters: {
|
|
1171
|
-
...
|
|
1172
|
-
shape: new
|
|
1810
|
+
...parameters.geometryParameters,
|
|
1811
|
+
shape: new Shape(points.map((point) => new Vector2(point.x, -point.z)))
|
|
1173
1812
|
}
|
|
1174
1813
|
});
|
|
1175
1814
|
}
|
|
1176
1815
|
}
|
|
1177
|
-
class
|
|
1178
|
-
isExtrudeMesh =
|
|
1816
|
+
class ExtrudeMesh extends BaseMesh {
|
|
1817
|
+
isExtrudeMesh = true;
|
|
1179
1818
|
type = "ExtrudeMesh";
|
|
1180
|
-
constructor({ geometryParameters
|
|
1181
|
-
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);
|
|
1182
1824
|
}
|
|
1183
|
-
static createFromPoints(
|
|
1184
|
-
return new
|
|
1185
|
-
...
|
|
1825
|
+
static createFromPoints(points, parameters = {}) {
|
|
1826
|
+
return new ExtrudeMesh({
|
|
1827
|
+
...parameters,
|
|
1186
1828
|
geometryParameters: {
|
|
1187
|
-
...
|
|
1188
|
-
shape: new
|
|
1829
|
+
...parameters.geometryParameters,
|
|
1830
|
+
shape: new Shape(points.map((point) => new Vector2(point.x, -point.z)))
|
|
1189
1831
|
}
|
|
1190
1832
|
});
|
|
1191
1833
|
}
|
|
1192
1834
|
}
|
|
1193
|
-
class
|
|
1194
|
-
isPlaneMesh =
|
|
1835
|
+
class PlaneMesh extends BaseMesh {
|
|
1836
|
+
isPlaneMesh = true;
|
|
1195
1837
|
type = "PlaneMesh";
|
|
1196
|
-
constructor({ geometryParameters
|
|
1197
|
-
super()
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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);
|
|
1203
1847
|
}
|
|
1204
1848
|
}
|
|
1205
|
-
class
|
|
1206
|
-
isCircleMesh =
|
|
1849
|
+
class CircleMesh extends BaseMesh {
|
|
1850
|
+
isCircleMesh = true;
|
|
1207
1851
|
type = "CircleMesh";
|
|
1208
|
-
constructor({ geometryParameters
|
|
1852
|
+
constructor({ geometryParameters, materialParameters } = {}) {
|
|
1209
1853
|
super(
|
|
1210
|
-
new
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1854
|
+
new CircleGeometry(
|
|
1855
|
+
geometryParameters?.radius,
|
|
1856
|
+
geometryParameters?.segments,
|
|
1857
|
+
geometryParameters?.thetaStart,
|
|
1858
|
+
geometryParameters?.thetaLength
|
|
1215
1859
|
),
|
|
1216
|
-
new
|
|
1217
|
-
)
|
|
1860
|
+
new MeshStandardNodeMaterial(materialParameters)
|
|
1861
|
+
);
|
|
1862
|
+
this.geometry.rotateX(-Math.PI / 2);
|
|
1218
1863
|
}
|
|
1219
1864
|
}
|
|
1220
|
-
class
|
|
1221
|
-
static traverseMeshes(
|
|
1222
|
-
|
|
1223
|
-
|
|
1865
|
+
class ObjectUtils {
|
|
1866
|
+
static traverseMeshes(object, callback) {
|
|
1867
|
+
object.traverse((child) => {
|
|
1868
|
+
if (child instanceof Mesh) {
|
|
1869
|
+
callback(child);
|
|
1870
|
+
}
|
|
1224
1871
|
});
|
|
1225
1872
|
}
|
|
1226
1873
|
}
|
|
1227
|
-
class
|
|
1228
|
-
static flow(
|
|
1229
|
-
const { baseColor
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
const
|
|
1234
|
-
return
|
|
1235
|
-
}
|
|
1236
|
-
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 = {}) {
|
|
1237
1892
|
const {
|
|
1238
|
-
baseColor
|
|
1239
|
-
flowColor
|
|
1240
|
-
speed
|
|
1241
|
-
scale
|
|
1242
|
-
intensity
|
|
1243
|
-
distortion
|
|
1244
|
-
} =
|
|
1245
|
-
|
|
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);
|
|
1246
1909
|
}
|
|
1247
1910
|
}
|
|
1248
|
-
function
|
|
1911
|
+
function reinterpretType(_value) {
|
|
1249
1912
|
}
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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;
|
|
1256
1927
|
})();
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
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(
|
|
1260
1937
|
{
|
|
1261
|
-
enabled:
|
|
1938
|
+
enabled: true,
|
|
1262
1939
|
opacity: 0.5,
|
|
1263
1940
|
color: 16711680,
|
|
1264
|
-
overwrite:
|
|
1941
|
+
overwrite: false
|
|
1265
1942
|
},
|
|
1266
|
-
|
|
1943
|
+
options
|
|
1267
1944
|
);
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
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
|
+
}
|
|
1272
1958
|
});
|
|
1273
1959
|
}
|
|
1274
1960
|
}
|
|
1275
|
-
const
|
|
1276
|
-
const
|
|
1277
|
-
|
|
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);
|
|
1278
1966
|
};
|
|
1279
|
-
class
|
|
1967
|
+
class Tween extends Tween$1 {
|
|
1280
1968
|
viewer;
|
|
1281
|
-
constructor(
|
|
1282
|
-
super(
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
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
|
+
}
|
|
1286
1978
|
};
|
|
1287
|
-
easingByMode(
|
|
1288
|
-
const
|
|
1289
|
-
return super.easing(
|
|
1979
|
+
easingByMode(mode) {
|
|
1980
|
+
const tweenModeFunc = animationModeEnum[mode];
|
|
1981
|
+
return super.easing(tweenModeFunc);
|
|
1290
1982
|
}
|
|
1291
|
-
start(
|
|
1292
|
-
|
|
1983
|
+
start(time2, overrideStartingValues) {
|
|
1984
|
+
this.viewer.addEventListener("afterControlsUpdate", this._update);
|
|
1985
|
+
return super.start(time2, overrideStartingValues);
|
|
1293
1986
|
}
|
|
1294
1987
|
stop() {
|
|
1295
|
-
|
|
1988
|
+
this.viewer.removeEventListener("afterControlsUpdate", this._update);
|
|
1989
|
+
return super.stop();
|
|
1296
1990
|
}
|
|
1297
1991
|
}
|
|
1298
|
-
function
|
|
1299
|
-
return new Promise((
|
|
1300
|
-
const { duration
|
|
1301
|
-
|
|
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);
|
|
1302
1997
|
}).onComplete(() => {
|
|
1303
|
-
|
|
1998
|
+
resolve();
|
|
1304
1999
|
}).onStop(() => {
|
|
1305
|
-
|
|
2000
|
+
reject("animation stop");
|
|
1306
2001
|
}).onStart(() => {
|
|
1307
|
-
|
|
2002
|
+
onStart?.(tween);
|
|
1308
2003
|
});
|
|
1309
|
-
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();
|
|
1310
2009
|
});
|
|
1311
2010
|
}
|
|
1312
|
-
const
|
|
1313
|
-
"Linear.None":
|
|
1314
|
-
"Quadratic.In":
|
|
1315
|
-
"Quadratic.Out":
|
|
1316
|
-
"Quadratic.InOut":
|
|
1317
|
-
"Cubic.In":
|
|
1318
|
-
"Cubic.Out":
|
|
1319
|
-
"Cubic.InOut":
|
|
1320
|
-
"Quartic.In":
|
|
1321
|
-
"Quartic.Out":
|
|
1322
|
-
"Quartic.InOut":
|
|
1323
|
-
"Quintic.In":
|
|
1324
|
-
"Quintic.Out":
|
|
1325
|
-
"Quintic.InOut":
|
|
1326
|
-
"Sinusoidal.In":
|
|
1327
|
-
"Sinusoidal.Out":
|
|
1328
|
-
"Sinusoidal.InOut":
|
|
1329
|
-
"Exponential.In":
|
|
1330
|
-
"Exponential.Out":
|
|
1331
|
-
"Exponential.InOut":
|
|
1332
|
-
"Circular.In":
|
|
1333
|
-
"Circular.Out":
|
|
1334
|
-
"Circular.InOut":
|
|
1335
|
-
"Elastic.In":
|
|
1336
|
-
"Elastic.Out":
|
|
1337
|
-
"Elastic.InOut":
|
|
1338
|
-
"Back.In":
|
|
1339
|
-
"Back.Out":
|
|
1340
|
-
"Back.InOut":
|
|
1341
|
-
"Bounce.In":
|
|
1342
|
-
"Bounce.Out":
|
|
1343
|
-
"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
|
|
1344
2043
|
};
|
|
1345
2044
|
export {
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
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
|
|
1374
2073
|
};
|
|
2074
|
+
//# sourceMappingURL=index.js.map
|