@needle-tools/engine 4.8.7-next.e134730 → 4.8.8-next.5537a55
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 +31 -23
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-CvRpjtJj.js → needle-engine.bundle-BoZqXGLO.js} +6957 -6906
- package/dist/{needle-engine.bundle-DrGsgE4t.min.js → needle-engine.bundle-CpsmWsJo.min.js} +144 -144
- package/dist/{needle-engine.bundle-X9nxhICu.umd.cjs → needle-engine.bundle-D6vHQoPC.umd.cjs} +147 -147
- 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 +30 -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 +15 -10
- 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 +8 -1
- package/lib/engine/engine_camera.js +25 -0
- package/lib/engine/engine_camera.js.map +1 -1
- package/lib/engine/engine_context.d.ts +9 -0
- package/lib/engine/engine_context.js +15 -0
- package/lib/engine/engine_context.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_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/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/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/OrbitControls.d.ts +30 -9
- package/lib/engine-components/OrbitControls.js +53 -14
- package/lib/engine-components/OrbitControls.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 +1 -1
- package/plugins/common/files.js +6 -3
- package/plugins/vite/alias.js +26 -17
- package/plugins/vite/editor-connection.js +4 -4
- package/src/engine/engine_addressables.ts +44 -33
- package/src/engine/engine_animation.ts +17 -10
- package/src/engine/engine_assetdatabase.ts +7 -7
- package/src/engine/engine_camera.ts +40 -1
- package/src/engine/engine_context.ts +21 -1
- package/src/engine/engine_gameobject.ts +2 -2
- package/src/engine/engine_loaders.callbacks.ts +1 -0
- package/src/engine/engine_loaders.ts +18 -13
- 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/DropListener.ts +44 -34
- package/src/engine-components/LookAtConstraint.ts +9 -1
- package/src/engine-components/OrbitControls.ts +78 -22
- 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
|
@@ -2,6 +2,7 @@ import { AnimationAction, AnimationClip, AnimationMixer, Object3D, PropertyBindi
|
|
|
2
2
|
|
|
3
3
|
import type { Context } from "./engine_context.js";
|
|
4
4
|
import { GLTF, IAnimationComponent, Model } from "./engine_types.js";
|
|
5
|
+
import { TypeStore } from "./engine_typestore.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Registry for animation related data. Use {@link registerAnimationMixer} to register an animation mixer instance.
|
|
@@ -88,12 +89,15 @@ export class AnimationUtils {
|
|
|
88
89
|
* This method will look for objects in the scene that have animations and assign them to the correct objects.
|
|
89
90
|
* @param file The GLTF file to assign the animations from
|
|
90
91
|
*/
|
|
91
|
-
static
|
|
92
|
+
static autoplayAnimations(file: Object3D | Pick<Model, "animations" | "scene">): Array<IAnimationComponent> | null {
|
|
92
93
|
if (!file || !file.animations) {
|
|
93
94
|
console.debug("No animations found in file");
|
|
94
|
-
return;
|
|
95
|
+
return null;
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
const scene = "scene" in file ? file.scene : file as Object3D;
|
|
99
|
+
const animationComponents = new Array<IAnimationComponent>();
|
|
100
|
+
|
|
97
101
|
for (let i = 0; i < file.animations.length; i++) {
|
|
98
102
|
const animation = file.animations[i];
|
|
99
103
|
if (!animation.tracks || animation.tracks.length <= 0) {
|
|
@@ -103,12 +107,12 @@ export class AnimationUtils {
|
|
|
103
107
|
for (const t in animation.tracks) {
|
|
104
108
|
const track = animation.tracks[t];
|
|
105
109
|
const parsedPath = PropertyBinding.parseTrackName(track.name);
|
|
106
|
-
let obj = PropertyBinding.findNode(
|
|
110
|
+
let obj = PropertyBinding.findNode(scene, parsedPath.nodeName);
|
|
107
111
|
if (!obj) {
|
|
108
112
|
const objectName = track["__objectName"] ?? track.name.substring(0, track.name.indexOf("."));
|
|
109
113
|
// let obj = gltf.scene.getObjectByName(objectName);
|
|
110
114
|
// this finds unnamed objects that still have tracks targeting them
|
|
111
|
-
obj =
|
|
115
|
+
obj = scene.getObjectByProperty('uuid', objectName);
|
|
112
116
|
|
|
113
117
|
if (!obj) {
|
|
114
118
|
// console.warn("could not find " + objectName, animation, gltf.scene);
|
|
@@ -116,20 +120,23 @@ export class AnimationUtils {
|
|
|
116
120
|
}
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
let animationComponent = findAnimationGameObjectInParent(obj);
|
|
123
|
+
let animationComponent = findAnimationGameObjectInParent(obj) || findAnimationGameObjectInParent(scene);
|
|
120
124
|
if (!animationComponent) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
const anim = TypeStore.get("Animation");
|
|
126
|
+
animationComponent = scene.addComponent(anim);
|
|
127
|
+
if (!animationComponent) {
|
|
128
|
+
console.error("Failed creating Animation component: No 'Animation' component found in TypeStore");
|
|
129
|
+
break;
|
|
124
130
|
}
|
|
125
|
-
animationComponent = opts.createAnimationComponent(file.scene, animation);
|
|
126
|
-
obj.addComponent(animationComponent);
|
|
127
131
|
}
|
|
132
|
+
animationComponents.push(animationComponent);
|
|
128
133
|
if (animationComponent.addClip) {
|
|
129
134
|
animationComponent.addClip(animation);
|
|
130
135
|
}
|
|
131
136
|
}
|
|
132
137
|
}
|
|
138
|
+
return animationComponents;
|
|
139
|
+
|
|
133
140
|
function findAnimationGameObjectInParent(obj): IAnimationComponent | null {
|
|
134
141
|
if (!obj) return null;
|
|
135
142
|
const components = obj.userData?.components;
|
|
@@ -61,7 +61,7 @@ export function disposeObjectResources(obj: object | null | undefined) {
|
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
if(typeof obj === "object") {
|
|
64
|
+
if (typeof obj === "object") {
|
|
65
65
|
obj[$disposed] = true;
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -79,8 +79,8 @@ export function disposeObjectResources(obj: object | null | undefined) {
|
|
|
79
79
|
disposeObjectResources(obj.bindMatrixInverse);
|
|
80
80
|
disposeObjectResources(obj.customDepthMaterial);
|
|
81
81
|
disposeObjectResources(obj.customDistanceMaterial);
|
|
82
|
-
obj.geometry =
|
|
83
|
-
obj.material =
|
|
82
|
+
obj.geometry = {};
|
|
83
|
+
obj.material = {};
|
|
84
84
|
obj.visible = false;
|
|
85
85
|
}
|
|
86
86
|
else if (obj instanceof Mesh) {
|
|
@@ -88,8 +88,8 @@ export function disposeObjectResources(obj: object | null | undefined) {
|
|
|
88
88
|
disposeObjectResources(obj.material);
|
|
89
89
|
disposeObjectResources(obj.customDepthMaterial);
|
|
90
90
|
disposeObjectResources(obj.customDistanceMaterial);
|
|
91
|
-
obj.geometry =
|
|
92
|
-
obj.material =
|
|
91
|
+
obj.geometry = {};
|
|
92
|
+
obj.material = {};
|
|
93
93
|
obj.visible = false;
|
|
94
94
|
}
|
|
95
95
|
else if (obj instanceof BufferGeometry) {
|
|
@@ -175,8 +175,8 @@ function free(obj: any) {
|
|
|
175
175
|
|
|
176
176
|
export function __internalNotifyObjectDestroyed(obj: Object3D) {
|
|
177
177
|
if (obj instanceof Mesh || obj instanceof SkinnedMesh) {
|
|
178
|
-
obj.material =
|
|
179
|
-
obj.geometry =
|
|
178
|
+
obj.material = {};
|
|
179
|
+
obj.geometry = {};
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Camera, Object3D } from "three";
|
|
1
|
+
import { Camera, HemisphereLightHelper, Object3D, PerspectiveCamera, Vector2, WebGLRenderer } from "three";
|
|
2
2
|
|
|
3
|
+
import { Mathf } from "./engine_math.js";
|
|
3
4
|
import type { ICameraController } from "./engine_types.js";
|
|
4
5
|
|
|
5
6
|
|
|
@@ -37,4 +38,42 @@ export function useForAutoFit(obj: Object3D): boolean {
|
|
|
37
38
|
*/
|
|
38
39
|
export function setAutoFitEnabled(obj: Object3D, enabled: boolean): void {
|
|
39
40
|
obj[autofit] = enabled;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export type FocusRect = DOMRect | Element | { x: number, y: number, width: number, height: number };
|
|
47
|
+
|
|
48
|
+
let rendererRect: DOMRect | undefined = undefined;
|
|
49
|
+
const overlapRect = { x: 0, y: 0, width: 0, height: 0 };
|
|
50
|
+
|
|
51
|
+
export function updateCameraFocusRect(focusRect: FocusRect, dt: number, camera: PerspectiveCamera, renderer: WebGLRenderer) {
|
|
52
|
+
|
|
53
|
+
if (focusRect instanceof Element) {
|
|
54
|
+
focusRect = focusRect.getBoundingClientRect();
|
|
55
|
+
}
|
|
56
|
+
rendererRect = renderer.domElement.getBoundingClientRect();
|
|
57
|
+
|
|
58
|
+
const rect = overlapRect;
|
|
59
|
+
rect.x = focusRect.x;
|
|
60
|
+
rect.y = focusRect.y;
|
|
61
|
+
rect.width = focusRect.width;
|
|
62
|
+
rect.height = focusRect.height;
|
|
63
|
+
|
|
64
|
+
rect.x -= rendererRect.x;
|
|
65
|
+
rect.y -= rendererRect.y;
|
|
66
|
+
|
|
67
|
+
const targetX = rect.width / -2 - (rect.x - (rendererRect.width / 2));
|
|
68
|
+
const targetY = rect.height / -2 - (rect.y - (rendererRect.height / 2));
|
|
69
|
+
|
|
70
|
+
const view = camera.view;
|
|
71
|
+
|
|
72
|
+
let offsetX = view?.offsetX || 0;
|
|
73
|
+
let offsetY = view?.offsetY || 0;
|
|
74
|
+
offsetX = Mathf.lerp(offsetX, targetX, dt);
|
|
75
|
+
offsetY = Mathf.lerp(offsetY, targetY, dt);
|
|
76
|
+
|
|
77
|
+
camera.setViewOffset(rendererRect.width, rendererRect.height, offsetX, offsetY, rendererRect.width, rendererRect.height);
|
|
78
|
+
camera.updateProjectionMatrix();
|
|
40
79
|
}
|
|
@@ -18,6 +18,7 @@ import { Addressables } from './engine_addressables.js';
|
|
|
18
18
|
import { AnimationsRegistry } from './engine_animation.js';
|
|
19
19
|
import { Application } from './engine_application.js';
|
|
20
20
|
import { AssetDatabase } from './engine_assetdatabase.js';
|
|
21
|
+
import { FocusRect, updateCameraFocusRect } from './engine_camera.js';
|
|
21
22
|
import { VERSION } from './engine_constants.js';
|
|
22
23
|
import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
|
|
23
24
|
import { WaitForPromise } from './engine_coroutine.js';
|
|
@@ -1371,6 +1372,20 @@ export class Context implements IContext {
|
|
|
1371
1372
|
this.internalUpdatePhysics(steps);
|
|
1372
1373
|
}
|
|
1373
1374
|
|
|
1375
|
+
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* Set a rect or dom element. The camera center will be moved to the center of the rect.
|
|
1379
|
+
* This is useful if you have Needle Engine embedded in a HTML layout and while you want the webgl background to fill e.g. the whole screen you want to move the camera center to free space.
|
|
1380
|
+
* For that you can simply pass in the rect or HMTL div that you want the camera to center on.
|
|
1381
|
+
*/
|
|
1382
|
+
public setCameraFocusRect(rect: FocusRect | null) {
|
|
1383
|
+
this._focusRect = rect;
|
|
1384
|
+
}
|
|
1385
|
+
get focusRect() { return this._focusRect; }
|
|
1386
|
+
private _focusRect: FocusRect | null = null;
|
|
1387
|
+
|
|
1388
|
+
|
|
1374
1389
|
private _lastTimestamp = 0;
|
|
1375
1390
|
private _accumulatedTime = 0;
|
|
1376
1391
|
private _dispatchReadyAfterFrame = false;
|
|
@@ -1421,7 +1436,7 @@ export class Context implements IContext {
|
|
|
1421
1436
|
|
|
1422
1437
|
Context.Current = this;
|
|
1423
1438
|
this.time.update();
|
|
1424
|
-
|
|
1439
|
+
|
|
1425
1440
|
if (debugframerate) console.log("FPS", (this.time.smoothedFps).toFixed(0));
|
|
1426
1441
|
|
|
1427
1442
|
|
|
@@ -1498,6 +1513,11 @@ export class Context implements IContext {
|
|
|
1498
1513
|
|
|
1499
1514
|
if (this.isVisibleToUser || this.runInBackground) {
|
|
1500
1515
|
|
|
1516
|
+
if (this._focusRect) {
|
|
1517
|
+
if (this.mainCamera instanceof PerspectiveCamera)
|
|
1518
|
+
updateCameraFocusRect(this._focusRect, this.time.deltaTime / .05, this.mainCamera, this.renderer);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1501
1521
|
this._currentFrameEvent = FrameEvent.OnBeforeRender;
|
|
1502
1522
|
|
|
1503
1523
|
// should we move these callbacks in the regular three onBeforeRender events?
|
|
@@ -166,9 +166,9 @@ export function destroy(instance: Object3D | Component, recursive: boolean = tru
|
|
|
166
166
|
setDestroyed(obj, true);
|
|
167
167
|
if (dispose) {
|
|
168
168
|
disposeObjectResources(obj);
|
|
169
|
+
// This needs to be called after disposing because it removes the references to resources
|
|
170
|
+
__internalRemoveReferences(obj);
|
|
169
171
|
}
|
|
170
|
-
// This needs to be called after disposing because it removes the references to resources
|
|
171
|
-
__internalRemoveReferences(obj);
|
|
172
172
|
}
|
|
173
173
|
destroyed_objects.length = 0;
|
|
174
174
|
destroyed_components.length = 0;
|
|
@@ -98,6 +98,7 @@ export namespace NeedleEngineModelLoader {
|
|
|
98
98
|
* }
|
|
99
99
|
* return null;
|
|
100
100
|
* });
|
|
101
|
+
* ```
|
|
101
102
|
*/
|
|
102
103
|
export function onCreateCustomModelLoader(callback: CustomLoaderCallback, opts?: CustomLoaderOptions) {
|
|
103
104
|
const entry = { name: opts?.name, priority: opts?.priority ?? 0, callback };
|
|
@@ -61,7 +61,7 @@ export async function onCreateLoader(url: string, context: Context): Promise<Cus
|
|
|
61
61
|
switch (type) {
|
|
62
62
|
case "unsupported":
|
|
63
63
|
return null;
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
default:
|
|
66
66
|
case "unknown":
|
|
67
67
|
{
|
|
@@ -287,15 +287,6 @@ async function onAfterLoaded(loader: Loader | CustomLoader, context: Context, gl
|
|
|
287
287
|
gltfId = gltfId.split("?")[0];
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
// assign animations of loaded glTF to all scenes
|
|
291
|
-
if ("scenes" in model) {
|
|
292
|
-
for (const scene of model.scenes) {
|
|
293
|
-
if (scene && !scene.animations?.length) {
|
|
294
|
-
scene.animations = [...model.animations];
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
290
|
// E.g. fbx material cleanup
|
|
300
291
|
postprocessLoadedFile(loader, model);
|
|
301
292
|
|
|
@@ -357,13 +348,27 @@ function checkIfUserAttemptedToLoadALocalFile(url: string) {
|
|
|
357
348
|
/**
|
|
358
349
|
* Postprocess the loaded file. This is used to apply any custom postprocessing to the loaded file.
|
|
359
350
|
*/
|
|
360
|
-
function postprocessLoadedFile(loader: object,
|
|
351
|
+
function postprocessLoadedFile(loader: object, model: Model) {
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
// assign animations of loaded glTF to all scenes
|
|
355
|
+
if ("scenes" in model) {
|
|
356
|
+
for (const scene of model.scenes) {
|
|
357
|
+
if (scene && !scene.animations?.length) {
|
|
358
|
+
for (const anim of model.animations) {
|
|
359
|
+
if (!scene.animations.includes(anim)) {
|
|
360
|
+
scene.animations.push(anim);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
361
366
|
|
|
362
367
|
if (loader instanceof FBXLoader || loader instanceof OBJLoader) {
|
|
363
368
|
|
|
364
|
-
let obj: Object3D | Model =
|
|
369
|
+
let obj: Object3D | Model = model;
|
|
365
370
|
if (!(obj instanceof Object3D)) {
|
|
366
|
-
obj = (
|
|
371
|
+
obj = (model as GLTF).scene || model.scenes.find(s => s);
|
|
367
372
|
}
|
|
368
373
|
|
|
369
374
|
obj.traverse((child) => {
|
|
@@ -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
|
}
|
|
@@ -10,6 +10,7 @@ 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
12
|
import { RGBAColor } from "../engine/js-extensions/index.js";
|
|
13
|
+
import { NeedleXREventArgs } from "../engine/engine_xr.js";
|
|
13
14
|
import { Behaviour, GameObject } from "./Component.js";
|
|
14
15
|
import { OrbitControls } from "./OrbitControls.js";
|
|
15
16
|
|
|
@@ -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}\"`;
|
|
@@ -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
|
}
|