@needle-tools/engine 4.8.7 → 4.8.8-next.12b5946
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 +13 -0
- package/README.md +55 -42
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-CZBThBDy.min.js → needle-engine.bundle-CS7vqRb3.min.js} +144 -144
- package/dist/{needle-engine.bundle-lBmpWgFp.umd.cjs → needle-engine.bundle-kYzccQZF.umd.cjs} +147 -147
- package/dist/{needle-engine.bundle-B0qaChJt.js → needle-engine.bundle-lC7eSFno.js} +7090 -7010
- package/dist/needle-engine.d.ts +7 -0
- package/dist/needle-engine.js +2 -2
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/engine_addressables.d.ts +12 -12
- package/lib/engine/engine_addressables.js +32 -23
- package/lib/engine/engine_addressables.js.map +1 -1
- package/lib/engine/engine_animation.d.ts +1 -3
- package/lib/engine/engine_animation.js +18 -12
- package/lib/engine/engine_animation.js.map +1 -1
- package/lib/engine/engine_assetdatabase.js +6 -6
- package/lib/engine/engine_assetdatabase.js.map +1 -1
- package/lib/engine/engine_camera.d.ts +23 -3
- package/lib/engine/engine_camera.js +34 -2
- package/lib/engine/engine_camera.js.map +1 -1
- package/lib/engine/engine_context.d.ts +15 -0
- package/lib/engine/engine_context.js +33 -0
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_create_objects.d.ts +3 -3
- package/lib/engine/engine_create_objects.js +5 -4
- package/lib/engine/engine_create_objects.js.map +1 -1
- package/lib/engine/engine_gameobject.js +2 -2
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +11 -11
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_loaders.callbacks.d.ts +1 -0
- package/lib/engine/engine_loaders.callbacks.js +1 -0
- package/lib/engine/engine_loaders.callbacks.js.map +1 -1
- package/lib/engine/engine_loaders.js +15 -11
- package/lib/engine/engine_loaders.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_lighting_settings.js +5 -2
- package/lib/engine/extensions/NEEDLE_lighting_settings.js.map +1 -1
- package/lib/engine/extensions/NEEDLE_lightmaps.js +1 -1
- package/lib/engine/extensions/NEEDLE_lightmaps.js.map +1 -1
- package/lib/engine/js-extensions/Object3D.d.ts +1 -1
- package/lib/engine/js-extensions/Vector.d.ts +5 -0
- package/lib/engine/js-extensions/Vector.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.attributes.d.ts +1 -0
- package/lib/engine/webcomponents/needle-engine.d.ts +2 -2
- package/lib/engine/webcomponents/needle-engine.js +19 -21
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine-components/Animation.js +2 -1
- package/lib/engine-components/Animation.js.map +1 -1
- package/lib/engine-components/AnimationUtilsAutoplay.js +1 -6
- package/lib/engine-components/AnimationUtilsAutoplay.js.map +1 -1
- package/lib/engine-components/Camera.d.ts +2 -0
- package/lib/engine-components/Camera.js +5 -1
- package/lib/engine-components/Camera.js.map +1 -1
- package/lib/engine-components/ContactShadows.d.ts +12 -2
- package/lib/engine-components/ContactShadows.js +24 -4
- package/lib/engine-components/ContactShadows.js.map +1 -1
- package/lib/engine-components/DropListener.d.ts +21 -15
- package/lib/engine-components/DropListener.js +38 -34
- package/lib/engine-components/DropListener.js.map +1 -1
- package/lib/engine-components/LookAtConstraint.d.ts +5 -1
- package/lib/engine-components/LookAtConstraint.js +8 -0
- package/lib/engine-components/LookAtConstraint.js.map +1 -1
- package/lib/engine-components/NestedGltf.d.ts +9 -4
- package/lib/engine-components/NestedGltf.js +32 -26
- package/lib/engine-components/NestedGltf.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +30 -9
- package/lib/engine-components/OrbitControls.js +56 -19
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/Renderer.js +2 -1
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/Skybox.js +8 -9
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -0
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/export/usdz/Extension.d.ts +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.d.ts +7 -0
- package/lib/engine-components/export/usdz/USDZExporter.js +8 -1
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.d.ts +4 -2
- package/lib/engine-components/webxr/WebXRImageTracking.js +117 -81
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/package.json +3 -3
- package/plugins/common/files.js +6 -3
- package/plugins/vite/alias.js +45 -23
- package/plugins/vite/editor-connection.js +4 -4
- package/src/engine/engine_addressables.ts +46 -33
- package/src/engine/engine_animation.ts +20 -12
- package/src/engine/engine_assetdatabase.ts +7 -7
- package/src/engine/engine_camera.ts +54 -3
- package/src/engine/engine_context.ts +39 -1
- package/src/engine/engine_create_objects.ts +8 -7
- package/src/engine/engine_gameobject.ts +2 -2
- package/src/engine/engine_gltf_builtin_components.ts +12 -11
- package/src/engine/engine_loaders.callbacks.ts +1 -0
- package/src/engine/engine_loaders.ts +18 -13
- package/src/engine/extensions/NEEDLE_lighting_settings.ts +5 -2
- package/src/engine/extensions/NEEDLE_lightmaps.ts +1 -1
- package/src/engine/js-extensions/Vector.ts +6 -0
- package/src/engine/webcomponents/needle-engine.attributes.ts +2 -0
- package/src/engine/webcomponents/needle-engine.ts +21 -21
- package/src/engine-components/Animation.ts +1 -1
- package/src/engine-components/AnimationUtilsAutoplay.ts +1 -6
- package/src/engine-components/Camera.ts +7 -1
- package/src/engine-components/ContactShadows.ts +27 -6
- package/src/engine-components/DropListener.ts +44 -34
- package/src/engine-components/LookAtConstraint.ts +9 -1
- package/src/engine-components/NestedGltf.ts +33 -24
- package/src/engine-components/OrbitControls.ts +81 -32
- package/src/engine-components/Renderer.ts +2 -1
- package/src/engine-components/Skybox.ts +9 -10
- package/src/engine-components/api.ts +2 -1
- package/src/engine-components/export/usdz/Extension.ts +1 -1
- package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +1 -1
- package/src/engine-components/export/usdz/USDZExporter.ts +21 -12
- package/src/engine-components/webxr/WebXRImageTracking.ts +138 -90
|
@@ -10,6 +10,12 @@ export function apply(object: Vector3) {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
// NOTE: keep in sync with method declarations below
|
|
14
|
+
declare module 'three' {
|
|
15
|
+
export interface Vector3 {
|
|
16
|
+
slerp(end: Vector3, t: number): Vector3;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
13
19
|
|
|
14
20
|
Vector3.prototype["slerp"] = function (end: Vector3, t: number) {
|
|
15
21
|
return slerp(this, end, t);
|
|
@@ -60,6 +60,8 @@ type SkyboxAttributes = {
|
|
|
60
60
|
"background-color"?: string,
|
|
61
61
|
/** URL to .exr, .hdr, .png, .jpg to be used for lighting */
|
|
62
62
|
"environment-image"?: string,
|
|
63
|
+
|
|
64
|
+
"environment-intensity"?: number,
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
export type TonemappingAttributeOptions = "none" | "linear" | "neutral" | "agx";
|
|
@@ -45,6 +45,7 @@ const observedAttributes = [
|
|
|
45
45
|
"tone-mapping-exposure",
|
|
46
46
|
"background-blurriness",
|
|
47
47
|
"background-color",
|
|
48
|
+
"environment-intensity",
|
|
48
49
|
]
|
|
49
50
|
|
|
50
51
|
// https://developers.google.com/web/fundamentals/web-components/customelements
|
|
@@ -54,8 +55,8 @@ const observedAttributes = [
|
|
|
54
55
|
* The needle engine web component creates and manages a needle engine context which is responsible for rendering a 3D scene using threejs.
|
|
55
56
|
* The needle engine context is created when the src attribute is set and disposed when the needle engine is removed from the document (you can prevent this by setting the keep-alive attribute to true).
|
|
56
57
|
* The needle engine context is accessible via the context property on the needle engine element (e.g. document.querySelector("needle-engine").context).
|
|
57
|
-
* @link https://engine.needle.tools/docs/reference/needle-engine-attributes
|
|
58
|
-
*
|
|
58
|
+
* See {@link https://engine.needle.tools/docs/reference/needle-engine-attributes}
|
|
59
|
+
*
|
|
59
60
|
* @example
|
|
60
61
|
* <needle-engine src="https://example.com/scene.glb"></needle-engine>
|
|
61
62
|
* @example
|
|
@@ -64,7 +65,7 @@ const observedAttributes = [
|
|
|
64
65
|
export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngineComponent {
|
|
65
66
|
|
|
66
67
|
static get observedAttributes() {
|
|
67
|
-
return observedAttributes
|
|
68
|
+
return observedAttributes;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
public get loadingProgress01(): number { return this._loadingProgress01; }
|
|
@@ -94,7 +95,7 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
94
95
|
/**
|
|
95
96
|
* Get the current context for this web component instance. The context is created when the src attribute is set and the loading has finished.
|
|
96
97
|
* The context is disposed when the needle engine is removed from the document (you can prevent this by setting the keep-alive attribute to true).
|
|
97
|
-
* @returns
|
|
98
|
+
* @returns a promise that resolves to the context when the loading has finished
|
|
98
99
|
*/
|
|
99
100
|
public getContext(): Promise<Context> {
|
|
100
101
|
return new Promise((res, _rej) => {
|
|
@@ -325,22 +326,14 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
325
326
|
if (debug) console.log("ktx2DecoderPath", newValue);
|
|
326
327
|
setKtx2TranscoderPath(newValue);
|
|
327
328
|
break;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
case "
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
case "background-blurriness": {
|
|
337
|
-
const value = parseFloat(newValue);
|
|
338
|
-
if (value != undefined && this._context) {
|
|
339
|
-
this._context.scene.backgroundBlurriness = value;
|
|
340
|
-
}
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
case "background-color": {
|
|
329
|
+
|
|
330
|
+
case "tonemapping":
|
|
331
|
+
case "tone-mapping":
|
|
332
|
+
case "tone-mapping-exposure":
|
|
333
|
+
case "background-blurriness":
|
|
334
|
+
case "background-color":
|
|
335
|
+
case "environment-intensity":
|
|
336
|
+
{
|
|
344
337
|
this.applyAttributes();
|
|
345
338
|
break;
|
|
346
339
|
}
|
|
@@ -552,11 +545,18 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
|
|
|
552
545
|
const backgroundBlurriness = this.getAttribute("background-blurriness");
|
|
553
546
|
if (backgroundBlurriness !== null && backgroundBlurriness !== undefined) {
|
|
554
547
|
const value = parseFloat(backgroundBlurriness);
|
|
555
|
-
if (value
|
|
548
|
+
if (!isNaN(value) && this._context) {
|
|
556
549
|
this._context.scene.backgroundBlurriness = value;
|
|
557
550
|
}
|
|
558
551
|
}
|
|
559
552
|
|
|
553
|
+
const environmentIntensity = this.getAttribute("environment-intensity");
|
|
554
|
+
if (environmentIntensity != undefined && this._context) {
|
|
555
|
+
const value = parseFloat(environmentIntensity);
|
|
556
|
+
if (!isNaN(value) && this._context)
|
|
557
|
+
this._context.scene.environmentIntensity = value;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
560
|
const backgroundColor = this.getAttribute("background-color");
|
|
561
561
|
if (this._context?.renderer) {
|
|
562
562
|
if (typeof backgroundColor === "string" && backgroundColor.length > 0) {
|
|
@@ -67,7 +67,7 @@ export class Animation extends Behaviour implements IAnimationComponent {
|
|
|
67
67
|
get isAnimationComponent(): boolean { return true; }
|
|
68
68
|
addClip(clip: AnimationClip) {
|
|
69
69
|
if (!this.animations) this.animations = [];
|
|
70
|
-
this.animations.push(clip);
|
|
70
|
+
if (!this.animations.includes(clip)) this.animations.push(clip);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
2
2
|
|
|
3
3
|
import { AnimationUtils } from "../engine/engine_animation.js";
|
|
4
|
-
import { addComponent } from "../engine/engine_components.js";
|
|
5
4
|
import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
|
|
6
5
|
import { Animation } from "./Animation.js";
|
|
7
6
|
import { Animator } from "./Animator.js";
|
|
@@ -29,11 +28,7 @@ ContextRegistry.registerCallback(ContextEvent.ContextCreated, args => {
|
|
|
29
28
|
return undefined;
|
|
30
29
|
}, true);
|
|
31
30
|
if (hasAnimation !== true) {
|
|
32
|
-
AnimationUtils.
|
|
33
|
-
createAnimationComponent: (obj, _clip) => {
|
|
34
|
-
return addComponent(obj, Animation);
|
|
35
|
-
},
|
|
36
|
-
});
|
|
31
|
+
AnimationUtils.autoplayAnimations(file.file as GLTF);
|
|
37
32
|
}
|
|
38
33
|
}
|
|
39
34
|
}
|
|
@@ -9,6 +9,7 @@ import { RenderTexture } from "../engine/engine_texture.js";
|
|
|
9
9
|
import { getTempColor, getWorldPosition } from "../engine/engine_three_utils.js";
|
|
10
10
|
import type { ICamera } from "../engine/engine_types.js"
|
|
11
11
|
import { getParam } from "../engine/engine_utils.js";
|
|
12
|
+
import { NeedleXREventArgs } from "../engine/engine_xr.js";
|
|
12
13
|
import { RGBAColor } from "../engine/js-extensions/index.js";
|
|
13
14
|
import { Behaviour, GameObject } from "./Component.js";
|
|
14
15
|
import { OrbitControls } from "./OrbitControls.js";
|
|
@@ -446,6 +447,11 @@ export class Camera extends Behaviour implements ICamera {
|
|
|
446
447
|
this.context.removeCamera(this);
|
|
447
448
|
}
|
|
448
449
|
|
|
450
|
+
onLeaveXR(_args: NeedleXREventArgs): void {
|
|
451
|
+
// Restore previous FOV
|
|
452
|
+
this.fieldOfView = this._fov;
|
|
453
|
+
}
|
|
454
|
+
|
|
449
455
|
|
|
450
456
|
/** @internal */
|
|
451
457
|
onBeforeRender() {
|
|
@@ -553,7 +559,7 @@ export class Camera extends Behaviour implements ICamera {
|
|
|
553
559
|
}
|
|
554
560
|
|
|
555
561
|
// restore previous fov (e.g. when user was in VR or AR and the camera's fov has changed)
|
|
556
|
-
this.fieldOfView = this.
|
|
562
|
+
this.fieldOfView = this.fieldOfView;
|
|
557
563
|
|
|
558
564
|
if (debug) {
|
|
559
565
|
const msg = `[Camera] Apply ClearFlags: ${ClearFlags[this._clearFlags]} - \"${this.name}\"`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BackSide, CustomBlending, DoubleSide, FrontSide, Group, Material, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshStandardMaterial, MinEquation, Object3D, OrthographicCamera, PlaneGeometry, RenderItem, ShaderMaterial, Vector3, WebGLRenderTarget } from "three";
|
|
1
|
+
import { BackSide, CustomBlending, DoubleSide, FrontSide, Group, Material, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshStandardMaterial, MinEquation, Object3D, OrthographicCamera, PlaneGeometry, RenderItem, ShaderMaterial, Vector3, Vector3Like, WebGLRenderTarget } from "three";
|
|
2
2
|
import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader.js';
|
|
3
3
|
import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader.js';
|
|
4
4
|
|
|
@@ -27,7 +27,14 @@ onStart(ctx => {
|
|
|
27
27
|
shadows.darkness = intensity;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
})
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
type FitParameters = {
|
|
34
|
+
object?: Object3D | Object3D[];
|
|
35
|
+
positionOffset?: Partial<Vector3Like>;
|
|
36
|
+
scaleFactor?: Partial<Vector3Like>;
|
|
37
|
+
}
|
|
31
38
|
|
|
32
39
|
// Adapted from https://github.com/mrdoob/three.js/blob/master/examples/webgl_shadow_contact.html.
|
|
33
40
|
|
|
@@ -50,7 +57,7 @@ export class ContactShadows extends Behaviour {
|
|
|
50
57
|
* @param context The context to create the contact shadows in.
|
|
51
58
|
* @returns The instance of the contact shadows.
|
|
52
59
|
*/
|
|
53
|
-
static auto(context?: Context): ContactShadows {
|
|
60
|
+
static auto(context?: Context, params?: FitParameters): ContactShadows {
|
|
54
61
|
if (!context) context = Context.Current;
|
|
55
62
|
if (!context) {
|
|
56
63
|
throw new Error("No context provided and no current context set.");
|
|
@@ -65,12 +72,13 @@ export class ContactShadows extends Behaviour {
|
|
|
65
72
|
this._instances.set(context, instance);
|
|
66
73
|
}
|
|
67
74
|
context.scene.add(instance.gameObject);
|
|
68
|
-
instance.fitShadows();
|
|
75
|
+
instance.fitShadows(params);
|
|
69
76
|
return instance;
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
/**
|
|
73
80
|
* When enabled the contact shadows component will be created to fit the whole scene.
|
|
81
|
+
* @default false
|
|
74
82
|
*/
|
|
75
83
|
@serializable()
|
|
76
84
|
autoFit: boolean = false;
|
|
@@ -107,12 +115,14 @@ export class ContactShadows extends Behaviour {
|
|
|
107
115
|
|
|
108
116
|
/**
|
|
109
117
|
* The minimum size of the shadows box
|
|
118
|
+
* @default undefined
|
|
110
119
|
*/
|
|
111
120
|
minSize?: Partial<Vec3>;
|
|
112
121
|
|
|
113
122
|
/**
|
|
114
123
|
* When enabled the shadows will not be updated automatically. Use `needsUpdate()` to update the shadows manually.
|
|
115
124
|
* This is useful when you want to update the shadows only when the scene changes.
|
|
125
|
+
* @default false
|
|
116
126
|
*/
|
|
117
127
|
manualUpdate: boolean = false;
|
|
118
128
|
/**
|
|
@@ -150,10 +160,11 @@ export class ContactShadows extends Behaviour {
|
|
|
150
160
|
/**
|
|
151
161
|
* Call to fit the shadows to the scene.
|
|
152
162
|
*/
|
|
153
|
-
fitShadows() {
|
|
163
|
+
fitShadows(params: FitParameters = {}) {
|
|
154
164
|
if (debug) console.warn("Fitting shadows to scene");
|
|
155
165
|
setAutoFitEnabled(this.shadowsRoot, false);
|
|
156
|
-
const
|
|
166
|
+
const objectToFit = params.object || this.context.scene;
|
|
167
|
+
const box = getBoundingBox(objectToFit, [this.shadowsRoot]);
|
|
157
168
|
// expand box in all directions (except below ground)
|
|
158
169
|
// 0.75 expands by 75% in each direction
|
|
159
170
|
// The "32" is pretty much heuristically determined – adjusting the value until we don't get a visible border anymore.
|
|
@@ -175,6 +186,16 @@ export class ContactShadows extends Behaviour {
|
|
|
175
186
|
// We can't move GroundProjection down because of immersive-ar mesh/plane tracking where occlusion would otherwise hide GroundProjection
|
|
176
187
|
this.shadowsRoot.position.set((min.x + box.max.x) / 2, min.y - offset, (min.z + box.max.z) / 2);
|
|
177
188
|
this.shadowsRoot.scale.set(box.max.x - min.x, box.max.y - min.y, box.max.z - min.z);
|
|
189
|
+
if (params.positionOffset) {
|
|
190
|
+
if (params.positionOffset.x !== undefined) this.shadowsRoot.position.x += params.positionOffset.x;
|
|
191
|
+
if (params.positionOffset.y !== undefined) this.shadowsRoot.position.y += params.positionOffset.y;
|
|
192
|
+
if (params.positionOffset.z !== undefined) this.shadowsRoot.position.z += params.positionOffset.z;
|
|
193
|
+
}
|
|
194
|
+
if (params.scaleFactor) {
|
|
195
|
+
if (params.scaleFactor.x !== undefined) this.shadowsRoot.scale.x *= params.scaleFactor.x;
|
|
196
|
+
if (params.scaleFactor.y !== undefined) this.shadowsRoot.scale.y *= params.scaleFactor.y;
|
|
197
|
+
if (params.scaleFactor.z !== undefined) this.shadowsRoot.scale.z *= params.scaleFactor.z;
|
|
198
|
+
}
|
|
178
199
|
this.applyMinSize();
|
|
179
200
|
this.shadowsRoot.matrixWorldNeedsUpdate = true;
|
|
180
201
|
if (debug) console.log("Fitted shadows to scene", this.shadowsRoot.scale.clone());
|
|
@@ -143,13 +143,6 @@ const blobKeyName = "blob";
|
|
|
143
143
|
*/
|
|
144
144
|
export class DropListener extends Behaviour {
|
|
145
145
|
|
|
146
|
-
/**
|
|
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.
|
|
149
|
-
*/
|
|
150
|
-
@serializable()
|
|
151
|
-
useNetworking: boolean = true;
|
|
152
|
-
|
|
153
146
|
/**
|
|
154
147
|
* When assigned, the DropListener will only accept files that are dropped on this specific object.
|
|
155
148
|
* This allows creating designated drop zones in your scene.
|
|
@@ -159,7 +152,10 @@ export class DropListener extends Behaviour {
|
|
|
159
152
|
|
|
160
153
|
/**
|
|
161
154
|
* 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.
|
|
155
|
+
* Useful for ensuring dropped models appear at an appropriate scale.
|
|
156
|
+
*
|
|
157
|
+
* **Tip**: Use the handy `fitObjectIntoVolume` function (`import { fitObjectIntoVolume } from "@needle-tools/engine"`) for custom fitting needs.
|
|
158
|
+
*
|
|
163
159
|
* @default false
|
|
164
160
|
*/
|
|
165
161
|
@serializable()
|
|
@@ -180,6 +176,14 @@ export class DropListener extends Behaviour {
|
|
|
180
176
|
@serializable()
|
|
181
177
|
placeAtHitPosition: boolean = true;
|
|
182
178
|
|
|
179
|
+
/**
|
|
180
|
+
* When enabled, the DropListener will automatically synchronize dropped files to other connected clients.
|
|
181
|
+
* When a file is dropped locally, it will be uploaded to blob storage and the URL will be shared with other clients.
|
|
182
|
+
* @default false
|
|
183
|
+
*/
|
|
184
|
+
@serializable()
|
|
185
|
+
useNetworking: boolean = false;
|
|
186
|
+
|
|
183
187
|
/**
|
|
184
188
|
* Event list that gets invoked after a file has been successfully added to the scene.
|
|
185
189
|
* Receives {@link DropListenerOnDropArguments} containing the added object and related information.
|
|
@@ -193,6 +197,27 @@ export class DropListener extends Behaviour {
|
|
|
193
197
|
@serializable(EventList)
|
|
194
198
|
onDropped: EventList<DropListenerOnDropArguments> = new EventList();
|
|
195
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Loads a file from the given URL and adds it to the scene.
|
|
202
|
+
* @returns A promise that resolves to the loaded object or null if loading failed.
|
|
203
|
+
*/
|
|
204
|
+
loadFromURL(url: string, data?: { point?: Vec3, size?: Vec3 }): Promise<Object3D | null> {
|
|
205
|
+
return this.addFromUrl(url, { screenposition: new Vector2(), point: data?.point, size: data?.size, }, false);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Forgets all previously added objects.
|
|
210
|
+
* The droplistener will then not be able to remove previously added objects.
|
|
211
|
+
*/
|
|
212
|
+
forgetObjects() {
|
|
213
|
+
this.removePreviouslyAddedObjects(false);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
// #region internals
|
|
220
|
+
|
|
196
221
|
/** @internal */
|
|
197
222
|
onEnable(): void {
|
|
198
223
|
this.context.renderer.domElement.addEventListener("dragover", this.onDrag);
|
|
@@ -208,21 +233,6 @@ export class DropListener extends Behaviour {
|
|
|
208
233
|
this.context.connection.stopListen("droplistener", this.onNetworkEvent);
|
|
209
234
|
}
|
|
210
235
|
|
|
211
|
-
/**
|
|
212
|
-
* Loads a file from the given URL and adds it to the scene.
|
|
213
|
-
*/
|
|
214
|
-
loadFromURL(url: string, data?: { point?: Vec3, size?: Vec3 }) {
|
|
215
|
-
this.addFromUrl(url, { screenposition: new Vector2(), point: data?.point, size: data?.size, }, true);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Forgets all previously added objects.
|
|
220
|
-
* The droplistener will then not be able to remove previously added objects.
|
|
221
|
-
*/
|
|
222
|
-
forgetObjects() {
|
|
223
|
-
this.removePreviouslyAddedObjects(false);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
236
|
/**
|
|
227
237
|
* Handles network events received from other clients containing information about dropped objects
|
|
228
238
|
* @param evt Network event data containing object information, position, and content URL
|
|
@@ -326,7 +336,7 @@ export class DropListener extends Behaviour {
|
|
|
326
336
|
}
|
|
327
337
|
}
|
|
328
338
|
if (files.length > 0) {
|
|
329
|
-
await this.
|
|
339
|
+
await this.addFromFiles(files, ctx);
|
|
330
340
|
}
|
|
331
341
|
}
|
|
332
342
|
|
|
@@ -359,6 +369,7 @@ export class DropListener extends Behaviour {
|
|
|
359
369
|
// Ignore dropped images
|
|
360
370
|
const lowercaseUrl = url.toLowerCase();
|
|
361
371
|
if (lowercaseUrl.endsWith(".hdr") || lowercaseUrl.endsWith(".hdri") || lowercaseUrl.endsWith(".exr") || lowercaseUrl.endsWith(".png") || lowercaseUrl.endsWith(".jpg") || lowercaseUrl.endsWith(".jpeg")) {
|
|
372
|
+
console.warn(`Fileformat is not supported: ${lowercaseUrl}`);
|
|
362
373
|
return null;
|
|
363
374
|
}
|
|
364
375
|
|
|
@@ -374,7 +385,7 @@ export class DropListener extends Behaviour {
|
|
|
374
385
|
});
|
|
375
386
|
if (res && this._addedObjects.length <= 0) {
|
|
376
387
|
ctx.url = url;
|
|
377
|
-
const obj = this.
|
|
388
|
+
const obj = this.onObjectLoaded(res, ctx, isRemote);
|
|
378
389
|
return obj;
|
|
379
390
|
}
|
|
380
391
|
}
|
|
@@ -388,12 +399,13 @@ export class DropListener extends Behaviour {
|
|
|
388
399
|
private _abort: AbortController | null = null;
|
|
389
400
|
|
|
390
401
|
/**
|
|
391
|
-
* Processes dropped files
|
|
392
|
-
*
|
|
402
|
+
* Processes dropped files and loads them as 3D models.
|
|
403
|
+
* When enabled, it also handles network drops (sending files between clients).
|
|
404
|
+
* Automatically handles cancelling previous uploads if new files are dropped.
|
|
393
405
|
* @param fileList Array of dropped files
|
|
394
|
-
* @param ctx Context information about where the drop occurred
|
|
406
|
+
* @param ctx Context information about where on the screen or in 3D space the drop occurred
|
|
395
407
|
*/
|
|
396
|
-
private async
|
|
408
|
+
private async addFromFiles(fileList: Array<File>, ctx: DropContext) {
|
|
397
409
|
if (debug) console.log("Add files", fileList)
|
|
398
410
|
if (!Array.isArray(fileList)) return;
|
|
399
411
|
if (!fileList.length) return;
|
|
@@ -427,7 +439,7 @@ export class DropListener extends Behaviour {
|
|
|
427
439
|
if (res) {
|
|
428
440
|
this.dispatchEvent(new CustomEvent(DropListenerEvents.FileDropped, { detail: file }));
|
|
429
441
|
ctx.file = file;
|
|
430
|
-
const obj = this.
|
|
442
|
+
const obj = this.onObjectLoaded(res, ctx, false);
|
|
431
443
|
|
|
432
444
|
// handle uploading the dropped object and networking the event
|
|
433
445
|
if (obj && this.context.connection.isConnected && this.useNetworking) {
|
|
@@ -478,7 +490,7 @@ export class DropListener extends Behaviour {
|
|
|
478
490
|
* @param isRemote Whether this object was shared from a remote client
|
|
479
491
|
* @returns The added object or null if adding failed
|
|
480
492
|
*/
|
|
481
|
-
private
|
|
493
|
+
private onObjectLoaded(data: { model: Model, contentMD5: string }, ctx: DropContext, isRemote: boolean): Object3D | null {
|
|
482
494
|
|
|
483
495
|
const { model, contentMD5 } = data;
|
|
484
496
|
|
|
@@ -522,9 +534,7 @@ export class DropListener extends Behaviour {
|
|
|
522
534
|
}
|
|
523
535
|
}
|
|
524
536
|
|
|
525
|
-
AnimationUtils.
|
|
526
|
-
createAnimationComponent: obj => addComponent(obj, Animation)
|
|
527
|
-
});
|
|
537
|
+
AnimationUtils.autoplayAnimations(model);
|
|
528
538
|
|
|
529
539
|
const evt = new DropListenerAddedEvent({
|
|
530
540
|
sender: this,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Object3D } from "three";
|
|
1
|
+
import { Object3D, Vector3 } from "three";
|
|
2
2
|
|
|
3
3
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
4
4
|
import { Behaviour } from "./Component.js";
|
|
@@ -23,4 +23,12 @@ export class LookAtConstraint extends Behaviour {
|
|
|
23
23
|
*/
|
|
24
24
|
@serializable(Object3D)
|
|
25
25
|
sources: Object3D[] = [];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Set the position of the constraint.
|
|
29
|
+
*/
|
|
30
|
+
setConstraintPosition(worldPosition: Vector3) {
|
|
31
|
+
const source = this.sources[0];
|
|
32
|
+
if (source) source.worldPosition = worldPosition;
|
|
33
|
+
}
|
|
26
34
|
}
|
|
@@ -4,6 +4,7 @@ import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.j
|
|
|
4
4
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
5
5
|
import { getParam } from "../engine/engine_utils.js";
|
|
6
6
|
import { Behaviour } from "../engine-components/Component.js";
|
|
7
|
+
import { EventList } from "./EventList.js";
|
|
7
8
|
|
|
8
9
|
const debug = getParam("debugnestedgltf");
|
|
9
10
|
|
|
@@ -11,17 +12,21 @@ const debug = getParam("debugnestedgltf");
|
|
|
11
12
|
* It will load the gltf file and instantiate it as a child of the parent of the GameObject that has this component
|
|
12
13
|
*/
|
|
13
14
|
export class NestedGltf extends Behaviour {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*/
|
|
15
|
+
|
|
16
|
+
/** A reference to the gltf file that should be loaded */
|
|
17
17
|
@serializable(AssetReference)
|
|
18
18
|
filePath?: AssetReference;
|
|
19
19
|
|
|
20
|
+
/** Invoked when the nested glTF file has been instantiated */
|
|
21
|
+
@serializable(EventList)
|
|
22
|
+
loaded: EventList<{ component: NestedGltf, instance: any, asset: AssetReference }> = new EventList();
|
|
23
|
+
|
|
20
24
|
/**
|
|
21
25
|
* EXPERIMENTAL for cloud asset loading
|
|
22
26
|
*/
|
|
23
27
|
loadAssetInParent = true;
|
|
24
28
|
|
|
29
|
+
|
|
25
30
|
private _isLoadingOrDoneLoading: boolean = false;
|
|
26
31
|
|
|
27
32
|
/** Register a callback that will be called when the progress of the loading changes */
|
|
@@ -31,7 +36,7 @@ export class NestedGltf extends Behaviour {
|
|
|
31
36
|
|
|
32
37
|
/** Begin loading the referenced gltf file in filePath */
|
|
33
38
|
preload() {
|
|
34
|
-
this.filePath?.preload();
|
|
39
|
+
return this.filePath?.preload() || null;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
/** @internal */
|
|
@@ -41,27 +46,31 @@ export class NestedGltf extends Behaviour {
|
|
|
41
46
|
|
|
42
47
|
const parent = this.gameObject.parent;
|
|
43
48
|
if (parent) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
res
|
|
56
|
-
|
|
57
|
-
res.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
49
|
+
|
|
50
|
+
if (this.filePath) {
|
|
51
|
+
|
|
52
|
+
this._isLoadingOrDoneLoading = true;
|
|
53
|
+
const opts = new InstantiateOptions();
|
|
54
|
+
// we need to provide stable guids for creating nested gltfs
|
|
55
|
+
opts.idProvider = new InstantiateIdProvider(this.hash(this.guid));
|
|
56
|
+
opts.parent = this.loadAssetInParent !== false ? parent : this.gameObject;
|
|
57
|
+
this.gameObject.updateMatrix();
|
|
58
|
+
const matrix = this.gameObject.matrix;
|
|
59
|
+
if (debug) console.log("Load nested:", this.filePath?.url ?? this.filePath, this.gameObject.position);
|
|
60
|
+
const res = await this.filePath?.instantiate?.call(this.filePath, opts);
|
|
61
|
+
if (debug) console.log("Nested loaded:", this.filePath?.url ?? this.filePath, res);
|
|
62
|
+
if (res && this.loadAssetInParent !== false) {
|
|
63
|
+
res.matrixAutoUpdate = false;
|
|
64
|
+
res.matrix.identity();
|
|
65
|
+
res.applyMatrix4(matrix);
|
|
66
|
+
res.matrixAutoUpdate = true;
|
|
67
|
+
res.layers.disableAll();
|
|
68
|
+
res.layers.set(this.layer);
|
|
69
|
+
this.loaded.invoke({ component: this, instance: res, asset: this.filePath });
|
|
70
|
+
}
|
|
71
|
+
if (debug) console.log("Nested loading done:", this.filePath?.url ?? this.filePath, res);
|
|
63
72
|
}
|
|
64
|
-
|
|
73
|
+
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
76
|
|