@@ -1,5 +1,6 @@
|
|
1
1
|
import { AnimationClip, Bone,Interpolant, KeyframeTrack, Matrix4, Object3D, PropertyBinding, Quaternion, Vector3 } from "three";
|
2
2
|
|
3
|
+
import { isDevEnvironment, showBalloonWarning } from "../../../../engine/debug/debug.js";
|
3
4
|
import { getParam } from "../../../../engine/engine_utils.js";
|
4
5
|
import { Animator } from "../../../Animator.js";
|
5
6
|
import { GameObject } from "../../../Component.js";
|
@@ -76,15 +77,37 @@
|
|
76
77
|
if (animator) this.useRootMotion = animator.applyRootMotion;
|
77
78
|
}
|
78
79
|
|
79
|
-
addTrack(track) {
|
80
|
+
addTrack(track: KeyframeTrack) {
|
80
81
|
if (!this.clip) {
|
81
82
|
console.error("This is a rest clip but you're trying to add tracks to it – this is likely a bug");
|
82
83
|
return;
|
83
84
|
}
|
84
85
|
|
85
86
|
if (track.name.endsWith("position")) this.pos = track;
|
86
|
-
if (track.name.endsWith("quaternion")) this.rot = track;
|
87
|
-
if (track.name.endsWith("scale")) this.scale = track;
|
87
|
+
else if (track.name.endsWith("quaternion")) this.rot = track;
|
88
|
+
else if (track.name.endsWith("scale")) this.scale = track;
|
89
|
+
else {
|
90
|
+
if (track.name.endsWith("activeSelf")) {
|
91
|
+
/*
|
92
|
+
// Construct a scale track, then apply to the existing scale track.
|
93
|
+
// Not supported right now, because it would also require properly tracking that these objects need to be enabled
|
94
|
+
// at animation start because they're animating the enabled state... otherwise they just stay disabled from scene start
|
95
|
+
const newValues = [...track.values].map((v) => v ? [1,1,1] : [0,0,0]).flat();
|
96
|
+
const scaleTrack = new KeyframeTrack(track.name.replace(".activeSelf", ".scale"), track.times, newValues, InterpolateDiscrete);
|
97
|
+
if (!this.scale)
|
98
|
+
{
|
99
|
+
this.scale = scaleTrack;
|
100
|
+
console.log("Mock scale track", this.scale);
|
101
|
+
}
|
102
|
+
*/
|
103
|
+
console.warn("[USDZ] Animation of enabled/disabled state is not supported for USDZ export and will NOT be exported: " + track.name + " on " + (this.root?.name ?? this.target.name) + ". Animate scale 0/1 instead.");
|
104
|
+
}
|
105
|
+
else {
|
106
|
+
console.warn("[USDZ] Animation track type not supported for USDZ export and will NOT be exported: " + track.name + " on " + (this.root?.name ?? this.target.name) + ". Only .position, .rotation, .scale are supported.");
|
107
|
+
}
|
108
|
+
|
109
|
+
if (isDevEnvironment()) showBalloonWarning("[USDZ] Some animations can't be exported. See console for details.");
|
110
|
+
}
|
88
111
|
}
|
89
112
|
|
90
113
|
getFrames(): number {
|
@@ -303,6 +326,10 @@
|
|
303
326
|
const targets = this.rootTargetMap.get(root);
|
304
327
|
const unregisteredNodesForThisClip = new Set(targets);
|
305
328
|
if (clip && clip.tracks) {
|
329
|
+
// We could sort so that supported tracks come first, this allows us to support some additional tracks by
|
330
|
+
// modifying what has already been written for the supported ones (e.g. enabled -> modify scale).
|
331
|
+
// Only needed if we're actually emulating some unsupported track types in addTrack(...).
|
332
|
+
// const sortedTracks = clip.tracks.filter(x => !!x).sort((a, _b) => a.name.endsWith("position") || a.name.endsWith("quaternion") || a.name.endsWith("scale") ? -1 : 1);
|
306
333
|
for (const track of clip.tracks) {
|
307
334
|
const parsedPath = PropertyBinding.parseTrackName(track.name);
|
308
335
|
const animationTarget = PropertyBinding.findNode(root, parsedPath.nodeName);
|
@@ -473,7 +500,7 @@
|
|
473
500
|
dict: AnimationDict;
|
474
501
|
model: USDObject | undefined = undefined;
|
475
502
|
|
476
|
-
private callback?:
|
503
|
+
private callback?: (writer: USDWriter, context: USDZExporterContext) => void;
|
477
504
|
|
478
505
|
constructor(object: Object3D, dict: AnimationDict) {
|
479
506
|
this.object = object;
|
@@ -488,7 +515,9 @@
|
|
488
515
|
this.callback = this.onSerialize.bind(this);
|
489
516
|
if (debugSerialization) console.log("REPARENT", model);
|
490
517
|
this.model = model;
|
491
|
-
|
518
|
+
|
519
|
+
if (this.callback)
|
520
|
+
this.model.addEventListener("serialize", this.callback);
|
492
521
|
}
|
493
522
|
|
494
523
|
skinnedMeshExport(writer: USDWriter, _context: USDZExporterContext) {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { AxesHelper, Box3, BoxGeometry, BufferAttribute, Color, type ColorRepresentation, CylinderGeometry, EdgesGeometry, Line, LineBasicMaterial, LineSegments, Mesh, Object3D, Quaternion, SphereGeometry, Vector3 } from 'three';
|
1
|
+
import { AxesHelper, Box3, BoxGeometry, BufferAttribute, BufferGeometry, Color, type ColorRepresentation, CylinderGeometry, EdgesGeometry, Line, LineBasicMaterial, LineSegments, Matrix4, Mesh, MeshBasicMaterial,Object3D, Quaternion, SphereGeometry, Vector3 } from 'three';
|
2
2
|
import ThreeMeshUI, { Inline, Text } from "three-mesh-ui"
|
3
3
|
import { type Options } from 'three-mesh-ui/build/types/core/elements/MeshUIBaseElement.js';
|
4
4
|
|
@@ -57,7 +57,7 @@
|
|
57
57
|
element.position.z = position.z;
|
58
58
|
return element as LabelHandle;
|
59
59
|
}
|
60
|
-
|
60
|
+
|
61
61
|
static DrawRay(origin: Vec3, dir: Vec3, color: ColorRepresentation = defaultColor, duration: number = 0, depthTest: boolean = true) {
|
62
62
|
if (!Gizmos.enabled) return;
|
63
63
|
const obj = Internal.getLine(duration);
|
@@ -163,6 +163,38 @@
|
|
163
163
|
obj.material["wireframe"] = wireframe;
|
164
164
|
this.DrawLine(pt0, pt1, color, duration, depthTest);
|
165
165
|
}
|
166
|
+
|
167
|
+
/**
|
168
|
+
* Render a wireframe mesh in the scene. The mesh will be removed after the given duration (if duration is 0 it will be rendered for one frame).
|
169
|
+
* If a mesh object is provided then the mesh's matrixWorld and geometry will be used. Otherwise, the provided matrix and geometry will be used.
|
170
|
+
* @param options the options for the wire mesh
|
171
|
+
* @param options.duration the duration in seconds the mesh will be rendered. If 0 it will be rendered for one frame
|
172
|
+
* @param options.color the color of the wire mesh
|
173
|
+
* @param options.depthTest if true the wire mesh will be rendered with depth test
|
174
|
+
* @param options.mesh the mesh object to render (if it is provided the matrix and geometry will be used)
|
175
|
+
* @param options.matrix the matrix of the mesh to render
|
176
|
+
* @param options.geometry the geometry of the mesh to render
|
177
|
+
* @example
|
178
|
+
* ```typescript
|
179
|
+
* Gizmos.DrawWireMesh({ duration: 1, color: 0xff0000, mesh: myMesh });
|
180
|
+
* ```
|
181
|
+
*/
|
182
|
+
static DrawWireMesh(options: { duration?: number, color?: ColorRepresentation, depthTest?: boolean } & ({ mesh: Mesh } | { matrix: Matrix4, geometry: BufferGeometry })) {
|
183
|
+
const mesh = Internal.getMesh(options.duration ?? 0);
|
184
|
+
if ("mesh" in options) {
|
185
|
+
mesh.geometry = options.mesh.geometry;
|
186
|
+
mesh.matrix.copy(options.mesh.matrixWorld);
|
187
|
+
}
|
188
|
+
else {
|
189
|
+
mesh.geometry = options.geometry;
|
190
|
+
mesh.matrix.copy(options.matrix);
|
191
|
+
}
|
192
|
+
mesh.matrixAutoUpdate = false;
|
193
|
+
mesh.matrixWorldAutoUpdate = false;
|
194
|
+
mesh.material["color"].set(options.color ?? defaultColor);
|
195
|
+
mesh.material["depthTest"] = options.depthTest ?? true;
|
196
|
+
mesh.material["wireframe"] = true;
|
197
|
+
}
|
166
198
|
}
|
167
199
|
|
168
200
|
const box: BoxGeometry = new BoxGeometry(1, 1, 1);
|
@@ -291,10 +323,21 @@
|
|
291
323
|
return arrowHead;
|
292
324
|
}
|
293
325
|
|
326
|
+
static getMesh(duration: number): Mesh {
|
327
|
+
let mesh = this.mesh.pop();
|
328
|
+
if (!mesh) {
|
329
|
+
mesh = new Mesh();
|
330
|
+
mesh.material = new MeshBasicMaterial();
|
331
|
+
}
|
332
|
+
this.registerTimedObject(Context.Current, mesh, duration, this.mesh);
|
333
|
+
return mesh;
|
334
|
+
}
|
335
|
+
|
294
336
|
private static linesCache: Array<Line> = [];
|
295
337
|
private static spheresCache: Mesh[] = [];
|
296
338
|
private static boxesCache: Mesh[] = [];
|
297
339
|
private static arrowHeadsCache: Mesh[] = [];
|
340
|
+
private static mesh: Mesh[] = [];
|
298
341
|
private static textLabelCache: Array<Text> = [];
|
299
342
|
|
300
343
|
private static registerTimedObject(context: Context, object: Object3D, duration: number, cache: Array<Object3D>) {
|
@@ -1,10 +1,9 @@
|
|
1
|
-
import { LODsManager as _LODsManager, NEEDLE_progressive,NEEDLE_progressive_mesh_model, NEEDLE_progressive_plugin } from "@needle-tools/gltf-progressive";
|
1
|
+
import { LODsManager as _LODsManager, NEEDLE_progressive, NEEDLE_progressive_mesh_model, NEEDLE_progressive_plugin } from "@needle-tools/gltf-progressive";
|
2
2
|
import { Box3, BufferGeometry, Camera, Mesh, PerspectiveCamera, Scene, Sphere, Vector3, WebGLRenderer } from "three";
|
3
3
|
|
4
4
|
import { findResourceUsers } from "./engine_assetdatabase.js";
|
5
5
|
import type { Context } from "./engine_context.js";
|
6
6
|
import { Gizmos } from "./engine_gizmos.js";
|
7
|
-
import { getRaycastMesh, setRaycastMesh } from "./engine_physics.js";
|
8
7
|
import { getTempVector } from "./engine_three_utils.js";
|
9
8
|
import { IGameObject } from "./engine_types.js";
|
10
9
|
import { getParam } from "./engine_utils.js";
|
@@ -21,6 +20,20 @@
|
|
21
20
|
readonly context: Context;
|
22
21
|
private _lodsManager?: _LODsManager;
|
23
22
|
|
23
|
+
/**
|
24
|
+
* The target triangle density is the desired max amount of triangles on screen when the mesh is filling the screen.
|
25
|
+
* @default 200_000
|
26
|
+
*/
|
27
|
+
get targetTriangleDensity() {
|
28
|
+
return this._lodsManager?.targetTriangleDensity ?? -1;
|
29
|
+
}
|
30
|
+
set targetTriangleDensity(value: number) {
|
31
|
+
if (!this._lodsManager) {
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
this._lodsManager.targetTriangleDensity = value;
|
35
|
+
}
|
36
|
+
|
24
37
|
constructor(context: Context) {
|
25
38
|
this.context = context;
|
26
39
|
}
|
@@ -28,41 +41,17 @@
|
|
28
41
|
/** @internal */
|
29
42
|
setRenderer(renderer: WebGLRenderer) {
|
30
43
|
this._lodsManager?.disable();
|
31
|
-
|
32
|
-
|
44
|
+
_LODsManager.removePlugin(this);
|
45
|
+
_LODsManager.addPlugin(this);
|
46
|
+
_LODsManager.debugDrawLine = Gizmos.DrawLine;
|
47
|
+
this._lodsManager = _LODsManager.get(renderer);
|
33
48
|
this._lodsManager.enable();
|
34
|
-
_LODsManager.debugDrawLine = Gizmos.DrawLine;
|
35
49
|
}
|
36
50
|
|
37
|
-
/** @internal */
|
38
|
-
onBeforeGetLODMesh(mesh: Mesh): void {
|
39
|
-
if (!getRaycastMesh(mesh)) {
|
40
|
-
setRaycastMesh(mesh, mesh.geometry as BufferGeometry);
|
41
|
-
}
|
42
|
-
}
|
43
51
|
|
44
52
|
/** @internal */
|
45
|
-
onRegisteredNewMesh(mesh: Mesh, _ext: NEEDLE_progressive_mesh_model): void {
|
46
|
-
const geometry = mesh.geometry as BufferGeometry;
|
47
|
-
if (geometry) {
|
48
|
-
geometry["needle:raycast-mesh"] = true;
|
49
|
-
if (!getRaycastMesh(mesh)) {
|
50
|
-
if (debug) console.log("Set raycast mesh", mesh.name, mesh.uuid, geometry);
|
51
|
-
setRaycastMesh(mesh, geometry);
|
52
|
-
findResourceUsers(geometry, true).forEach(user => {
|
53
|
-
if (user instanceof Mesh) {
|
54
|
-
setRaycastMesh(user, geometry);
|
55
|
-
}
|
56
|
-
});
|
57
|
-
}
|
58
|
-
}
|
59
|
-
}
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
/** @internal */
|
64
53
|
onAfterUpdatedLOD(_renderer: WebGLRenderer, _scene: Scene, camera: Camera, mesh: Mesh, level: number): void {
|
65
|
-
if(debug) this.onRenderDebug(camera, mesh, level);
|
54
|
+
if (debug) this.onRenderDebug(camera, mesh, level);
|
66
55
|
}
|
67
56
|
|
68
57
|
private onRenderDebug(camera: Camera, mesh: Mesh, level: number) {
|
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
1
|
+
import { getRaycastMesh } from '@needle-tools/gltf-progressive';
|
2
|
+
import { ArrayCamera, AxesHelper, Box3, BufferGeometry, Camera, type Intersection, Layers, Line, Mesh, Object3D, PerspectiveCamera, Ray, Raycaster, Sphere, Vector2, Vector3 } from 'three'
|
2
3
|
|
3
4
|
import { Gizmos } from './engine_gizmos.js';
|
4
5
|
import { Context } from './engine_setup.js';
|
@@ -10,25 +11,6 @@
|
|
10
11
|
const debugPhysics = getParam("debugphysics");
|
11
12
|
const layerMaskHelper: Layers = new Layers();
|
12
13
|
|
13
|
-
export function getRaycastMesh(obj: Object3D) {
|
14
|
-
if (obj.userData?.["needle:raycast-mesh"] instanceof BufferGeometry) {
|
15
|
-
return obj.userData["needle:raycast-mesh"];
|
16
|
-
}
|
17
|
-
if (debugPhysics) {
|
18
|
-
if (!obj["needle:warned about missing raycast mesh"]) {
|
19
|
-
obj["needle:warned about missing raycast mesh"] = true;
|
20
|
-
console.warn("No raycast mesh found for object: " + obj.name);
|
21
|
-
}
|
22
|
-
}
|
23
|
-
return null;
|
24
|
-
}
|
25
|
-
export function setRaycastMesh(obj: Object3D, geom: BufferGeometry) {
|
26
|
-
if (obj.type === "Mesh" || obj.type === "SkinnedMesh") {
|
27
|
-
if (!obj.userData) obj.userData = {};
|
28
|
-
obj.userData["needle:raycast-mesh"] = geom;
|
29
|
-
if(debugPhysics) delete obj["needle:warned about missing raycast mesh"];
|
30
|
-
}
|
31
|
-
}
|
32
14
|
|
33
15
|
export declare type RaycastTestObjectReturnType = void | boolean | "continue in children";
|
34
16
|
export declare type RaycastTestObjectCallback = (obj: Object3D) => RaycastTestObjectReturnType;
|
@@ -328,6 +310,7 @@
|
|
328
310
|
const raycastMesh = getRaycastMesh(obj);
|
329
311
|
if (raycastMesh) mesh.geometry = raycastMesh;
|
330
312
|
raycaster.intersectObject(obj, false, results);
|
313
|
+
if (debugPhysics) Gizmos.DrawWireMesh({ mesh: obj as Mesh, depthTest: false, duration: .1 })
|
331
314
|
mesh.geometry = geometry;
|
332
315
|
}
|
333
316
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Object3D } from "three";
|
2
2
|
|
3
|
-
import { USDObject, USDZExporterContext } from "./ThreeUSDZExporter.js";
|
3
|
+
import { USDObject, USDWriter, USDZExporterContext } from "./ThreeUSDZExporter.js";
|
4
4
|
|
5
5
|
/**
|
6
6
|
* Interface for USDZ Exporter Extensions used by {@link USDZExporter}
|
@@ -14,12 +14,12 @@
|
|
14
14
|
/**
|
15
15
|
* Called before the document is built
|
16
16
|
*/
|
17
|
-
onBeforeBuildDocument?(context);
|
17
|
+
onBeforeBuildDocument?(context: USDZExporterContext);
|
18
18
|
/**
|
19
19
|
* Called after the document is built
|
20
20
|
*/
|
21
|
-
onAfterBuildDocument?(context);
|
21
|
+
onAfterBuildDocument?(context: USDZExporterContext);
|
22
22
|
onExportObject?(object: Object3D, model: USDObject, context: USDZExporterContext);
|
23
|
-
onAfterSerialize?(context);
|
24
|
-
onAfterHierarchy?(context, writer:
|
23
|
+
onAfterSerialize?(context: USDZExporterContext);
|
24
|
+
onAfterHierarchy?(context: USDZExporterContext, writer: USDWriter);
|
25
25
|
}
|
@@ -111,11 +111,11 @@
|
|
111
111
|
if (box) {
|
112
112
|
writer.appendLine(`token shapeType = "Box"`);
|
113
113
|
writer.appendLine(`float3 extent = (${box.max.x - box.min.x}, ${box.max.y - box.min.y}, ${box.max.z - box.min.z})`);
|
114
|
-
console.log("[USDZ]
|
114
|
+
console.log("[USDZ] Only Box, Sphere, and Capsule colliders are supported in visionOS/iOS. MeshCollider will be exported as Box", colliderSource);
|
115
115
|
}
|
116
116
|
}
|
117
117
|
else {
|
118
|
-
console.warn("[USDZ]
|
118
|
+
console.warn("[USDZ] Only Box, Sphere, and Capsule colliders are supported in visionOS/iOS. Ignoring collider:", colliderSource)
|
119
119
|
}
|
120
120
|
|
121
121
|
writer.beginBlock(`def RealityKitStruct "pose"`, "{", true );
|
@@ -1,19 +1,16 @@
|
|
1
|
-
import {
|
1
|
+
import { getRaycastMesh } from "@needle-tools/gltf-progressive";
|
2
|
+
import { AxesHelper, Material, Mesh, Object3D, SkinnedMesh, Texture, Vector4 } from "three";
|
2
3
|
|
3
4
|
import { showBalloonWarning } from "../engine/debug/index.js";
|
4
5
|
import { getComponent, getOrAddComponent } from "../engine/engine_components.js";
|
5
|
-
import {
|
6
|
-
import {
|
7
|
-
import { $instancingAutoUpdateBounds, $instancingRenderer, InstancingUtil, NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing.js";
|
8
|
-
import { onStart } from "../engine/engine_lifecycle_api.js";
|
6
|
+
import { Gizmos } from "../engine/engine_gizmos.js";
|
7
|
+
import { InstancingUtil, NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing.js";
|
9
8
|
import { isLocalNetwork } from "../engine/engine_networking_utils.js";
|
10
|
-
import { getRaycastMesh } from "../engine/engine_physics.js";
|
11
9
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
12
|
-
import {
|
13
|
-
import { getTempVector
|
14
|
-
import type {
|
15
|
-
import { getParam
|
16
|
-
import { NEEDLE_progressive, ProgressiveMaterialTextureLoadingResult } from "../engine/extensions/NEEDLE_progressive.js";
|
10
|
+
import { FrameEvent } from "../engine/engine_setup.js";
|
11
|
+
import { getTempVector } from "../engine/engine_three_utils.js";
|
12
|
+
import type { IRenderer, ISharedMaterials } from "../engine/engine_types.js";
|
13
|
+
import { getParam } from "../engine/engine_utils.js";
|
17
14
|
import { NEEDLE_render_objects } from "../engine/extensions/NEEDLE_render_objects.js";
|
18
15
|
import { setCustomVisibility } from "../engine/js-extensions/Layers.js";
|
19
16
|
import { Behaviour, GameObject } from "./Component.js";
|
@@ -95,7 +95,10 @@
|
|
95
95
|
|
96
96
|
uuid: string;
|
97
97
|
name: string;
|
98
|
-
|
98
|
+
/** If no type is provided, type is chosen automatically (Xform or Mesh) */
|
99
|
+
type?: string;
|
100
|
+
/** MaterialBindingAPI and SkelBindingAPI are handled automatically, extra schemas can be added here */
|
101
|
+
extraSchemas: string[] = [];
|
99
102
|
displayName?: string;
|
100
103
|
visibility?: "inherited" | "invisible"; // defaults to "inherited" in USD
|
101
104
|
matrix: Matrix4;
|
@@ -227,14 +230,14 @@
|
|
227
230
|
|
228
231
|
}
|
229
232
|
|
230
|
-
addEventListener( evt, listener ) {
|
233
|
+
addEventListener( evt, listener: ( writer: USDWriter, context: USDZExporterContext ) => void ) {
|
231
234
|
|
232
235
|
if ( ! this._eventListeners[ evt ] ) this._eventListeners[ evt ] = [];
|
233
236
|
this._eventListeners[ evt ].push( listener );
|
234
237
|
|
235
238
|
}
|
236
239
|
|
237
|
-
removeEventListener( evt, listener ) {
|
240
|
+
removeEventListener( evt, listener: ( writer: USDWriter, context: USDZExporterContext ) => void ) {
|
238
241
|
|
239
242
|
if ( ! this._eventListeners[ evt ] ) return;
|
240
243
|
const index = this._eventListeners[ evt ].indexOf( listener );
|
@@ -812,6 +815,7 @@
|
|
812
815
|
Progress.end("export-usdz-resources");
|
813
816
|
|
814
817
|
const writer = new USDWriter();
|
818
|
+
const arAnchoringOptions = context.exporter.sceneAnchoringOptions.ar;
|
815
819
|
|
816
820
|
writer.beginBlock( `def Xform "${context.document.name}"` );
|
817
821
|
|
@@ -819,20 +823,21 @@
|
|
819
823
|
kind = "sceneLibrary"
|
820
824
|
)` );
|
821
825
|
|
822
|
-
writer.beginBlock( `def Xform "Scene" (
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
826
|
+
writer.beginBlock( `def Xform "Scene"`, '(', false);
|
827
|
+
writer.appendLine( `apiSchemas = ["Preliminary_AnchoringAPI"]` );
|
828
|
+
writer.appendLine( `customData = {`);
|
829
|
+
writer.appendLine( ` bool preliminary_collidesWithEnvironment = 0` );
|
830
|
+
writer.appendLine( ` string sceneName = "Scene"`);
|
831
|
+
writer.appendLine( `}` );
|
832
|
+
writer.appendLine( `sceneName = "Scene"` );
|
833
|
+
writer.closeBlock( ')' );
|
834
|
+
writer.beginBlock();
|
830
835
|
|
831
|
-
writer.appendLine( `token preliminary:anchoring:type = "${
|
832
|
-
if (
|
833
|
-
writer.appendLine( `token preliminary:planeAnchoring:alignment = "${
|
836
|
+
writer.appendLine( `token preliminary:anchoring:type = "${arAnchoringOptions.anchoring.type}"` );
|
837
|
+
if (arAnchoringOptions.anchoring.type === 'plane')
|
838
|
+
writer.appendLine( `token preliminary:planeAnchoring:alignment = "${arAnchoringOptions.planeAnchoring.alignment}"` );
|
834
839
|
// bit hacky as we don't have a callback here yet. Relies on the fact that the image is named identical in the ImageTracking extension.
|
835
|
-
if (
|
840
|
+
if (arAnchoringOptions.anchoring.type === 'image')
|
836
841
|
writer.appendLine( `rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>` );
|
837
842
|
writer.appendLine();
|
838
843
|
|
@@ -1228,8 +1233,7 @@
|
|
1228
1233
|
|
1229
1234
|
const isSkinnedMesh = geometry && geometry.isBufferGeometry && geometry.attributes.skinIndex !== undefined && geometry.attributes.skinIndex.count > 0;
|
1230
1235
|
const objType = isSkinnedMesh ? 'SkelRoot' : 'Xform';
|
1231
|
-
const
|
1232
|
-
|
1236
|
+
const _apiSchemas = new Array<string>();
|
1233
1237
|
writer.appendLine();
|
1234
1238
|
if ( geometry ) {
|
1235
1239
|
writer.beginBlock( `def ${objType} "${name}"`, "(", false );
|
@@ -1239,7 +1243,9 @@
|
|
1239
1243
|
writer.appendLine(`prepend references = @./geometries/${getGeometryName(geometry, name)}.usda@</Geometry_doubleSided>`);
|
1240
1244
|
else
|
1241
1245
|
writer.appendLine(`prepend references = @./geometries/${getGeometryName(geometry, name)}.usda@</Geometry>`);
|
1242
|
-
|
1246
|
+
_apiSchemas.push("MaterialBindingAPI");
|
1247
|
+
if (isSkinnedMesh)
|
1248
|
+
_apiSchemas.push("SkelBindingAPI");
|
1243
1249
|
}
|
1244
1250
|
else if ( camera )
|
1245
1251
|
writer.beginBlock( `def Camera "${name}"`, "(", false );
|
@@ -1251,6 +1257,10 @@
|
|
1251
1257
|
if (model.displayName)
|
1252
1258
|
writer.appendLine(`displayName = "${model.displayName}"`);
|
1253
1259
|
if (model.type === undefined) {
|
1260
|
+
if (model.extraSchemas?.length)
|
1261
|
+
_apiSchemas.push(...model.extraSchemas);
|
1262
|
+
if (_apiSchemas.length)
|
1263
|
+
writer.appendLine(`prepend apiSchemas = [${_apiSchemas.map(s => `"${s}"`).join(', ')}]`);
|
1254
1264
|
writer.closeBlock( ")" );
|
1255
1265
|
writer.beginBlock();
|
1256
1266
|
}
|
@@ -578,21 +578,17 @@
|
|
578
578
|
private _rootRotationBeforeExport: Quaternion = new Quaternion();
|
579
579
|
private _rootScaleBeforeExport: Vector3 = new Vector3();
|
580
580
|
|
581
|
-
|
582
|
-
if (!this.objectToExport) return;
|
581
|
+
getARScaleAndTarget(): { scale: number, _invertForward: boolean, target: Object3D } {
|
582
|
+
if (!this.objectToExport) return { scale: 1, _invertForward: false, target: this.gameObject };
|
583
583
|
|
584
|
-
// first check if the sessionroot is in the parent hierarchy
|
585
|
-
// if that's the case we apply the scale to the object being exported
|
586
584
|
let sessionRoot = GameObject.getComponentInParent(this.objectToExport, WebARSessionRoot);
|
587
585
|
const hasSessionRootInParentHierarchy = sessionRoot !== null && sessionRoot !== undefined;
|
588
|
-
// if it's not in the parent hierarchy BUT in the child hierarchy we apply it to the sessionRoot object itself
|
589
|
-
// that's the case when no objectToExport is explictly assigned and the whole scene is being exported
|
590
586
|
if(!sessionRoot) sessionRoot = GameObject.getComponentInChildren(this.objectToExport, WebARSessionRoot);
|
591
587
|
|
592
588
|
if (debug) console.log("applyWebARSessionRoot", sessionRoot, sessionRoot?.arScale);
|
593
589
|
|
594
590
|
let arScale = 1;
|
595
|
-
let
|
591
|
+
let _invertForward = false;
|
596
592
|
const target = hasSessionRootInParentHierarchy || !sessionRoot ? this.objectToExport : sessionRoot.gameObject;
|
597
593
|
|
598
594
|
if (!sessionRoot) {
|
@@ -601,12 +597,19 @@
|
|
601
597
|
}
|
602
598
|
else {
|
603
599
|
arScale = sessionRoot.arScale;
|
604
|
-
|
600
|
+
_invertForward = sessionRoot.invertForward;
|
605
601
|
}
|
606
|
-
|
607
|
-
// either apply the scale to the object being exported or to the sessionRoot object itself
|
602
|
+
|
608
603
|
const scale = 1 / arScale;
|
609
604
|
|
605
|
+
return { scale, _invertForward, target };
|
606
|
+
}
|
607
|
+
|
608
|
+
private applyWebARSessionRoot() {
|
609
|
+
if (!this.objectToExport) return;
|
610
|
+
|
611
|
+
const { scale, _invertForward, target } = this.getARScaleAndTarget();
|
612
|
+
|
610
613
|
this._rootSessionRootWasAppliedTo = target;
|
611
614
|
this._rootPositionBeforeExport.copy(target.position);
|
612
615
|
this._rootRotationBeforeExport.copy(target.quaternion);
|
@@ -614,7 +617,7 @@
|
|
614
617
|
|
615
618
|
target.scale.multiplyScalar(scale);
|
616
619
|
// legacy, should likely be deleted
|
617
|
-
if (
|
620
|
+
if (_invertForward) {
|
618
621
|
target.matrix.multiply(USDZExporter.invertForwardMatrix);
|
619
622
|
}
|
620
623
|
// udate childs as well
|
@@ -1,11 +1,13 @@
|
|
1
1
|
import { Matrix4, Object3D, Quaternion, Vector3 } from "three";
|
2
|
+
import { Object3DEventMap } from "three";
|
2
3
|
|
3
|
-
import { showBalloonWarning } from "../../engine/debug/index.js";
|
4
|
+
import { isDevEnvironment, showBalloonWarning } from "../../engine/debug/index.js";
|
4
5
|
import { AssetReference } from "../../engine/engine_addressables.js";
|
5
6
|
import { serializable } from "../../engine/engine_serialization.js";
|
6
7
|
import { CircularBuffer, getParam } from "../../engine/engine_utils.js";
|
7
8
|
import { type NeedleXREventArgs, NeedleXRSession } from "../../engine/xr/api.js";
|
8
|
-
import {
|
9
|
+
import { IUSDExporterExtension } from "../../engine-components/export/usdz/Extension.js";
|
10
|
+
import { imageToCanvas, USDObject, USDWriter, USDZExporterContext } from "../../engine-components/export/usdz/ThreeUSDZExporter.js";
|
9
11
|
import { USDZExporter } from "../../engine-components/export/usdz/USDZExporter.js";
|
10
12
|
import { Behaviour, GameObject } from "../Component.js";
|
11
13
|
import { Renderer } from "../Renderer.js";
|
@@ -143,7 +145,7 @@
|
|
143
145
|
hideWhenTrackingIsLost: boolean = true;
|
144
146
|
}
|
145
147
|
|
146
|
-
class ImageTrackingExtension {
|
148
|
+
class ImageTrackingExtension implements IUSDExporterExtension {
|
147
149
|
|
148
150
|
get extensionName() { return "image-tracking"; }
|
149
151
|
|
@@ -159,14 +161,62 @@
|
|
159
161
|
|
160
162
|
onAfterHierarchy(_context: USDZExporterContext, writer: USDWriter) {
|
161
163
|
writer.beginBlock(`def Preliminary_ReferenceImage "AnchoringReferenceImage"`);
|
162
|
-
writer.appendLine(`uniform asset image = @
|
164
|
+
writer.appendLine(`uniform asset image = @image_tracking/` + this.filename + `@`);
|
163
165
|
writer.appendLine(`uniform double physicalWidth = ` + (this.widthInMeters * 100).toFixed(8));
|
164
166
|
writer.closeBlock();
|
165
167
|
}
|
166
168
|
|
169
|
+
onBeforeBuildDocument(_context: USDZExporterContext) {
|
170
|
+
const imageTracking = GameObject.findObjectOfType(WebXRImageTracking);
|
171
|
+
if (!imageTracking || !imageTracking.trackedImages) return;
|
172
|
+
|
173
|
+
// Warn if more than one tracked image is used for USDZ; that's not supported at the moment.
|
174
|
+
if (imageTracking.trackedImages.length > 1)
|
175
|
+
{
|
176
|
+
if (isDevEnvironment()) showBalloonWarning("USDZ: Only one tracked image is supported.");
|
177
|
+
console.warn("USDZ: Only one tracked image is supported.");
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
167
181
|
onAfterSerialize(context: USDZExporterContext) {
|
168
|
-
context.files['
|
182
|
+
context.files['image_tracking/' + this.filename] = this.imageData;
|
169
183
|
}
|
184
|
+
|
185
|
+
onExportObject(object: Object3D<Object3DEventMap>, model: USDObject, _context: USDZExporterContext) {
|
186
|
+
const imageTracking = GameObject.findObjectOfType(WebXRImageTracking);
|
187
|
+
if (!imageTracking || !imageTracking.trackedImages) return;
|
188
|
+
|
189
|
+
for (const trackedImage of imageTracking.trackedImages) {
|
190
|
+
if (trackedImage.object?.asset === object) {
|
191
|
+
const exporter = GameObject.findObjectOfType(USDZExporter);
|
192
|
+
if (!exporter) continue;
|
193
|
+
|
194
|
+
const { scale } = exporter.getARScaleAndTarget();
|
195
|
+
|
196
|
+
// We have to reset the image tracking object's position and rotation, because QuickLook applies them.
|
197
|
+
// On Android WebXR they're replaced by the tracked data.
|
198
|
+
model.matrix = object.matrixWorld.clone()
|
199
|
+
.invert()
|
200
|
+
.multiply(new Matrix4().makeRotationY(Math.PI))
|
201
|
+
// apply session root scale again after undoing the world transformation
|
202
|
+
// TODO check if we actually do that in WebXR image tracking
|
203
|
+
.scale(new Vector3(scale, scale, scale));
|
204
|
+
|
205
|
+
// Unfortunately looks like Apple's docs are incomplete:
|
206
|
+
// https://developer.apple.com/documentation/realitykit/preliminary_anchoringapi#Nest-and-Layer-Anchorable-Prims
|
207
|
+
// In practice, it seems that nesting is not allowed – no image tracking will be applied to nested objects.
|
208
|
+
// Thus, we can't have separate transforms for "regularly placing content" and "placing content with an image marker".
|
209
|
+
// model.extraSchemas.push("Preliminary_AnchoringAPI");
|
210
|
+
model.addEventListener("serialize", (_writer: USDWriter, _context: USDZExporterContext) => {
|
211
|
+
// writer.appendLine( `token preliminary:anchoring:type = "image"` );
|
212
|
+
// writer.appendLine( `rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>` );
|
213
|
+
});
|
214
|
+
|
215
|
+
// We can only apply this to the first tracked image, more are not supported by QuickLook.
|
216
|
+
break;
|
217
|
+
}
|
218
|
+
}
|
219
|
+
}
|
170
220
|
}
|
171
221
|
|
172
222
|
export class WebXRImageTracking extends Behaviour {
|