@needle-tools/engine 4.2.5 → 4.3.0-alpha.1
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/CHANGELOG.md +9 -0
- package/components.needle.json +1 -1
- package/dist/needle-engine.bundle.js +4245 -2977
- package/dist/needle-engine.bundle.light.js +4236 -2968
- package/dist/needle-engine.bundle.light.min.js +73 -73
- package/dist/needle-engine.bundle.light.umd.cjs +61 -61
- package/dist/needle-engine.bundle.min.js +62 -62
- package/dist/needle-engine.bundle.umd.cjs +61 -61
- package/dist/needle-engine.light.d.ts +9 -9
- package/dist/three-examples.js +825 -794
- package/dist/three-examples.light.js +825 -794
- package/dist/three-examples.light.min.js +12 -12
- package/dist/three-examples.light.umd.cjs +10 -10
- package/dist/three-examples.min.js +12 -12
- package/dist/three-examples.umd.cjs +10 -10
- package/lib/engine/engine_addressables.d.ts +3 -0
- package/lib/engine/engine_addressables.js +18 -0
- package/lib/engine/engine_addressables.js.map +1 -1
- package/lib/engine/engine_input.d.ts +20 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_types.d.ts +162 -17
- package/lib/engine-components/Animator.d.ts +129 -21
- package/lib/engine-components/Animator.js +115 -21
- package/lib/engine-components/Animator.js.map +1 -1
- package/lib/engine-components/AnimatorController.d.ts +161 -32
- package/lib/engine-components/AnimatorController.js +176 -29
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/AudioListener.d.ts +16 -5
- package/lib/engine-components/AudioListener.js +16 -5
- package/lib/engine-components/AudioListener.js.map +1 -1
- package/lib/engine-components/AudioSource.d.ts +120 -28
- package/lib/engine-components/AudioSource.js +120 -37
- package/lib/engine-components/AudioSource.js.map +1 -1
- package/lib/engine-components/AvatarLoader.d.ts +61 -0
- package/lib/engine-components/AvatarLoader.js +61 -1
- package/lib/engine-components/AvatarLoader.js.map +1 -1
- package/lib/engine-components/AxesHelper.d.ts +19 -1
- package/lib/engine-components/AxesHelper.js +19 -1
- package/lib/engine-components/AxesHelper.js.map +1 -1
- package/lib/engine-components/BoxHelperComponent.d.ts +26 -0
- package/lib/engine-components/BoxHelperComponent.js +26 -0
- package/lib/engine-components/BoxHelperComponent.js.map +1 -1
- package/lib/engine-components/Camera.d.ts +126 -37
- package/lib/engine-components/Camera.js +139 -37
- package/lib/engine-components/Camera.js.map +1 -1
- package/lib/engine-components/CameraUtils.js +20 -0
- package/lib/engine-components/CameraUtils.js.map +1 -1
- package/lib/engine-components/Collider.d.ts +95 -21
- package/lib/engine-components/Collider.js +100 -23
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Component.d.ts +554 -106
- package/lib/engine-components/Component.js +352 -81
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.d.ts +95 -21
- package/lib/engine-components/DragControls.js +126 -32
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/DropListener.d.ts +99 -16
- package/lib/engine-components/DropListener.js +119 -14
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/Light.d.ts +102 -5
- package/lib/engine-components/Light.js +102 -44
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/NeedleMenu.d.ts +28 -11
- package/lib/engine-components/NeedleMenu.js +28 -11
- package/lib/engine-components/NeedleMenu.js.map +1 -1
- package/lib/engine-components/Networking.d.ts +37 -5
- package/lib/engine-components/Networking.js +37 -5
- package/lib/engine-components/Networking.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.d.ts +8 -0
- package/lib/engine-components/SceneSwitcher.js +72 -8
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/SpatialTrigger.d.ts +66 -1
- package/lib/engine-components/SpatialTrigger.js +74 -2
- package/lib/engine-components/SpatialTrigger.js.map +1 -1
- package/lib/engine-components/SpectatorCamera.d.ts +66 -4
- package/lib/engine-components/SpectatorCamera.js +132 -6
- package/lib/engine-components/SpectatorCamera.js.map +1 -1
- package/lib/engine-components/SyncedTransform.d.ts +45 -6
- package/lib/engine-components/SyncedTransform.js +45 -6
- package/lib/engine-components/SyncedTransform.js.map +1 -1
- package/lib/engine-components/TransformGizmo.d.ts +49 -3
- package/lib/engine-components/TransformGizmo.js +49 -3
- package/lib/engine-components/TransformGizmo.js.map +1 -1
- package/lib/engine-components/ui/EventSystem.d.ts +1 -0
- package/lib/engine-components/ui/EventSystem.js +8 -5
- package/lib/engine-components/ui/EventSystem.js.map +1 -1
- package/lib/engine-components/webxr/WebXR.d.ts +131 -22
- package/lib/engine-components/webxr/WebXR.js +132 -23
- package/lib/engine-components/webxr/WebXR.js.map +1 -1
- package/lib/engine-components-experimental/networking/PlayerSync.d.ts +82 -9
- package/lib/engine-components-experimental/networking/PlayerSync.js +76 -11
- package/lib/engine-components-experimental/networking/PlayerSync.js.map +1 -1
- package/package.json +1 -1
- package/plugins/vite/alias.js +6 -3
- package/src/engine/engine_addressables.ts +21 -0
- package/src/engine/engine_input.ts +20 -1
- package/src/engine/engine_types.ts +179 -18
- package/src/engine-components/Animator.ts +142 -22
- package/src/engine-components/AnimatorController.ts +184 -34
- package/src/engine-components/AudioListener.ts +16 -5
- package/src/engine-components/AudioSource.ts +126 -37
- package/src/engine-components/AvatarLoader.ts +61 -2
- package/src/engine-components/AxesHelper.ts +21 -1
- package/src/engine-components/BoxHelperComponent.ts +26 -0
- package/src/engine-components/Camera.ts +147 -41
- package/src/engine-components/CameraUtils.ts +20 -0
- package/src/engine-components/Collider.ts +102 -27
- package/src/engine-components/Component.ts +605 -129
- package/src/engine-components/DragControls.ts +134 -38
- package/src/engine-components/DropListener.ts +143 -23
- package/src/engine-components/Light.ts +105 -44
- package/src/engine-components/NeedleMenu.ts +29 -11
- package/src/engine-components/Networking.ts +37 -6
- package/src/engine-components/SceneSwitcher.ts +78 -9
- package/src/engine-components/SpatialTrigger.ts +80 -3
- package/src/engine-components/SpectatorCamera.ts +136 -18
- package/src/engine-components/SyncedTransform.ts +50 -7
- package/src/engine-components/TransformGizmo.ts +49 -4
- package/src/engine-components/ui/EventSystem.ts +9 -7
- package/src/engine-components/webxr/WebXR.ts +144 -27
- package/src/engine-components-experimental/networking/PlayerSync.ts +85 -13
|
@@ -19,63 +19,104 @@ import { Animation } from "./Animation.js";
|
|
|
19
19
|
import { Behaviour } from "./Component.js";
|
|
20
20
|
import { EventList } from "./EventList.js";
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Debug mode can be enabled with the URL parameter `?debugdroplistener`, which
|
|
24
|
+
* logs additional information during drag and drop events and visualizes hit points.
|
|
25
|
+
*/
|
|
22
26
|
const debug = getParam("debugdroplistener");
|
|
23
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Events dispatched by the DropListener component
|
|
30
|
+
* @enum {string}
|
|
31
|
+
*/
|
|
24
32
|
export enum DropListenerEvents {
|
|
25
33
|
/**
|
|
26
|
-
* Dispatched when a file is dropped into the scene. The detail of the event is the
|
|
34
|
+
* Dispatched when a file is dropped into the scene. The detail of the event is the {@link File} that was dropped.
|
|
35
|
+
* The event is called once for each dropped file.
|
|
27
36
|
*/
|
|
28
37
|
FileDropped = "file-dropped",
|
|
29
38
|
/**
|
|
30
|
-
* Dispatched when a new object is added to the scene. The detail of the event
|
|
39
|
+
* Dispatched when a new object is added to the scene. The detail of the event contains {@link DropListenerOnDropArguments} for the content that was added.
|
|
31
40
|
*/
|
|
32
41
|
ObjectAdded = "object-added",
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Context information for a drop operation
|
|
46
|
+
*/
|
|
35
47
|
declare type DropContext = {
|
|
48
|
+
/** Position where the file was dropped in screen coordinates */
|
|
36
49
|
screenposition: Vector2;
|
|
50
|
+
/** URL of the dropped content, if applicable */
|
|
37
51
|
url?: string,
|
|
52
|
+
/** File object of the dropped content, if applicable */
|
|
38
53
|
file?: File;
|
|
54
|
+
/** 3D position where the content should be placed */
|
|
39
55
|
point?: Vec3;
|
|
56
|
+
/** Size dimensions for the content */
|
|
40
57
|
size?: Vec3;
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
|
|
44
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Network event arguments passed between clients when using the DropListener with networking
|
|
63
|
+
*/
|
|
45
64
|
export declare type DropListenerNetworkEventArguments = {
|
|
65
|
+
/** Unique identifier of the sender */
|
|
46
66
|
guid: string,
|
|
67
|
+
/** Name of the dropped object */
|
|
47
68
|
name: string,
|
|
69
|
+
/** URL or array of URLs to the dropped content */
|
|
48
70
|
url: string | string[],
|
|
49
71
|
/** Worldspace point where the object was placed in the scene */
|
|
50
72
|
point: Vec3;
|
|
51
73
|
/** Bounding box size */
|
|
52
74
|
size: Vec3;
|
|
75
|
+
/** MD5 hash of the content for verification */
|
|
53
76
|
contentMD5: string;
|
|
54
77
|
}
|
|
55
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Arguments provided to handlers when an object is dropped or added to the scene
|
|
81
|
+
*/
|
|
56
82
|
export declare type DropListenerOnDropArguments = {
|
|
83
|
+
/** The DropListener component that processed the drop event */
|
|
57
84
|
sender: DropListener,
|
|
58
|
-
/**
|
|
85
|
+
/** The root object added to the scene */
|
|
59
86
|
object: Object3D,
|
|
60
|
-
/** The
|
|
87
|
+
/** The complete model with all associated data */
|
|
61
88
|
model: Model,
|
|
89
|
+
/** MD5 hash of the content for verification */
|
|
62
90
|
contentMD5: string;
|
|
91
|
+
/** The original dropped URL or File object */
|
|
63
92
|
dropped: URL | File | undefined;
|
|
64
93
|
}
|
|
65
94
|
|
|
66
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* CustomEvent dispatched when an object is added to the scene via the DropListener
|
|
97
|
+
*/
|
|
67
98
|
class DropListenerAddedEvent<T extends DropListenerOnDropArguments> extends CustomEvent<T> {
|
|
99
|
+
/**
|
|
100
|
+
* Creates a new added event with the provided details
|
|
101
|
+
* @param detail Information about the added object
|
|
102
|
+
*/
|
|
68
103
|
constructor(detail: T) {
|
|
69
104
|
super(DropListenerEvents.ObjectAdded, { detail });
|
|
70
105
|
}
|
|
71
106
|
}
|
|
72
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Key name used for blob storage parameters
|
|
110
|
+
*/
|
|
73
111
|
const blobKeyName = "blob";
|
|
74
112
|
|
|
75
113
|
/** The DropListener component is used to listen for drag and drop events in the browser and add the dropped files to the scene
|
|
76
114
|
* It can be used to allow users to drag and drop glTF files into the scene to add new objects.
|
|
77
115
|
*
|
|
78
|
-
*
|
|
116
|
+
* If {@link useNetworking} is enabled, the DropListener will automatically synchronize dropped files to other connected clients.
|
|
117
|
+
* Enable {@link fitIntoVolume} to automatically scale dropped objects to fit within the volume defined by {@link fitVolumeSize}.
|
|
118
|
+
*
|
|
119
|
+
* The following events are dispatched by the DropListener:
|
|
79
120
|
* - **object-added** - dispatched when a new object is added to the scene
|
|
80
121
|
* - **file-dropped** - dispatched when a file is dropped into the scene
|
|
81
122
|
*
|
|
@@ -103,42 +144,46 @@ const blobKeyName = "blob";
|
|
|
103
144
|
export class DropListener extends Behaviour {
|
|
104
145
|
|
|
105
146
|
/**
|
|
106
|
-
* When enabled the DropListener will automatically
|
|
147
|
+
* When enabled, the DropListener will automatically synchronize dropped files to other connected clients.
|
|
148
|
+
* When a file is dropped locally, it will be uploaded to blob storage and the URL will be shared with other clients.
|
|
107
149
|
*/
|
|
108
150
|
@serializable()
|
|
109
151
|
useNetworking: boolean = true;
|
|
110
152
|
|
|
111
153
|
/**
|
|
112
|
-
* When assigned the
|
|
154
|
+
* When assigned, the DropListener will only accept files that are dropped on this specific object.
|
|
155
|
+
* This allows creating designated drop zones in your scene.
|
|
113
156
|
*/
|
|
114
157
|
@serializable(Object3D)
|
|
115
158
|
dropArea?: Object3D;
|
|
116
159
|
|
|
117
160
|
/**
|
|
118
|
-
* When enabled
|
|
161
|
+
* When enabled, dropped objects will be automatically scaled to fit within the volume defined by fitVolumeSize.
|
|
162
|
+
* Useful for ensuring dropped models appear at an appropriate scale.
|
|
119
163
|
* @default false
|
|
120
164
|
*/
|
|
121
165
|
@serializable()
|
|
122
166
|
fitIntoVolume: boolean = false;
|
|
123
167
|
|
|
124
168
|
/**
|
|
125
|
-
*
|
|
169
|
+
* Defines the dimensions of the volume that dropped objects will be scaled to fit within.
|
|
170
|
+
* Only used when fitIntoVolume is enabled.
|
|
126
171
|
*/
|
|
127
172
|
@serializable(Vector3)
|
|
128
173
|
fitVolumeSize = new Vector3(1, 1, 1);
|
|
129
174
|
|
|
130
|
-
/**
|
|
175
|
+
/**
|
|
176
|
+
* When enabled, dropped objects will be positioned at the point where the cursor hit the scene.
|
|
177
|
+
* When disabled, objects will be placed at the origin of the DropListener.
|
|
131
178
|
* @default true
|
|
132
179
|
*/
|
|
133
180
|
@serializable()
|
|
134
181
|
placeAtHitPosition: boolean = true;
|
|
135
182
|
|
|
136
|
-
|
|
137
183
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
184
|
+
* Event list that gets invoked after a file has been successfully added to the scene.
|
|
185
|
+
* Receives {@link DropListenerOnDropArguments} containing the added object and related information.
|
|
140
186
|
* @event object-added
|
|
141
|
-
* @param {DropListenerOnDropArguments} evt
|
|
142
187
|
* @example
|
|
143
188
|
* ```typescript
|
|
144
189
|
* dropListener.onDropped.addEventListener((evt) => {
|
|
@@ -178,6 +223,10 @@ export class DropListener extends Behaviour {
|
|
|
178
223
|
this.removePreviouslyAddedObjects(false);
|
|
179
224
|
}
|
|
180
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Handles network events received from other clients containing information about dropped objects
|
|
228
|
+
* @param evt Network event data containing object information, position, and content URL
|
|
229
|
+
*/
|
|
181
230
|
private onNetworkEvent = (evt: DropListenerNetworkEventArguments) => {
|
|
182
231
|
if (!this.useNetworking) {
|
|
183
232
|
if (debug) console.debug("[DropListener] Ignoring networked event because networking is disabled", evt);
|
|
@@ -199,6 +248,11 @@ export class DropListener extends Behaviour {
|
|
|
199
248
|
}
|
|
200
249
|
}
|
|
201
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Handles clipboard paste events and processes them as potential URL drops
|
|
253
|
+
* Only URLs are processed by this handler, and only when editing is allowed
|
|
254
|
+
* @param evt The paste event
|
|
255
|
+
*/
|
|
202
256
|
private handlePaste = (evt: Event) => {
|
|
203
257
|
if (this.context.connection.allowEditing === false) return;
|
|
204
258
|
if (evt.defaultPrevented) return;
|
|
@@ -217,12 +271,22 @@ export class DropListener extends Behaviour {
|
|
|
217
271
|
.catch(console.warn);
|
|
218
272
|
}
|
|
219
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Handles drag events over the renderer's canvas
|
|
276
|
+
* Prevents default behavior to enable drop events
|
|
277
|
+
* @param evt The drag event
|
|
278
|
+
*/
|
|
220
279
|
private onDrag = (evt: DragEvent) => {
|
|
221
280
|
if (this.context.connection.allowEditing === false) return;
|
|
222
281
|
// necessary to get drop event
|
|
223
282
|
evt.preventDefault();
|
|
224
283
|
}
|
|
225
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Processes drop events to add files to the scene
|
|
287
|
+
* Handles both file drops and text/URL drops
|
|
288
|
+
* @param evt The drop event
|
|
289
|
+
*/
|
|
226
290
|
private onDrop = async (evt: DragEvent) => {
|
|
227
291
|
if (this.context.connection.allowEditing === false) return;
|
|
228
292
|
|
|
@@ -266,6 +330,14 @@ export class DropListener extends Behaviour {
|
|
|
266
330
|
}
|
|
267
331
|
}
|
|
268
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Processes a dropped or pasted URL and tries to load it as a 3D model
|
|
335
|
+
* Handles special cases like GitHub URLs and Polyhaven asset URLs
|
|
336
|
+
* @param url The URL to process
|
|
337
|
+
* @param ctx Context information about where the drop occurred
|
|
338
|
+
* @param isRemote Whether this URL was shared from a remote client
|
|
339
|
+
* @returns The added object or null if loading failed
|
|
340
|
+
*/
|
|
269
341
|
private async addFromUrl(url: string, ctx: DropContext, isRemote: boolean) {
|
|
270
342
|
if (debug) console.log("dropped url", url);
|
|
271
343
|
|
|
@@ -315,6 +387,12 @@ export class DropListener extends Behaviour {
|
|
|
315
387
|
|
|
316
388
|
private _abort: AbortController | null = null;
|
|
317
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Processes dropped files, loads them as 3D models, and handles networking if enabled
|
|
392
|
+
* Creates an abort controller to cancel previous uploads if new files are dropped
|
|
393
|
+
* @param fileList Array of dropped files
|
|
394
|
+
* @param ctx Context information about where the drop occurred
|
|
395
|
+
*/
|
|
318
396
|
private async addDroppedFiles(fileList: Array<File>, ctx: DropContext) {
|
|
319
397
|
if (debug) console.log("Add files", fileList)
|
|
320
398
|
if (!Array.isArray(fileList)) return;
|
|
@@ -361,7 +439,10 @@ export class DropListener extends Behaviour {
|
|
|
361
439
|
private readonly _addedObjects = new Array<Object3D>();
|
|
362
440
|
private readonly _addedModels = new Array<Model>();
|
|
363
441
|
|
|
364
|
-
/**
|
|
442
|
+
/**
|
|
443
|
+
* Removes all previously added objects from the scene
|
|
444
|
+
* @param doDestroy When true, destroys the objects; when false, just clears the references
|
|
445
|
+
*/
|
|
365
446
|
private removePreviouslyAddedObjects(doDestroy: boolean = true) {
|
|
366
447
|
if (doDestroy) {
|
|
367
448
|
for (const prev of this._addedObjects) {
|
|
@@ -375,7 +456,13 @@ export class DropListener extends Behaviour {
|
|
|
375
456
|
}
|
|
376
457
|
|
|
377
458
|
/**
|
|
378
|
-
* Adds
|
|
459
|
+
* Adds a loaded model to the scene with proper positioning and scaling.
|
|
460
|
+
* Handles placement based on component settings and raycasting.
|
|
461
|
+
* If {@link fitIntoVolume} is enabled, the object will be scaled to fit within the volume defined by {@link fitVolumeSize}.
|
|
462
|
+
* @param data The loaded model data and content hash
|
|
463
|
+
* @param ctx Context information about where the drop occurred
|
|
464
|
+
* @param isRemote Whether this object was shared from a remote client
|
|
465
|
+
* @returns The added object or null if adding failed
|
|
379
466
|
*/
|
|
380
467
|
private addObject(data: { model: Model, contentMD5: string }, ctx: DropContext, isRemote: boolean): Object3D | null {
|
|
381
468
|
|
|
@@ -444,6 +531,13 @@ export class DropListener extends Behaviour {
|
|
|
444
531
|
return obj;
|
|
445
532
|
}
|
|
446
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Sends a network event to other clients about a dropped object
|
|
536
|
+
* Only triggered when networking is enabled and the connection is established
|
|
537
|
+
* @param url The URL to the content that was dropped
|
|
538
|
+
* @param obj The object that was added to the scene
|
|
539
|
+
* @param contentmd5 The content hash for verification
|
|
540
|
+
*/
|
|
447
541
|
private async sendDropEvent(url: string, obj: Object3D, contentmd5: string) {
|
|
448
542
|
if (!this.useNetworking) {
|
|
449
543
|
if (debug) console.debug("[DropListener] Ignoring networked event because networking is disabled", url);
|
|
@@ -463,12 +557,20 @@ export class DropListener extends Behaviour {
|
|
|
463
557
|
this.context.connection.send("droplistener", evt);
|
|
464
558
|
}
|
|
465
559
|
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Deletes remote state for this DropListener's objects
|
|
563
|
+
* Called when new files are dropped to clean up previous state
|
|
564
|
+
*/
|
|
466
565
|
private deleteDropEvent() {
|
|
467
566
|
this.context.connection.sendDeleteRemoteState(this.guid);
|
|
468
567
|
}
|
|
469
568
|
|
|
470
|
-
|
|
471
|
-
|
|
569
|
+
/**
|
|
570
|
+
* Tests if a drop event occurred within the designated drop area if one is specified
|
|
571
|
+
* @param ctx The drop context containing screen position information
|
|
572
|
+
* @returns True if the drop is valid (either no drop area is set or the drop occurred inside it)
|
|
573
|
+
*/
|
|
472
574
|
private testIfIsInDropArea(ctx: DropContext): boolean {
|
|
473
575
|
if (this.dropArea) {
|
|
474
576
|
const screenPoint = this.context.input.convertScreenspaceToRaycastSpace(ctx.screenposition.clone());
|
|
@@ -492,7 +594,11 @@ export class DropListener extends Behaviour {
|
|
|
492
594
|
|
|
493
595
|
}
|
|
494
596
|
|
|
495
|
-
|
|
597
|
+
/**
|
|
598
|
+
* Attempts to convert a Polyhaven website URL to a direct glTF model download URL
|
|
599
|
+
* @param urlStr The original Polyhaven URL
|
|
600
|
+
* @returns The direct download URL for the glTF model if it's a valid Polyhaven asset URL, otherwise returns the original URL
|
|
601
|
+
*/
|
|
496
602
|
function tryResolvePolyhavenAssetUrl(urlStr: string) {
|
|
497
603
|
if (!urlStr.startsWith("https://polyhaven.com/")) return urlStr;
|
|
498
604
|
// Handle dropping polyhaven image url
|
|
@@ -506,10 +612,18 @@ function tryResolvePolyhavenAssetUrl(urlStr: string) {
|
|
|
506
612
|
return assetUrl;
|
|
507
613
|
}
|
|
508
614
|
|
|
509
|
-
|
|
510
|
-
|
|
615
|
+
/**
|
|
616
|
+
* Helper namespace for loading files and models from various sources
|
|
617
|
+
*/
|
|
511
618
|
namespace FileHelper {
|
|
512
619
|
|
|
620
|
+
/**
|
|
621
|
+
* Loads and processes a File object into a 3D model
|
|
622
|
+
* @param file The file to load (supported formats: gltf, glb, fbx, obj, usdz, vrm)
|
|
623
|
+
* @param context The application context
|
|
624
|
+
* @param args Additional arguments including a unique guid for instantiation
|
|
625
|
+
* @returns Promise containing the loaded model and its content hash, or null if loading failed
|
|
626
|
+
*/
|
|
513
627
|
export async function loadFile(file: File, context: Context, args: { guid: string }): Promise<{ model: Model, contentMD5: string } | null> {
|
|
514
628
|
const name = file.name.toLowerCase();
|
|
515
629
|
if (name.endsWith(".gltf") ||
|
|
@@ -544,6 +658,12 @@ namespace FileHelper {
|
|
|
544
658
|
return null;
|
|
545
659
|
}
|
|
546
660
|
|
|
661
|
+
/**
|
|
662
|
+
* Loads a 3D model from a URL with progress visualization
|
|
663
|
+
* @param url The URL to load the model from
|
|
664
|
+
* @param args Arguments including context, parent object, and optional placement information
|
|
665
|
+
* @returns Promise containing the loaded model and its content hash, or null if loading failed
|
|
666
|
+
*/
|
|
547
667
|
export async function loadFileFromURL(url: URL, args: { guid: string, context: Context, parent: Object3D, point?: Vec3, size?: Vec3 }): Promise<{ model: Model, contentMD5: string } | null> {
|
|
548
668
|
return new Promise(async (resolve, _reject) => {
|
|
549
669
|
|
|
@@ -24,82 +24,86 @@ const shadowMaxDistance = 300;
|
|
|
24
24
|
const debug = getParam("debuglights");
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Defines the type of light in a scene.
|
|
29
|
+
*/
|
|
30
30
|
export enum LightType {
|
|
31
|
-
|
|
32
|
-
/// <para>The light is a spot light.</para>
|
|
33
|
-
/// </summary>
|
|
31
|
+
/** Spot light that emits light in a cone shape */
|
|
34
32
|
Spot = 0,
|
|
35
|
-
|
|
36
|
-
/// <para>The light is a directional light.</para>
|
|
37
|
-
/// </summary>
|
|
33
|
+
/** Directional light that emits parallel light rays in a specific direction */
|
|
38
34
|
Directional = 1,
|
|
39
|
-
|
|
40
|
-
/// <para>The light is a point light.</para>
|
|
41
|
-
/// </summary>
|
|
35
|
+
/** Point light that emits light in all directions from a single point */
|
|
42
36
|
Point = 2,
|
|
37
|
+
/** Area light */
|
|
43
38
|
Area = 3,
|
|
44
|
-
|
|
45
|
-
/// <para>The light is a rectangle shaped area light. It affects only baked lightmaps and lightprobes.</para>
|
|
46
|
-
/// </summary>
|
|
39
|
+
/** Rectangle shaped area light that only affects baked lightmaps and light probes */
|
|
47
40
|
Rectangle = 3,
|
|
48
|
-
|
|
49
|
-
/// <para>The light is a disc shaped area light. It affects only baked lightmaps and lightprobes.</para>
|
|
50
|
-
/// </summary>
|
|
41
|
+
/** Disc shaped area light that only affects baked lightmaps and light probes */
|
|
51
42
|
Disc = 4,
|
|
52
43
|
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Defines how a light contributes to the scene lighting.
|
|
47
|
+
*/
|
|
53
48
|
export enum LightmapBakeType {
|
|
54
|
-
|
|
55
|
-
/// <para>Realtime lights cast run time light and shadows. They can change position, orientation, color, brightness, and many other properties at run time. No lighting gets baked into lightmaps or light probes..</para>
|
|
56
|
-
/// </summary>
|
|
49
|
+
/** Light affects the scene in real-time with no baking */
|
|
57
50
|
Realtime = 4,
|
|
58
|
-
|
|
59
|
-
/// <para>Baked lights cannot move or change in any way during run time. All lighting for static objects gets baked into lightmaps. Lighting and shadows for dynamic objects gets baked into Light Probes.</para>
|
|
60
|
-
/// </summary>
|
|
51
|
+
/** Light is completely baked into lightmaps and light probes */
|
|
61
52
|
Baked = 2,
|
|
62
|
-
|
|
63
|
-
/// <para>Mixed lights allow a mix of realtime and baked lighting, based on the Mixed Lighting Mode used. These lights cannot move, but can change color and intensity at run time. Changes to color and intensity only affect direct lighting as indirect lighting gets baked. If using Subtractive mode, changes to color or intensity are not calculated at run time on static objects.</para>
|
|
64
|
-
/// </summary>
|
|
53
|
+
/** Combines aspects of realtime and baked lighting */
|
|
65
54
|
Mixed = 1,
|
|
66
55
|
}
|
|
67
56
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Defines the shadow casting options for a Light.
|
|
59
|
+
* @enum {number}
|
|
60
|
+
*/
|
|
71
61
|
enum LightShadows {
|
|
72
|
-
|
|
73
|
-
/// <para>Do not cast shadows (default).</para>
|
|
74
|
-
/// </summary>
|
|
62
|
+
/** No shadows are cast */
|
|
75
63
|
None = 0,
|
|
76
|
-
|
|
77
|
-
/// <para>Cast "hard" shadows (with no shadow filtering).</para>
|
|
78
|
-
/// </summary>
|
|
64
|
+
/** Hard-edged shadows without filtering */
|
|
79
65
|
Hard = 1,
|
|
80
|
-
|
|
81
|
-
/// <para>Cast "soft" shadows (with 4x PCF filtering).</para>
|
|
82
|
-
/// </summary>
|
|
66
|
+
/** Soft shadows with PCF filtering */
|
|
83
67
|
Soft = 2,
|
|
84
68
|
}
|
|
85
69
|
|
|
86
|
-
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
70
|
+
/**
|
|
71
|
+
* The Light component creates a light source in the scene.
|
|
72
|
+
* Supports directional, spot, and point light types with various customization options.
|
|
73
|
+
* Lights can cast shadows with configurable settings and can be set to baked or realtime rendering.
|
|
74
|
+
*
|
|
75
|
+
* Debug mode can be enabled with the URL parameter `?debuglights`, which shows
|
|
76
|
+
* additional console output and visual helpers for lights.
|
|
77
|
+
*
|
|
91
78
|
* @category Rendering
|
|
92
79
|
* @group Components
|
|
93
80
|
*/
|
|
94
81
|
export class Light extends Behaviour implements ILight {
|
|
95
82
|
|
|
83
|
+
/**
|
|
84
|
+
* The type of light (spot, directional, point, etc.)
|
|
85
|
+
*/
|
|
96
86
|
@serializable()
|
|
97
87
|
private type: LightType = 0;
|
|
98
88
|
|
|
89
|
+
/**
|
|
90
|
+
* The maximum distance the light affects
|
|
91
|
+
*/
|
|
99
92
|
public range: number = 1;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The full outer angle of the spotlight cone in degrees
|
|
96
|
+
*/
|
|
100
97
|
public spotAngle: number = 1;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The angle of the inner cone in degrees for soft-edge spotlights
|
|
101
|
+
*/
|
|
101
102
|
public innerSpotAngle: number = 1;
|
|
102
103
|
|
|
104
|
+
/**
|
|
105
|
+
* The color of the light
|
|
106
|
+
*/
|
|
103
107
|
@serializable(Color)
|
|
104
108
|
set color(val: Color) {
|
|
105
109
|
this._color = val;
|
|
@@ -113,6 +117,9 @@ export class Light extends Behaviour implements ILight {
|
|
|
113
117
|
}
|
|
114
118
|
public _color: Color = new Color(0xffffff);
|
|
115
119
|
|
|
120
|
+
/**
|
|
121
|
+
* The near plane distance for shadow projection
|
|
122
|
+
*/
|
|
116
123
|
@serializable()
|
|
117
124
|
set shadowNearPlane(val: number) {
|
|
118
125
|
if (val === this._shadowNearPlane) return;
|
|
@@ -125,6 +132,9 @@ export class Light extends Behaviour implements ILight {
|
|
|
125
132
|
get shadowNearPlane(): number { return this._shadowNearPlane; }
|
|
126
133
|
private _shadowNearPlane: number = .1;
|
|
127
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Shadow bias value to reduce shadow acne and peter-panning
|
|
137
|
+
*/
|
|
128
138
|
@serializable()
|
|
129
139
|
set shadowBias(val: number) {
|
|
130
140
|
if (val === this._shadowBias) return;
|
|
@@ -137,6 +147,9 @@ export class Light extends Behaviour implements ILight {
|
|
|
137
147
|
get shadowBias(): number { return this._shadowBias; }
|
|
138
148
|
private _shadowBias: number = 0;
|
|
139
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Shadow normal bias to reduce shadow acne on sloped surfaces
|
|
152
|
+
*/
|
|
140
153
|
@serializable()
|
|
141
154
|
set shadowNormalBias(val: number) {
|
|
142
155
|
if (val === this._shadowNormalBias) return;
|
|
@@ -152,6 +165,9 @@ export class Light extends Behaviour implements ILight {
|
|
|
152
165
|
/** when enabled this will remove the multiplication when setting the shadow bias settings initially */
|
|
153
166
|
private _overrideShadowBiasSettings: boolean = false;
|
|
154
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Shadow casting mode (None, Hard, or Soft)
|
|
170
|
+
*/
|
|
155
171
|
@serializable()
|
|
156
172
|
set shadows(val: LightShadows) {
|
|
157
173
|
this._shadows = val;
|
|
@@ -163,9 +179,16 @@ export class Light extends Behaviour implements ILight {
|
|
|
163
179
|
get shadows(): LightShadows { return this._shadows; }
|
|
164
180
|
private _shadows: LightShadows = 1;
|
|
165
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Determines if the light contributes to realtime lighting, baked lighting, or a mix
|
|
184
|
+
*/
|
|
166
185
|
@serializable()
|
|
167
186
|
private lightmapBakeType: LightmapBakeType = LightmapBakeType.Realtime;
|
|
168
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Brightness of the light. In WebXR experiences, the intensity is automatically
|
|
190
|
+
* adjusted based on the AR session scale to maintain consistent lighting.
|
|
191
|
+
*/
|
|
169
192
|
@serializable()
|
|
170
193
|
set intensity(val: number) {
|
|
171
194
|
this._intensity = val;
|
|
@@ -184,6 +207,9 @@ export class Light extends Behaviour implements ILight {
|
|
|
184
207
|
get intensity(): number { return this._intensity; }
|
|
185
208
|
private _intensity: number = -1;
|
|
186
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Maximum distance the shadow is projected
|
|
212
|
+
*/
|
|
187
213
|
@serializable()
|
|
188
214
|
get shadowDistance(): number {
|
|
189
215
|
const light = this.light;
|
|
@@ -207,6 +233,9 @@ export class Light extends Behaviour implements ILight {
|
|
|
207
233
|
private shadowWidth?: number;
|
|
208
234
|
private shadowHeight?: number;
|
|
209
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Resolution of the shadow map in pixels (width and height)
|
|
238
|
+
*/
|
|
210
239
|
@serializable()
|
|
211
240
|
get shadowResolution(): number {
|
|
212
241
|
const light = this.light;
|
|
@@ -226,10 +255,16 @@ export class Light extends Behaviour implements ILight {
|
|
|
226
255
|
}
|
|
227
256
|
private _shadowResolution?: number = undefined;
|
|
228
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Whether this light's illumination is entirely baked into lightmaps
|
|
260
|
+
*/
|
|
229
261
|
get isBaked() {
|
|
230
262
|
return this.lightmapBakeType === LightmapBakeType.Baked;
|
|
231
263
|
}
|
|
232
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Checks if the GameObject itself is a {@link ThreeLight} object
|
|
267
|
+
*/
|
|
233
268
|
private get selfIsLight(): boolean {
|
|
234
269
|
if (this.gameObject["isLight"] === true) return true;
|
|
235
270
|
switch (this.gameObject.type) {
|
|
@@ -241,8 +276,16 @@ export class Light extends Behaviour implements ILight {
|
|
|
241
276
|
return false;
|
|
242
277
|
}
|
|
243
278
|
|
|
279
|
+
/**
|
|
280
|
+
* The underlying three.js {@link ThreeLight} instance
|
|
281
|
+
*/
|
|
244
282
|
private light: ThreeLight | undefined = undefined;
|
|
245
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Gets the world position of the light
|
|
286
|
+
* @param vec Vector3 to store the result
|
|
287
|
+
* @returns The world position as a Vector3
|
|
288
|
+
*/
|
|
246
289
|
public getWorldPosition(vec: Vector3): Vector3 {
|
|
247
290
|
if (this.light) {
|
|
248
291
|
if (this.type === LightType.Directional) {
|
|
@@ -311,6 +354,10 @@ export class Light extends Behaviour implements ILight {
|
|
|
311
354
|
// this.updateIntensity();
|
|
312
355
|
}
|
|
313
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Creates the appropriate three.js light based on the configured light type
|
|
359
|
+
* and applies all settings like shadows, intensity, and color.
|
|
360
|
+
*/
|
|
314
361
|
createLight() {
|
|
315
362
|
const lightAlreadyCreated = this.selfIsLight;
|
|
316
363
|
|
|
@@ -447,6 +494,10 @@ export class Light extends Behaviour implements ILight {
|
|
|
447
494
|
|
|
448
495
|
}
|
|
449
496
|
|
|
497
|
+
/**
|
|
498
|
+
* Coroutine that updates the main light reference in the context
|
|
499
|
+
* if this directional light should be the main light
|
|
500
|
+
*/
|
|
450
501
|
*updateMainLightRoutine() {
|
|
451
502
|
while (true) {
|
|
452
503
|
if (this.type === LightType.Directional) {
|
|
@@ -459,8 +510,14 @@ export class Light extends Behaviour implements ILight {
|
|
|
459
510
|
}
|
|
460
511
|
}
|
|
461
512
|
|
|
513
|
+
/**
|
|
514
|
+
* Controls whether the renderer's shadow map type can be changed when soft shadows are used
|
|
515
|
+
*/
|
|
462
516
|
static allowChangingRendererShadowMapType: boolean = true;
|
|
463
517
|
|
|
518
|
+
/**
|
|
519
|
+
* Updates shadow settings based on whether the shadows are set to hard or soft
|
|
520
|
+
*/
|
|
464
521
|
private updateShadowSoftHard() {
|
|
465
522
|
if (!this.light) return;
|
|
466
523
|
if (!this.light.shadow) return;
|
|
@@ -486,6 +543,10 @@ export class Light extends Behaviour implements ILight {
|
|
|
486
543
|
}
|
|
487
544
|
}
|
|
488
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Configures a directional light by adding and positioning its target
|
|
548
|
+
* @param dirLight The directional light to set up
|
|
549
|
+
*/
|
|
489
550
|
private setDirectionalLight(dirLight: DirectionalLight) {
|
|
490
551
|
dirLight.add(dirLight.target);
|
|
491
552
|
dirLight.target.position.set(0, 0, -1);
|