@needle-tools/engine 5.1.9 → 5.1.10
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 +6 -0
- package/SKILL.md +62 -4
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-XZ6cKssu.js → needle-engine.bundle-DP--RGAU.js} +3144 -3021
- package/dist/{needle-engine.bundle-CHq9xqXv.min.js → needle-engine.bundle-DZD-GG7l.min.js} +122 -121
- package/dist/{needle-engine.bundle-DYPk7VZg.umd.cjs → needle-engine.bundle-Dv5TidOk.umd.cjs} +132 -131
- package/dist/needle-engine.d.ts +90 -17
- package/dist/needle-engine.js +610 -606
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/three-examples.js +727 -781
- package/dist/three-examples.min.js +12 -12
- package/dist/three-examples.umd.cjs +9 -9
- package/lib/engine/api.d.ts +1 -1
- package/lib/engine/api.js +1 -1
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_init.js +2 -2
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_license.d.ts +7 -7
- package/lib/engine/engine_license.js +71 -71
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_materialpropertyblock.d.ts +13 -4
- package/lib/engine/engine_materialpropertyblock.js +16 -5
- package/lib/engine/engine_materialpropertyblock.js.map +1 -1
- package/lib/engine/engine_networking_blob.js +3 -3
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_utils_qrcode.js +2 -2
- package/lib/engine/engine_utils_qrcode.js.map +1 -1
- package/lib/engine/postprocessing/postprocessing.d.ts +18 -0
- package/lib/engine/postprocessing/postprocessing.js +31 -2
- package/lib/engine/postprocessing/postprocessing.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js +5 -5
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.js +2 -2
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine/xr/TempXRContext.js +2 -2
- package/lib/engine/xr/TempXRContext.js.map +1 -1
- package/lib/engine/xr/XRHandMeshModel.d.ts +31 -0
- package/lib/engine/xr/XRHandMeshModel.js +153 -0
- package/lib/engine/xr/XRHandMeshModel.js.map +1 -0
- package/lib/engine-components/ReflectionProbe.js +3 -3
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/RendererLightmap.js +1 -1
- package/lib/engine-components/RendererLightmap.js.map +1 -1
- package/lib/engine-components/SyncedRoom.js +5 -0
- package/lib/engine-components/SyncedRoom.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/DepthOfField.js +10 -2
- package/lib/engine-components/postprocessing/Effects/DepthOfField.js.map +1 -1
- package/lib/engine-components/postprocessing/index.d.ts +4 -0
- package/lib/engine-components/postprocessing/index.js +7 -0
- package/lib/engine-components/postprocessing/index.js.map +1 -1
- package/lib/engine-components/utils/LookAt.d.ts +13 -4
- package/lib/engine-components/utils/LookAt.js +13 -4
- package/lib/engine-components/utils/LookAt.js.map +1 -1
- package/lib/engine-components/webxr/WebXR.js +1 -1
- package/lib/engine-components/webxr/WebXR.js.map +1 -1
- package/lib/engine-components/webxr/controllers/XRControllerModel.d.ts +1 -1
- package/lib/engine-components/webxr/controllers/XRControllerModel.js +12 -7
- package/lib/engine-components/webxr/controllers/XRControllerModel.js.map +1 -1
- package/package.json +2 -2
- package/plugins/common/license.js +4 -4
- package/plugins/vite/license.js +4 -4
- package/src/engine/api.ts +1 -1
- package/src/engine/engine_init.ts +2 -2
- package/src/engine/engine_license.ts +68 -68
- package/src/engine/engine_materialpropertyblock.ts +17 -5
- package/src/engine/engine_networking_blob.ts +3 -3
- package/src/engine/engine_utils_qrcode.ts +2 -2
- package/src/engine/postprocessing/postprocessing.ts +32 -2
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +5 -5
- package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
- package/src/engine/webcomponents/needle-engine.ts +2 -2
- package/src/engine/xr/TempXRContext.ts +2 -2
- package/src/engine/xr/XRHandMeshModel.ts +179 -0
- package/src/engine-components/ReflectionProbe.ts +3 -3
- package/src/engine-components/RendererLightmap.ts +1 -1
- package/src/engine-components/SyncedRoom.ts +5 -0
- package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
- package/src/engine-components/postprocessing/Effects/DepthOfField.ts +10 -2
- package/src/engine-components/postprocessing/index.ts +7 -0
- package/src/engine-components/utils/LookAt.ts +13 -4
- package/src/engine-components/webxr/WebXR.ts +1 -1
- package/src/engine-components/webxr/controllers/XRControllerModel.ts +13 -7
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { type Bone, type Object3D, type XRHandSpace } from "three";
|
|
2
|
+
import type { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Vendored from three's `examples/jsm/webxr/XRHandMeshModel.js` (r185).
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS IS OWNED BY THE ENGINE AND NOT PATCHED IN THE THREE FORK
|
|
9
|
+
* -----------------------------------------------------------------
|
|
10
|
+
* three addons carry no API stability guarantee and this one changed under us twice:
|
|
11
|
+
*
|
|
12
|
+
* - r184 replaced `const object = gltf.scene.children[0]` with
|
|
13
|
+
* `const object = clone(gltf.scene.children[0])`. That single word broke every custom
|
|
14
|
+
* (Unity-exported) hand model, silently: `SkeletonUtils.clone` runs `Object3D.copy`,
|
|
15
|
+
* which deep-copies userData via `JSON.parse(JSON.stringify(...))`. Needle stores live
|
|
16
|
+
* component instances in `userData.components`, and components hold a `gameObject`
|
|
17
|
+
* back-reference, so that throws "Converting circular structure to JSON" — swallowed by
|
|
18
|
+
* GLTFLoader into an onError nobody had set, leaving an empty, invisible hand.
|
|
19
|
+
* - The clone also broke component creation: `XRControllerModel` creates the glTF's builtin
|
|
20
|
+
* components after load, but the parser's node -> object associations point at the parsed
|
|
21
|
+
* original while the clone is what gets displayed.
|
|
22
|
+
*
|
|
23
|
+
* DELIBERATE DIFFERENCES FROM UPSTREAM
|
|
24
|
+
* ------------------------------------
|
|
25
|
+
* 1. No clone. Upstream needs it only because of `customCache`, where several hands share
|
|
26
|
+
* one parsed gltf. We never pass a cache and build a fresh GLTFLoader per hand load, so
|
|
27
|
+
* nothing is shared and the clone was pure overhead. Using the parsed object directly
|
|
28
|
+
* restores the pre-r184 contract the engine is written against: `handModel.add(object)`
|
|
29
|
+
* reparents it out of `gltf.scene` (Object3D.add calls removeFromParent), object identity
|
|
30
|
+
* is preserved so the parser's node map stays valid, and components created during parse
|
|
31
|
+
* live on the object that is actually rendered.
|
|
32
|
+
* 2. `customCache` is dropped entirely rather than left unused — sharing a parsed hand
|
|
33
|
+
* between models would reintroduce the aliasing the clone existed to prevent.
|
|
34
|
+
* 3. The hand mesh inherits the layer mask of the object it is added to (the engine puts
|
|
35
|
+
* hands on the ignore-raycast layer).
|
|
36
|
+
* 4. Load failures are reported instead of vanishing.
|
|
37
|
+
* 5. A missing SkinnedMesh or missing joints no longer throw / spam per joint.
|
|
38
|
+
*
|
|
39
|
+
* When bumping three, diff this against the upstream file and fold in real fixes by hand.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const DEFAULT_HAND_PROFILE_PATH = "https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@1.0/dist/profiles/generic-hand/";
|
|
43
|
+
|
|
44
|
+
/** The joints of a WebXR hand, in the order three's hand models declare them. */
|
|
45
|
+
const JOINT_NAMES = [
|
|
46
|
+
"wrist",
|
|
47
|
+
"thumb-metacarpal",
|
|
48
|
+
"thumb-phalanx-proximal",
|
|
49
|
+
"thumb-phalanx-distal",
|
|
50
|
+
"thumb-tip",
|
|
51
|
+
"index-finger-metacarpal",
|
|
52
|
+
"index-finger-phalanx-proximal",
|
|
53
|
+
"index-finger-phalanx-intermediate",
|
|
54
|
+
"index-finger-phalanx-distal",
|
|
55
|
+
"index-finger-tip",
|
|
56
|
+
"middle-finger-metacarpal",
|
|
57
|
+
"middle-finger-phalanx-proximal",
|
|
58
|
+
"middle-finger-phalanx-intermediate",
|
|
59
|
+
"middle-finger-phalanx-distal",
|
|
60
|
+
"middle-finger-tip",
|
|
61
|
+
"ring-finger-metacarpal",
|
|
62
|
+
"ring-finger-phalanx-proximal",
|
|
63
|
+
"ring-finger-phalanx-intermediate",
|
|
64
|
+
"ring-finger-phalanx-distal",
|
|
65
|
+
"ring-finger-tip",
|
|
66
|
+
"pinky-finger-metacarpal",
|
|
67
|
+
"pinky-finger-phalanx-proximal",
|
|
68
|
+
"pinky-finger-phalanx-intermediate",
|
|
69
|
+
"pinky-finger-phalanx-distal",
|
|
70
|
+
"pinky-finger-tip",
|
|
71
|
+
] as const;
|
|
72
|
+
|
|
73
|
+
type JointBone = Bone & { jointName?: string };
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Renders a skinned hand mesh and drives its bones from the tracked WebXR joint poses.
|
|
77
|
+
*
|
|
78
|
+
* @see {@link XRControllerModel} which creates one per tracked hand.
|
|
79
|
+
*/
|
|
80
|
+
export class XRHandMeshModel {
|
|
81
|
+
|
|
82
|
+
/** The three XRHandSpace whose `joints` drive this mesh. */
|
|
83
|
+
readonly controller: XRHandSpace;
|
|
84
|
+
/** The object the loaded hand mesh is added to. */
|
|
85
|
+
readonly handModel: Object3D;
|
|
86
|
+
/** The bones driven by {@link updateMesh}, in {@link JOINT_NAMES} order. Sparse if the model lacks joints. */
|
|
87
|
+
readonly bones: Array<JointBone | undefined> = [];
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param handModel the object the loaded mesh is parented to
|
|
91
|
+
* @param controller the hand space providing joint poses
|
|
92
|
+
* @param path base path for the model (defaults to the generic hand profile CDN)
|
|
93
|
+
* @param handedness file name to load (without extension) — either "left"/"right" for the
|
|
94
|
+
* default profile, or a full path for a custom hand model
|
|
95
|
+
* @param loader loader to use; a plain GLTFLoader is created when omitted
|
|
96
|
+
* @param onLoad invoked with the loaded hand object once it has been added
|
|
97
|
+
*/
|
|
98
|
+
constructor(
|
|
99
|
+
handModel: Object3D,
|
|
100
|
+
controller: XRHandSpace,
|
|
101
|
+
path: string | null,
|
|
102
|
+
handedness: string,
|
|
103
|
+
loader: GLTFLoader | null = null,
|
|
104
|
+
onLoad: ((object: Object3D) => void) | null = null,
|
|
105
|
+
) {
|
|
106
|
+
this.controller = controller;
|
|
107
|
+
this.handModel = handModel;
|
|
108
|
+
|
|
109
|
+
const pathToUse = path || DEFAULT_HAND_PROFILE_PATH;
|
|
110
|
+
|
|
111
|
+
if (loader === null) {
|
|
112
|
+
loader = new GLTFLoader();
|
|
113
|
+
loader.setPath(pathToUse);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const url = `${handedness}.glb`;
|
|
117
|
+
loader.load(url,
|
|
118
|
+
(gltf: GLTF) => {
|
|
119
|
+
const object = gltf.scene.children[0];
|
|
120
|
+
if (!object) {
|
|
121
|
+
console.error(`XRHandMeshModel: "${pathToUse}${url}" contains no hand object.`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// NOTE: adding reparents `object` out of `gltf.scene` (Object3D.add calls
|
|
126
|
+
// removeFromParent), which is what lets the caller detect that the hand has
|
|
127
|
+
// been taken over and create the glTF's builtin components on it.
|
|
128
|
+
this.handModel.add(object);
|
|
129
|
+
// the hand mesh inherits the layers of whatever it was added to
|
|
130
|
+
object.layers.mask = this.handModel.layers.mask;
|
|
131
|
+
|
|
132
|
+
const mesh = object.getObjectByProperty("type", "SkinnedMesh");
|
|
133
|
+
if (mesh) {
|
|
134
|
+
mesh.frustumCulled = false;
|
|
135
|
+
mesh.castShadow = true;
|
|
136
|
+
mesh.receiveShadow = true;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.warn(`XRHandMeshModel: no SkinnedMesh in the ${handedness} hand model — it will not deform.`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const missing: string[] = [];
|
|
143
|
+
for (const jointName of JOINT_NAMES) {
|
|
144
|
+
const bone = object.getObjectByName(jointName) as JointBone | undefined;
|
|
145
|
+
if (bone) bone.jointName = jointName;
|
|
146
|
+
else missing.push(jointName);
|
|
147
|
+
this.bones.push(bone);
|
|
148
|
+
}
|
|
149
|
+
// one warning instead of one per joint — a model with the wrong rig misses all 25
|
|
150
|
+
if (missing.length) {
|
|
151
|
+
console.warn(`XRHandMeshModel: the ${handedness} hand model is missing ${missing.length} of ${JOINT_NAMES.length} joints (${missing.join(", ")}).`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
onLoad?.(object);
|
|
155
|
+
},
|
|
156
|
+
undefined,
|
|
157
|
+
(err: unknown) => {
|
|
158
|
+
// Upstream passes no error handler, so a failed hand model silently leaves an
|
|
159
|
+
// empty object and no hand is ever visible. Surface it instead.
|
|
160
|
+
console.error(`XRHandMeshModel: failed to load the ${handedness} hand model from "${pathToUse}${url}" — the hand will not be visible.`, err);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Copies the current tracked joint poses onto the mesh bones. Called every XR frame. */
|
|
165
|
+
updateMesh() {
|
|
166
|
+
const XRJoints = this.controller.joints;
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < this.bones.length; i++) {
|
|
169
|
+
const bone = this.bones[i];
|
|
170
|
+
if (!bone?.jointName) continue;
|
|
171
|
+
|
|
172
|
+
const XRJoint = XRJoints[bone.jointName];
|
|
173
|
+
if (XRJoint?.visible) {
|
|
174
|
+
bone.position.copy(XRJoint.position);
|
|
175
|
+
bone.quaternion.copy(XRJoint.quaternion);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -289,9 +289,9 @@ export class ReflectionProbe extends Behaviour {
|
|
|
289
289
|
if (block) {
|
|
290
290
|
const current = block.getOverride("envMap")?.value;
|
|
291
291
|
if (current === this.texture) {
|
|
292
|
-
block.
|
|
293
|
-
block.
|
|
294
|
-
block.
|
|
292
|
+
block.removeOverride("envMap");
|
|
293
|
+
block.removeOverride("envMapRotation");
|
|
294
|
+
block.removeOverride("envMapIntensity");
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
}
|
|
@@ -190,6 +190,11 @@ export class SyncedRoom extends Behaviour {
|
|
|
190
190
|
if (val && (typeof val === "string" || typeof val === "number")) {
|
|
191
191
|
hasRoomParameter = true;
|
|
192
192
|
const roomNameParam = utils.sanitizeString(val.toString());
|
|
193
|
+
// The URL parameter takes precedence over the configured roomName. That is
|
|
194
|
+
// easy to miss when a room name was set deliberately, so say so in dev.
|
|
195
|
+
if ((debug || isDevEnvironment()) && this.roomName?.length > 0 && this.roomName !== roomNameParam) {
|
|
196
|
+
console.warn(`[SyncedRoom] Joining "${roomNameParam}" from the "${this.urlParameterName}" URL parameter — the roomName set on the component ("${this.roomName}") is ignored.\nThe URL parameter always wins. To use the component's room name instead, clear "urlParameterName".`);
|
|
197
|
+
}
|
|
193
198
|
this.roomName = roomNameParam;
|
|
194
199
|
}
|
|
195
200
|
else if (this.joinRandomRoom) {
|
|
@@ -3,7 +3,7 @@ import { Euler, Material, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from "t
|
|
|
3
3
|
|
|
4
4
|
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/index.js";
|
|
5
5
|
import { findObjectOfType } from "../../../engine/engine_components.js";
|
|
6
|
-
import {
|
|
6
|
+
import { $Ljyi } from "../../../engine/engine_license.js";
|
|
7
7
|
import { serializable } from "../../../engine/engine_serialization.js";
|
|
8
8
|
import { getFormattedDate, Progress } from "../../../engine/engine_time_utils.js";
|
|
9
9
|
import { DeviceUtilities, getParam } from "../../../engine/engine_utils.js";
|
|
@@ -277,7 +277,7 @@ export class USDZExporter extends Behaviour {
|
|
|
277
277
|
let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
|
|
278
278
|
name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
|
|
279
279
|
|
|
280
|
-
if (
|
|
280
|
+
if (!$Ljyi()) {
|
|
281
281
|
if (name !== "") name += "-";
|
|
282
282
|
name += "MadeWithNeedle";
|
|
283
283
|
}
|
|
@@ -682,7 +682,7 @@ export class USDZExporter extends Behaviour {
|
|
|
682
682
|
if (debug)
|
|
683
683
|
showBalloonMessage("Quicklook url: " + callToActionURL);
|
|
684
684
|
if (callToActionURL) {
|
|
685
|
-
if (
|
|
685
|
+
if (!$Ljyi()) {
|
|
686
686
|
console.warn("Quicklook closed: custom redirects require a Needle Engine Pro license: https://needle.tools/pricing", callToActionURL)
|
|
687
687
|
}
|
|
688
688
|
else {
|
|
@@ -697,7 +697,7 @@ export class USDZExporter extends Behaviour {
|
|
|
697
697
|
private buildQuicklookOverlay(): CustomBranding {
|
|
698
698
|
const obj: CustomBranding = {};
|
|
699
699
|
if (this.customBranding) Object.assign(obj, this.customBranding);
|
|
700
|
-
if (
|
|
700
|
+
if (!$Ljyi()) {
|
|
701
701
|
console.log("Custom Quicklook banner text requires pro license: https://needle.tools/pricing");
|
|
702
702
|
obj.callToAction = "Close";
|
|
703
703
|
obj.checkoutTitle = "🌵 Made with Needle";
|
|
@@ -83,12 +83,20 @@ export class DepthOfField extends PostProcessingEffect {
|
|
|
83
83
|
// inverted: true,
|
|
84
84
|
// // blendFunction: BlendFunction.SET,
|
|
85
85
|
// });
|
|
86
|
+
// Seed from the parameters, the same way BloomEffect does. The
|
|
87
|
+
// onValueChanged handlers below only fire on a LATER assignment, so
|
|
88
|
+
// hardcoding defaults here discarded anything set before the effect
|
|
89
|
+
// was created — which is the usual case, since components are
|
|
90
|
+
// configured right after addComponent but the effect is built when
|
|
91
|
+
// the composer asks for it. Values applied in awake/start were lost
|
|
92
|
+
// and only took hold if the same value was assigned a second time.
|
|
86
93
|
const dof = new MODULES.POSTPROCESSING.MODULE.DepthOfFieldEffect(this.context.mainCamera!, {
|
|
87
|
-
worldFocusRange: .
|
|
94
|
+
worldFocusRange: this.focalLength.value,
|
|
88
95
|
focalLength: 1,
|
|
89
|
-
bokehScale:
|
|
96
|
+
bokehScale: this.aperture.value,
|
|
90
97
|
resolutionScale: this.resolutionScale.value,
|
|
91
98
|
});
|
|
99
|
+
dof.cocMaterial.worldFocusDistance = this.focusDistance.value;
|
|
92
100
|
|
|
93
101
|
this.focusDistance.onValueChanged = v => {
|
|
94
102
|
dof.cocMaterial.worldFocusDistance = v;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export * from "./PostProcessingEffect.js";
|
|
2
2
|
export * from "./PostProcessingHandler.js"
|
|
3
3
|
export { PostProcessingEffectOrder } from "./utils.js";
|
|
4
|
+
/* Effect option enums. The effect components themselves are exported from
|
|
5
|
+
codegen/components.ts, but these were unreachable: `DepthOfField.mode`
|
|
6
|
+
could not be set to a named value from outside the package. */
|
|
7
|
+
export { DepthOfFieldMode } from "./Effects/DepthOfField.js";
|
|
8
|
+
export { QualityLevel } from "./Effects/Antialiasing.js";
|
|
9
|
+
export { ScreenSpaceAmbientOcclusionN8QualityMode } from "./Effects/ScreenspaceAmbientOcclusionN8.js";
|
|
10
|
+
export { NEToneMappingMode } from "./Effects/Tonemapping.utils.js";
|
|
4
11
|
export { PostProcessingManager } from "./Volume.js"
|
|
5
12
|
export { Volume } from "./Volume.js"
|
|
6
13
|
export * from "./VolumeParameter.js"
|
|
@@ -20,25 +20,34 @@ import { Behaviour } from "../Component.js";
|
|
|
20
20
|
export class LookAt extends Behaviour implements UsdzBehaviour {
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* The target object to look at.
|
|
23
|
+
* The target object to look at.
|
|
24
|
+
* @default undefined — when no target is set the main camera is used
|
|
24
25
|
*/
|
|
25
26
|
@serializable(Object3D)
|
|
26
27
|
target?: Object3D;
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
|
-
* Inverts the forward direction
|
|
30
|
+
* Inverts the forward direction, so the object turns its back to the target
|
|
31
|
+
* instead of facing it. Useful for models authored facing -Z.
|
|
32
|
+
* @default false
|
|
30
33
|
*/
|
|
31
34
|
@serializable()
|
|
32
35
|
invertForward: boolean = false;
|
|
33
36
|
|
|
34
37
|
/**
|
|
35
|
-
*
|
|
38
|
+
* Keeps the object upright while turning, so it only rotates around the up
|
|
39
|
+
* axis rather than tilting to point directly at a target above or below it.
|
|
40
|
+
* @default true
|
|
36
41
|
*/
|
|
37
42
|
@serializable()
|
|
38
43
|
keepUpDirection: boolean = true;
|
|
39
44
|
|
|
40
45
|
/**
|
|
41
|
-
*
|
|
46
|
+
* Copies the target's rotation instead of aiming at its position. Use this to
|
|
47
|
+
* match a target's orientation, for example to align with the camera.
|
|
48
|
+
*
|
|
49
|
+
* Ignored during a VR or AR session.
|
|
50
|
+
* @default false
|
|
42
51
|
*/
|
|
43
52
|
@serializable()
|
|
44
53
|
copyTargetRotation: boolean = false;
|
|
@@ -520,7 +520,7 @@ export class WebXR extends Behaviour {
|
|
|
520
520
|
models = this.gameObject.addComponent(XRControllerModel)!;
|
|
521
521
|
this._createdComponentsInSession.push(models);
|
|
522
522
|
models.createControllerModel = this.showControllerModels;
|
|
523
|
-
models.createHandModel
|
|
523
|
+
models.createHandModel = this.showHandModels;
|
|
524
524
|
}
|
|
525
525
|
if (models) models.enabled = enabled;
|
|
526
526
|
return models;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AxesHelper, Group, Material, Mesh, Object3D, XRHandSpace } from "three";
|
|
2
2
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
3
|
import { XRControllerModelFactory } from "three/examples/jsm/webxr/XRControllerModelFactory.js";
|
|
4
|
-
import { XRHandMeshModel } from "three/examples/jsm/webxr/XRHandMeshModel.js";
|
|
5
4
|
|
|
6
5
|
import { AssetReference } from "../../../engine/engine_addressables.js";
|
|
7
6
|
import { setDontDestroy } from "../../../engine/engine_gameobject.js";
|
|
@@ -15,6 +14,7 @@ import { NeedleXRController, type NeedleXRControllerEventArgs, type NeedleXREven
|
|
|
15
14
|
import { registerComponentExtension, registerExtensions } from "../../../engine/extensions/extensions.js";
|
|
16
15
|
import { NEEDLE_progressive } from "../../../engine/extensions/NEEDLE_progressive.js";
|
|
17
16
|
import { flipForwardMatrix } from "../../../engine/xr/internal.js";
|
|
17
|
+
import { XRHandMeshModel } from "../../../engine/xr/XRHandMeshModel.js";
|
|
18
18
|
import { Behaviour, Component, GameObject } from "../../Component.js"
|
|
19
19
|
|
|
20
20
|
const debug = getParam("debugwebxr");
|
|
@@ -288,8 +288,6 @@ export class XRControllerModel extends Behaviour {
|
|
|
288
288
|
|
|
289
289
|
const loader = new GLTFLoader();
|
|
290
290
|
addDracoAndKTX2Loaders(loader, context);
|
|
291
|
-
await registerExtensions(loader, context, this.sourceId ?? "", this.sourceId ?? "");
|
|
292
|
-
const componentsExtension = registerComponentExtension(loader);
|
|
293
291
|
|
|
294
292
|
let filename = "";
|
|
295
293
|
|
|
@@ -306,22 +304,30 @@ export class XRControllerModel extends Behaviour {
|
|
|
306
304
|
loader.setPath('https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@1.0/dist/profiles/generic-hand/');
|
|
307
305
|
}
|
|
308
306
|
|
|
307
|
+
// The hand is a separate glTF and needs its own source id: its components resolve their
|
|
308
|
+
// relative uris against it (resolveUrl) and its lightmaps/skybox are keyed by it. Using
|
|
309
|
+
// this component's id resolved the hand's uris next to the HOST glb and let the hand's
|
|
310
|
+
// skybox overwrite the scene's. customHand.url is already resolved, `filename` is not
|
|
311
|
+
// (extension stripped for XRHandMeshModel).
|
|
312
|
+
const handSourceId = customHand?.url ?? `${loader.path}${filename}.glb`;
|
|
313
|
+
await registerExtensions(loader, context, handSourceId, handSourceId);
|
|
314
|
+
const componentsExtension = registerComponentExtension(loader);
|
|
315
|
+
|
|
309
316
|
|
|
310
317
|
const handObject = new Object3D();
|
|
311
318
|
setDontDestroy(handObject);
|
|
312
|
-
// @ts-ignore
|
|
313
319
|
const handmesh = new XRHandMeshModel(handObject, hand, loader.path, filename, loader, (object: Object3D) => {
|
|
314
320
|
|
|
315
321
|
const gltf = componentsExtension?.gltf;
|
|
316
|
-
//
|
|
317
|
-
// we need
|
|
322
|
+
// XRHandMeshModel reparents the hand out of the gltf scene before calling this
|
|
323
|
+
// callback; we need it back in the scene so the builtin components are created on it
|
|
318
324
|
if (gltf?.scene.children?.length === 0) {
|
|
319
325
|
gltf.scene.children[0] = object;
|
|
320
326
|
}
|
|
321
327
|
|
|
322
328
|
// console.log(controller.side, componentsExtension.gltf, object, componentsExtension.gltf.scene?.children)
|
|
323
329
|
if (componentsExtension?.gltf)
|
|
324
|
-
getLoader().createBuiltinComponents(comp.context,
|
|
330
|
+
getLoader().createBuiltinComponents(comp.context, handSourceId, componentsExtension.gltf, null, componentsExtension);
|
|
325
331
|
|
|
326
332
|
// The hand mesh should not receive raycasts
|
|
327
333
|
object.traverse(child => {
|