@@ -1,7 +1,10 @@
|
|
1
1
|
import { TypeStore } from "./../engine_typestore"
|
2
|
-
|
2
|
+
|
3
3
|
// Import types
|
4
4
|
import { __Ignore } from "../../engine-components/codegen/components";
|
5
|
+
import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
|
6
|
+
import { ActionCollection } from "../../engine-components/export/usdz/extensions/behavior/Actions";
|
7
|
+
import { ActionModel } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
|
5
8
|
import { AlignmentConstraint } from "../../engine-components/AlignmentConstraint";
|
6
9
|
import { Animation } from "../../engine-components/Animation";
|
7
10
|
import { AnimationCurve } from "../../engine-components/AnimationCurve";
|
@@ -26,6 +29,8 @@
|
|
26
29
|
import { AxesHelper } from "../../engine-components/AxesHelper";
|
27
30
|
import { BaseUIComponent } from "../../engine-components/ui/BaseUIComponent";
|
28
31
|
import { BasicIKConstraint } from "../../engine-components/BasicIKConstraint";
|
32
|
+
import { BehaviorExtension } from "../../engine-components/export/usdz/extensions/behavior/Behaviour";
|
33
|
+
import { BehaviorModel } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
|
29
34
|
import { Behaviour } from "../../engine-components/Component";
|
30
35
|
import { Bloom } from "../../engine-components/postprocessing/Effects/Bloom";
|
31
36
|
import { BoxCollider } from "../../engine-components/Collider";
|
@@ -37,6 +42,8 @@
|
|
37
42
|
import { Canvas } from "../../engine-components/ui/Canvas";
|
38
43
|
import { CanvasGroup } from "../../engine-components/ui/CanvasGroup";
|
39
44
|
import { CapsuleCollider } from "../../engine-components/Collider";
|
45
|
+
import { ChangeMaterialOnClick } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
|
46
|
+
import { ChangeTransformOnClick } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
|
40
47
|
import { CharacterController } from "../../engine-components/CharacterController";
|
41
48
|
import { CharacterControllerInput } from "../../engine-components/CharacterController";
|
42
49
|
import { ChromaticAberration } from "../../engine-components/postprocessing/Effects/ChromaticAberration";
|
@@ -50,10 +57,12 @@
|
|
50
57
|
import { DeleteBox } from "../../engine-components/DeleteBox";
|
51
58
|
import { DepthOfField } from "../../engine-components/postprocessing/Effects/DepthOfField";
|
52
59
|
import { DeviceFlag } from "../../engine-components/DeviceFlag";
|
60
|
+
import { DocumentExtension } from "../../engine-components/export/usdz/extensions/DocumentExtension";
|
53
61
|
import { DragControls } from "../../engine-components/DragControls";
|
54
62
|
import { DropListener } from "../../engine-components/DropListener";
|
55
63
|
import { Duplicatable } from "../../engine-components/Duplicatable";
|
56
64
|
import { EmissionModule } from "../../engine-components/ParticleSystemModules";
|
65
|
+
import { EmphasizeOnClick } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
|
57
66
|
import { EventList } from "../../engine-components/EventList";
|
58
67
|
import { EventListEvent } from "../../engine-components/EventList";
|
59
68
|
import { EventSystem } from "../../engine-components/ui/EventSystem";
|
@@ -70,13 +79,14 @@
|
|
70
79
|
import { GridHelper } from "../../engine-components/GridHelper";
|
71
80
|
import { GridLayoutGroup } from "../../engine-components/ui/Layout";
|
72
81
|
import { GroundProjectedEnv } from "../../engine-components/GroundProjection";
|
82
|
+
import { GroupActionModel } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
|
83
|
+
import { HideOnStart } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
|
73
84
|
import { HingeJoint } from "../../engine-components/Joints";
|
74
85
|
import { HorizontalLayoutGroup } from "../../engine-components/ui/Layout";
|
75
86
|
import { Image } from "../../engine-components/ui/Image";
|
76
87
|
import { InheritVelocityModule } from "../../engine-components/ParticleSystemModules";
|
77
88
|
import { InputField } from "../../engine-components/ui/InputField";
|
78
89
|
import { Interactable } from "../../engine-components/Interactable";
|
79
|
-
import { LayoutGroup } from "../../engine-components/ui/Layout";
|
80
90
|
import { Light } from "../../engine-components/Light";
|
81
91
|
import { LimitVelocityOverLifetimeModule } from "../../engine-components/ParticleSystemModules";
|
82
92
|
import { LODGroup } from "../../engine-components/LODGroup";
|
@@ -98,17 +108,21 @@
|
|
98
108
|
import { OpenURL } from "../../engine-components/utils/OpenURL";
|
99
109
|
import { OrbitControls } from "../../engine-components/OrbitControls";
|
100
110
|
import { Outline } from "../../engine-components/ui/Outline";
|
111
|
+
import { Padding } from "../../engine-components/ui/Layout";
|
101
112
|
import { ParticleBurst } from "../../engine-components/ParticleSystemModules";
|
102
113
|
import { ParticleSubEmitter } from "../../engine-components/ParticleSystemSubEmitter";
|
103
114
|
import { ParticleSystem } from "../../engine-components/ParticleSystem";
|
104
115
|
import { ParticleSystemRenderer } from "../../engine-components/ParticleSystem";
|
105
116
|
import { PixelationEffect } from "../../engine-components/postprocessing/Effects/Pixelation";
|
106
117
|
import { PlayableDirector } from "../../engine-components/timeline/PlayableDirector";
|
118
|
+
import { PlayAnimationOnClick } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
|
107
119
|
import { PlayerColor } from "../../engine-components/PlayerColor";
|
108
120
|
import { PlayerState } from "../../engine-components-experimental/networking/PlayerSync";
|
109
121
|
import { PlayerSync } from "../../engine-components-experimental/networking/PlayerSync";
|
110
122
|
import { PointerEventData } from "../../engine-components/ui/PointerEvents";
|
111
123
|
import { PostProcessingHandler } from "../../engine-components/postprocessing/PostProcessingHandler";
|
124
|
+
import { PreliminaryAction } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
|
125
|
+
import { PreliminaryTrigger } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
|
112
126
|
import { PresentationMode } from "../../engine-components-experimental/Presentation";
|
113
127
|
import { QuickLookOverlay } from "../../engine-components/export/usdz/USDZExporter";
|
114
128
|
import { RawImage } from "../../engine-components/ui/Image";
|
@@ -127,6 +141,7 @@
|
|
127
141
|
import { SceneSwitcher } from "../../engine-components/SceneSwitcher";
|
128
142
|
import { ScreenCapture } from "../../engine-components/ScreenCapture";
|
129
143
|
import { ScreenSpaceAmbientOcclusion } from "../../engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusion";
|
144
|
+
import { SetActiveOnClick } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
|
130
145
|
import { ShadowCatcher } from "../../engine-components/ShadowCatcher";
|
131
146
|
import { ShapeModule } from "../../engine-components/ParticleSystemModules";
|
132
147
|
import { SignalAsset } from "../../engine-components/timeline/SignalAsset";
|
@@ -151,24 +166,33 @@
|
|
151
166
|
import { SyncedCamera } from "../../engine-components/SyncedCamera";
|
152
167
|
import { SyncedRoom } from "../../engine-components/SyncedRoom";
|
153
168
|
import { SyncedTransform } from "../../engine-components/SyncedTransform";
|
169
|
+
import { TapGestureTrigger } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
|
154
170
|
import { TeleportTarget } from "../../engine-components/webxr/WebXRController";
|
155
171
|
import { TestRunner } from "../../engine-components/TestRunner";
|
156
172
|
import { TestSimulateUserData } from "../../engine-components/TestRunner";
|
157
173
|
import { Text } from "../../engine-components/ui/Text";
|
174
|
+
import { TextBuilder } from "../../engine-components/export/usdz/extensions/USDZText";
|
175
|
+
import { TextExtension } from "../../engine-components/export/usdz/extensions/USDZText";
|
158
176
|
import { TextureSheetAnimationModule } from "../../engine-components/ParticleSystemModules";
|
159
177
|
import { TiltShiftEffect } from "../../engine-components/postprocessing/Effects/TiltShiftEffect";
|
160
178
|
import { ToneMapping } from "../../engine-components/postprocessing/Effects/Tonemapping";
|
161
179
|
import { TrailModule } from "../../engine-components/ParticleSystemModules";
|
162
180
|
import { TransformData } from "../../engine-components/export/usdz/extensions/Animation";
|
163
181
|
import { TransformGizmo } from "../../engine-components/TransformGizmo";
|
182
|
+
import { TriggerBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
|
183
|
+
import { TriggerModel } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
|
164
184
|
import { UIRaycastUtils } from "../../engine-components/ui/RaycastUtils";
|
165
185
|
import { UIRootComponent } from "../../engine-components/ui/BaseUIComponent";
|
166
186
|
import { UsageMarker } from "../../engine-components/Interactable";
|
187
|
+
import { USDZBehaviours } from "../../engine-components/export/usdz/extensions/behavior/Behaviour";
|
167
188
|
import { USDZExporter } from "../../engine-components/export/usdz/USDZExporter";
|
189
|
+
import { USDZText } from "../../engine-components/export/usdz/extensions/USDZText";
|
190
|
+
import { VariantAction } from "../../engine-components/export/usdz/extensions/behavior/Actions";
|
168
191
|
import { VelocityOverLifetimeModule } from "../../engine-components/ParticleSystemModules";
|
169
192
|
import { VerticalLayoutGroup } from "../../engine-components/ui/Layout";
|
170
193
|
import { VideoPlayer } from "../../engine-components/VideoPlayer";
|
171
194
|
import { Vignette } from "../../engine-components/postprocessing/Effects/Vignette";
|
195
|
+
import { VisibilityAction } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents";
|
172
196
|
import { Voip } from "../../engine-components/Voip";
|
173
197
|
import { Volume } from "../../engine-components/postprocessing/Volume";
|
174
198
|
import { VolumeParameter } from "../../engine-components/postprocessing/VolumeParameter";
|
@@ -190,9 +214,12 @@
|
|
190
214
|
import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering";
|
191
215
|
import { XRRig } from "../../engine-components/webxr/WebXRRig";
|
192
216
|
import { XRState } from "../../engine-components/XRFlag";
|
193
|
-
|
217
|
+
|
194
218
|
// Register types
|
195
219
|
TypeStore.add("__Ignore", __Ignore);
|
220
|
+
TypeStore.add("ActionBuilder", ActionBuilder);
|
221
|
+
TypeStore.add("ActionCollection", ActionCollection);
|
222
|
+
TypeStore.add("ActionModel", ActionModel);
|
196
223
|
TypeStore.add("AlignmentConstraint", AlignmentConstraint);
|
197
224
|
TypeStore.add("Animation", Animation);
|
198
225
|
TypeStore.add("AnimationCurve", AnimationCurve);
|
@@ -217,6 +244,8 @@
|
|
217
244
|
TypeStore.add("AxesHelper", AxesHelper);
|
218
245
|
TypeStore.add("BaseUIComponent", BaseUIComponent);
|
219
246
|
TypeStore.add("BasicIKConstraint", BasicIKConstraint);
|
247
|
+
TypeStore.add("BehaviorExtension", BehaviorExtension);
|
248
|
+
TypeStore.add("BehaviorModel", BehaviorModel);
|
220
249
|
TypeStore.add("Behaviour", Behaviour);
|
221
250
|
TypeStore.add("Bloom", Bloom);
|
222
251
|
TypeStore.add("BoxCollider", BoxCollider);
|
@@ -228,6 +257,8 @@
|
|
228
257
|
TypeStore.add("Canvas", Canvas);
|
229
258
|
TypeStore.add("CanvasGroup", CanvasGroup);
|
230
259
|
TypeStore.add("CapsuleCollider", CapsuleCollider);
|
260
|
+
TypeStore.add("ChangeMaterialOnClick", ChangeMaterialOnClick);
|
261
|
+
TypeStore.add("ChangeTransformOnClick", ChangeTransformOnClick);
|
231
262
|
TypeStore.add("CharacterController", CharacterController);
|
232
263
|
TypeStore.add("CharacterControllerInput", CharacterControllerInput);
|
233
264
|
TypeStore.add("ChromaticAberration", ChromaticAberration);
|
@@ -241,10 +272,12 @@
|
|
241
272
|
TypeStore.add("DeleteBox", DeleteBox);
|
242
273
|
TypeStore.add("DepthOfField", DepthOfField);
|
243
274
|
TypeStore.add("DeviceFlag", DeviceFlag);
|
275
|
+
TypeStore.add("DocumentExtension", DocumentExtension);
|
244
276
|
TypeStore.add("DragControls", DragControls);
|
245
277
|
TypeStore.add("DropListener", DropListener);
|
246
278
|
TypeStore.add("Duplicatable", Duplicatable);
|
247
279
|
TypeStore.add("EmissionModule", EmissionModule);
|
280
|
+
TypeStore.add("EmphasizeOnClick", EmphasizeOnClick);
|
248
281
|
TypeStore.add("EventList", EventList);
|
249
282
|
TypeStore.add("EventListEvent", EventListEvent);
|
250
283
|
TypeStore.add("EventSystem", EventSystem);
|
@@ -261,13 +294,14 @@
|
|
261
294
|
TypeStore.add("GridHelper", GridHelper);
|
262
295
|
TypeStore.add("GridLayoutGroup", GridLayoutGroup);
|
263
296
|
TypeStore.add("GroundProjectedEnv", GroundProjectedEnv);
|
297
|
+
TypeStore.add("GroupActionModel", GroupActionModel);
|
298
|
+
TypeStore.add("HideOnStart", HideOnStart);
|
264
299
|
TypeStore.add("HingeJoint", HingeJoint);
|
265
300
|
TypeStore.add("HorizontalLayoutGroup", HorizontalLayoutGroup);
|
266
301
|
TypeStore.add("Image", Image);
|
267
302
|
TypeStore.add("InheritVelocityModule", InheritVelocityModule);
|
268
303
|
TypeStore.add("InputField", InputField);
|
269
304
|
TypeStore.add("Interactable", Interactable);
|
270
|
-
TypeStore.add("LayoutGroup", LayoutGroup);
|
271
305
|
TypeStore.add("Light", Light);
|
272
306
|
TypeStore.add("LimitVelocityOverLifetimeModule", LimitVelocityOverLifetimeModule);
|
273
307
|
TypeStore.add("LODGroup", LODGroup);
|
@@ -289,17 +323,21 @@
|
|
289
323
|
TypeStore.add("OpenURL", OpenURL);
|
290
324
|
TypeStore.add("OrbitControls", OrbitControls);
|
291
325
|
TypeStore.add("Outline", Outline);
|
326
|
+
TypeStore.add("Padding", Padding);
|
292
327
|
TypeStore.add("ParticleBurst", ParticleBurst);
|
293
328
|
TypeStore.add("ParticleSubEmitter", ParticleSubEmitter);
|
294
329
|
TypeStore.add("ParticleSystem", ParticleSystem);
|
295
330
|
TypeStore.add("ParticleSystemRenderer", ParticleSystemRenderer);
|
296
331
|
TypeStore.add("PixelationEffect", PixelationEffect);
|
297
332
|
TypeStore.add("PlayableDirector", PlayableDirector);
|
333
|
+
TypeStore.add("PlayAnimationOnClick", PlayAnimationOnClick);
|
298
334
|
TypeStore.add("PlayerColor", PlayerColor);
|
299
335
|
TypeStore.add("PlayerState", PlayerState);
|
300
336
|
TypeStore.add("PlayerSync", PlayerSync);
|
301
337
|
TypeStore.add("PointerEventData", PointerEventData);
|
302
338
|
TypeStore.add("PostProcessingHandler", PostProcessingHandler);
|
339
|
+
TypeStore.add("PreliminaryAction", PreliminaryAction);
|
340
|
+
TypeStore.add("PreliminaryTrigger", PreliminaryTrigger);
|
303
341
|
TypeStore.add("PresentationMode", PresentationMode);
|
304
342
|
TypeStore.add("QuickLookOverlay", QuickLookOverlay);
|
305
343
|
TypeStore.add("RawImage", RawImage);
|
@@ -318,6 +356,7 @@
|
|
318
356
|
TypeStore.add("SceneSwitcher", SceneSwitcher);
|
319
357
|
TypeStore.add("ScreenCapture", ScreenCapture);
|
320
358
|
TypeStore.add("ScreenSpaceAmbientOcclusion", ScreenSpaceAmbientOcclusion);
|
359
|
+
TypeStore.add("SetActiveOnClick", SetActiveOnClick);
|
321
360
|
TypeStore.add("ShadowCatcher", ShadowCatcher);
|
322
361
|
TypeStore.add("ShapeModule", ShapeModule);
|
323
362
|
TypeStore.add("SignalAsset", SignalAsset);
|
@@ -342,24 +381,33 @@
|
|
342
381
|
TypeStore.add("SyncedCamera", SyncedCamera);
|
343
382
|
TypeStore.add("SyncedRoom", SyncedRoom);
|
344
383
|
TypeStore.add("SyncedTransform", SyncedTransform);
|
384
|
+
TypeStore.add("TapGestureTrigger", TapGestureTrigger);
|
345
385
|
TypeStore.add("TeleportTarget", TeleportTarget);
|
346
386
|
TypeStore.add("TestRunner", TestRunner);
|
347
387
|
TypeStore.add("TestSimulateUserData", TestSimulateUserData);
|
348
388
|
TypeStore.add("Text", Text);
|
389
|
+
TypeStore.add("TextBuilder", TextBuilder);
|
390
|
+
TypeStore.add("TextExtension", TextExtension);
|
349
391
|
TypeStore.add("TextureSheetAnimationModule", TextureSheetAnimationModule);
|
350
392
|
TypeStore.add("TiltShiftEffect", TiltShiftEffect);
|
351
393
|
TypeStore.add("ToneMapping", ToneMapping);
|
352
394
|
TypeStore.add("TrailModule", TrailModule);
|
353
395
|
TypeStore.add("TransformData", TransformData);
|
354
396
|
TypeStore.add("TransformGizmo", TransformGizmo);
|
397
|
+
TypeStore.add("TriggerBuilder", TriggerBuilder);
|
398
|
+
TypeStore.add("TriggerModel", TriggerModel);
|
355
399
|
TypeStore.add("UIRaycastUtils", UIRaycastUtils);
|
356
400
|
TypeStore.add("UIRootComponent", UIRootComponent);
|
357
401
|
TypeStore.add("UsageMarker", UsageMarker);
|
402
|
+
TypeStore.add("USDZBehaviours", USDZBehaviours);
|
358
403
|
TypeStore.add("USDZExporter", USDZExporter);
|
404
|
+
TypeStore.add("USDZText", USDZText);
|
405
|
+
TypeStore.add("VariantAction", VariantAction);
|
359
406
|
TypeStore.add("VelocityOverLifetimeModule", VelocityOverLifetimeModule);
|
360
407
|
TypeStore.add("VerticalLayoutGroup", VerticalLayoutGroup);
|
361
408
|
TypeStore.add("VideoPlayer", VideoPlayer);
|
362
409
|
TypeStore.add("Vignette", Vignette);
|
410
|
+
TypeStore.add("VisibilityAction", VisibilityAction);
|
363
411
|
TypeStore.add("Voip", Voip);
|
364
412
|
TypeStore.add("Volume", Volume);
|
365
413
|
TypeStore.add("VolumeParameter", VolumeParameter);
|
@@ -9,6 +9,7 @@
|
|
9
9
|
const __dirname = path.dirname(__filename);
|
10
10
|
|
11
11
|
const filesUsingHotReload = new Set();
|
12
|
+
let assetsDirectory = "";
|
12
13
|
|
13
14
|
export const needleReload = (command, config, userSettings) => {
|
14
15
|
if (command === "build") return;
|
@@ -23,8 +24,9 @@
|
|
23
24
|
if (res) config = res;
|
24
25
|
}
|
25
26
|
|
27
|
+
const projectConfig = tryLoadProjectConfig();
|
28
|
+
assetsDirectory = path.resolve(projectConfig?.assetsDirectory || "assets");
|
26
29
|
|
27
|
-
const projectConfig = tryLoadProjectConfig();
|
28
30
|
const buildDirectory = projectConfig?.buildDirectory?.length ? process.cwd().replaceAll("\\", "/") + "/" + projectConfig?.buildDirectory : "";
|
29
31
|
if (buildDirectory?.length) {
|
30
32
|
setTimeout(() => console.log("Build directory: ", buildDirectory), 100);
|
@@ -153,9 +155,18 @@
|
|
153
155
|
}
|
154
156
|
}
|
155
157
|
|
156
|
-
|
158
|
+
// these are known file types we export from integrations
|
159
|
+
const knownExportFileTypes = [ ".glb", ".gltf", ".bin", "exr", ".ktx2", ".mp3", ".ogg", ".mp4", ".webm" ];
|
160
|
+
if (!knownExportFileTypes.some((type) => file.endsWith(type)))
|
157
161
|
return;
|
158
162
|
|
163
|
+
// we only care about exports into "assets"
|
164
|
+
if (!path.resolve(file).startsWith(assetsDirectory))
|
165
|
+
return;
|
166
|
+
|
167
|
+
if (file.endsWith(".svelte") || file.endsWith(".vue") || file.endsWith(".ts") || file.endsWith(".js") || file.endsWith(".jsx") || file.endsWith(".tsx"))
|
168
|
+
return;
|
169
|
+
|
159
170
|
if (file.endsWith(lockFileName)) return;
|
160
171
|
let fileSize = "";
|
161
172
|
const isGlbOrGltfFile = file.endsWith(".glb") || file.endsWith(".bin");
|
@@ -176,6 +176,10 @@
|
|
176
176
|
return this.internalOnPlay(act, options);
|
177
177
|
}
|
178
178
|
}
|
179
|
+
if (!(clip instanceof AnimationClip)) {
|
180
|
+
console.warn("Clip is no AnimationClip", clip, "on object: " + this.name)
|
181
|
+
return;
|
182
|
+
}
|
179
183
|
const act = this.mixer.clipAction(clip);
|
180
184
|
this.actions.push(act);
|
181
185
|
return this.internalOnPlay(act, options);
|
@@ -1,17 +1,18 @@
|
|
1
1
|
import { GameObject } from "../../../Component";
|
2
2
|
import { getParam } from "../../../../engine/engine_utils";
|
3
|
-
import { Object3D, Color, Matrix4, MeshStandardMaterial, Vector3, Quaternion, Interpolant } from "three";
|
4
|
-
//@ts-ignore
|
5
|
-
import { USDZObject, buildMatrix } from "three/examples/jsm/exporters/USDZExporter"
|
6
|
-
import { IUSDZExporterExtension } from "../Extension";
|
7
3
|
|
4
|
+
import { USDObject, buildMatrix } from "../ThreeUSDZExporter";
|
5
|
+
import { IUSDExporterExtension } from "../Extension";
|
6
|
+
|
7
|
+
import { Object3D, Matrix4, Vector3, Quaternion, Interpolant, AnimationClip, KeyframeTrack } from "three";
|
8
|
+
|
8
9
|
const debug = getParam("debugusdzanimation");
|
9
10
|
|
10
11
|
export interface UsdzAnimation {
|
11
|
-
createAnimation(ext: AnimationExtension, model:
|
12
|
+
createAnimation(ext: AnimationExtension, model: USDObject, context);
|
12
13
|
}
|
13
14
|
|
14
|
-
export type AnimationClipCollection = Array<{ root: Object3D, clips: Array<
|
15
|
+
export type AnimationClipCollection = Array<{ root: Object3D, clips: Array<AnimationClip> }>;
|
15
16
|
|
16
17
|
export class RegisteredAnimationInfo {
|
17
18
|
|
@@ -20,9 +21,9 @@
|
|
20
21
|
|
21
22
|
private ext: AnimationExtension;
|
22
23
|
private root: Object3D;
|
23
|
-
private clip:
|
24
|
+
private clip: AnimationClip;
|
24
25
|
|
25
|
-
constructor(ext: AnimationExtension, root:
|
26
|
+
constructor(ext: AnimationExtension, root: Object3D, clip: AnimationClip) {
|
26
27
|
this.ext = ext;
|
27
28
|
this.root = root;
|
28
29
|
this.clip = clip;
|
@@ -30,17 +31,17 @@
|
|
30
31
|
}
|
31
32
|
|
32
33
|
export class TransformData {
|
33
|
-
clip:
|
34
|
-
pos?:
|
35
|
-
rot?:
|
36
|
-
scale?:
|
34
|
+
clip: AnimationClip;
|
35
|
+
pos?: KeyframeTrack;
|
36
|
+
rot?: KeyframeTrack;
|
37
|
+
scale?: KeyframeTrack;
|
37
38
|
get frameRate(): number { return 60; }
|
38
39
|
|
39
40
|
private ext: AnimationExtension;
|
40
41
|
private root: Object3D;
|
41
42
|
private target: Object3D;
|
42
43
|
|
43
|
-
constructor(ext: AnimationExtension, root: Object3D, target: Object3D, clip:
|
44
|
+
constructor(ext: AnimationExtension, root: Object3D, target: Object3D, clip: AnimationClip) {
|
44
45
|
this.ext = ext;
|
45
46
|
this.root = root;
|
46
47
|
this.target = target;
|
@@ -78,14 +79,14 @@
|
|
78
79
|
|
79
80
|
declare type AnimationDict = Map<Object3D, Array<TransformData>>;
|
80
81
|
|
81
|
-
export class AnimationExtension implements
|
82
|
+
export class AnimationExtension implements IUSDExporterExtension {
|
82
83
|
|
83
84
|
get extensionName(): string { return "animation" }
|
84
85
|
private dict: AnimationDict = new Map();
|
85
86
|
// private rootTargetMap: Map<Object3D, Object3D[]> = new Map();
|
86
87
|
private rootTargetMap: Map<Object3D, Object3D[]> = new Map();
|
87
88
|
|
88
|
-
getStartTime01(root: Object3D, clip:
|
89
|
+
getStartTime01(root: Object3D, clip: AnimationClip) {
|
89
90
|
const targets = this.rootTargetMap.get(root);
|
90
91
|
if (!targets) return Infinity;
|
91
92
|
let longestStartTime: number = -1;
|
@@ -108,7 +109,7 @@
|
|
108
109
|
return longestStartTime;
|
109
110
|
}
|
110
111
|
|
111
|
-
registerAnimation(root: Object3D, clip:
|
112
|
+
registerAnimation(root: Object3D, clip: AnimationClip): RegisteredAnimationInfo | null {
|
112
113
|
if (!clip || !root) return null;
|
113
114
|
if (!this.rootTargetMap.has(root)) this.rootTargetMap.set(root, []);
|
114
115
|
// this.rootTargetMap.get(root)?.push(clip);
|
@@ -165,7 +166,7 @@
|
|
165
166
|
}
|
166
167
|
}
|
167
168
|
|
168
|
-
onExportObject(object, model:
|
169
|
+
onExportObject(object, model: USDObject, _context) {
|
169
170
|
|
170
171
|
GameObject.foreachComponent(object, (comp) => {
|
171
172
|
const c = comp as unknown as UsdzAnimation;
|
@@ -187,7 +188,7 @@
|
|
187
188
|
|
188
189
|
object: Object3D;
|
189
190
|
dict: AnimationDict;
|
190
|
-
model:
|
191
|
+
model: USDObject | undefined = undefined;
|
191
192
|
|
192
193
|
private callback?: Function;
|
193
194
|
|
@@ -196,7 +197,7 @@
|
|
196
197
|
this.dict = dict;
|
197
198
|
}
|
198
199
|
|
199
|
-
registerCallback(model:
|
200
|
+
registerCallback(model: USDObject) {
|
200
201
|
if (this.model && this.callback) {
|
201
202
|
this.model.removeEventListener("serialize", this.callback);
|
202
203
|
}
|
@@ -209,6 +210,7 @@
|
|
209
210
|
}
|
210
211
|
|
211
212
|
onSerialize(writer, _context) {
|
213
|
+
if (!this.model) return;
|
212
214
|
if (debug)
|
213
215
|
console.log("SERIALIZE", this.model.name, this.object.type);
|
214
216
|
// do we have a track for this?
|
@@ -228,22 +230,24 @@
|
|
228
230
|
const rotation = new Quaternion();
|
229
231
|
const scale = new Vector3(1, 1, 1);
|
230
232
|
|
231
|
-
// TODO doesn't support individual time arrays right now
|
232
|
-
// could use these in case we don't have time values that are identical
|
233
|
-
/*
|
234
|
-
const translationInterpolant = o.pos?.createInterpolant() as THREE.Interpolant;
|
235
|
-
const rotationInterpolant = o.rot?.createInterpolant() as THREE.Interpolant;
|
236
|
-
const scaleInterpolant = o.scale?.createInterpolant() as THREE.Interpolant;
|
237
|
-
*/
|
238
|
-
|
239
233
|
writer.appendLine("matrix4d xformOp:transform.timeSamples = {");
|
240
234
|
writer.indent++;
|
241
235
|
|
242
236
|
for (const transformData of arr) {
|
243
|
-
let
|
244
|
-
|
245
|
-
|
246
|
-
|
237
|
+
let posTimesArray = transformData.pos?.times;
|
238
|
+
let rotTimesArray = transformData.rot?.times;
|
239
|
+
let scaleTimesArray = transformData.scale?.times;
|
240
|
+
|
241
|
+
// timesArray is the sorted union of all time values
|
242
|
+
let timesArray: number[] = [];
|
243
|
+
if (posTimesArray) for (const t of posTimesArray) timesArray.push(t);
|
244
|
+
if (rotTimesArray) for (const t of rotTimesArray) timesArray.push(t);
|
245
|
+
if (scaleTimesArray) for (const t of scaleTimesArray) timesArray.push(t);
|
246
|
+
// sort
|
247
|
+
timesArray.sort((a, b) => a - b);
|
248
|
+
timesArray = [...new Set(timesArray)];
|
249
|
+
|
250
|
+
if (!timesArray || timesArray.length === 0) {
|
247
251
|
console.error("got an animated object but no time values??", object, transformData);
|
248
252
|
continue;
|
249
253
|
}
|
@@ -276,8 +280,8 @@
|
|
276
280
|
rotation.set(quat[0], quat[1], quat[2], quat[3]);
|
277
281
|
}
|
278
282
|
if (scaleInterpolant) {
|
279
|
-
const
|
280
|
-
scale.set(
|
283
|
+
const scaleVal = scaleInterpolant.evaluate(time);
|
284
|
+
scale.set(scaleVal[0], scaleVal[1], scaleVal[2]);
|
281
285
|
}
|
282
286
|
|
283
287
|
composedTransform.compose(translation, rotation, scale);
|
@@ -290,17 +294,5 @@
|
|
290
294
|
}
|
291
295
|
writer.indent--;
|
292
296
|
writer.appendLine("}");
|
293
|
-
|
294
|
-
/*
|
295
|
-
let transform3 = new Matrix4();
|
296
|
-
transform3.compose(0.2,0,0);
|
297
|
-
const transform = buildMatrix(model.matrix);
|
298
|
-
const transform2 = buildMatrix(transform3.multiply(model.matrix));
|
299
|
-
|
300
|
-
writer.appendLine(`matrix4d xformOp:transform.timeSamples = {
|
301
|
-
0: ${transform},
|
302
|
-
30: ${transform2}
|
303
|
-
}`);
|
304
|
-
*/
|
305
297
|
}
|
306
298
|
}
|
@@ -4,7 +4,7 @@
|
|
4
4
|
import { EventSystem } from "./EventSystem";
|
5
5
|
import { showGizmos } from '../../engine/engine_default_parameters';
|
6
6
|
import { AxesHelper, Object3D } from 'three';
|
7
|
-
import { IGraphic } from './Interfaces';
|
7
|
+
import { ICanvas, IGraphic } from './Interfaces';
|
8
8
|
import { ShadowCastingMode } from '../Renderer';
|
9
9
|
export const includesDir = "./include";
|
10
10
|
|
@@ -50,6 +50,12 @@
|
|
50
50
|
return this._root;
|
51
51
|
}
|
52
52
|
|
53
|
+
protected get Canvas() {
|
54
|
+
const cv = this.Root as any as ICanvas;
|
55
|
+
if (cv?.isCanvas) return cv;
|
56
|
+
return null;
|
57
|
+
}
|
58
|
+
|
53
59
|
// private _intermediate?: Object3D;
|
54
60
|
protected _parentComponent?: BaseUIComponent | null = undefined;
|
55
61
|
|
@@ -3,12 +3,14 @@
|
|
3
3
|
import { FrameEvent } from "../../engine/engine_setup";
|
4
4
|
import { BaseUIComponent, UIRootComponent } from "./BaseUIComponent";
|
5
5
|
import { GameObject } from "../Component";
|
6
|
-
import { Object3D } from "three";
|
6
|
+
import { Matrix4, Object3D } from "three";
|
7
7
|
import { RectTransform } from "./RectTransform";
|
8
|
-
import { ICanvas } from "./Interfaces";
|
8
|
+
import { ICanvas, ILayoutGroup, IRectTransform } from "./Interfaces";
|
9
9
|
import { Camera } from "../Camera";
|
10
10
|
import { EventSystem } from "./EventSystem";
|
11
11
|
import * as ThreeMeshUI from 'three-mesh-ui'
|
12
|
+
import { getParam } from "../../engine/engine_utils";
|
13
|
+
import { LayoutGroup } from "./Layout";
|
12
14
|
|
13
15
|
export enum RenderMode {
|
14
16
|
ScreenSpaceOverlay = 0,
|
@@ -17,8 +19,14 @@
|
|
17
19
|
Undefined = -1,
|
18
20
|
}
|
19
21
|
|
22
|
+
const debugLayout = getParam("debuguilayout");
|
23
|
+
|
20
24
|
export class Canvas extends UIRootComponent implements ICanvas {
|
21
25
|
|
26
|
+
get isCanvas() {
|
27
|
+
return true;
|
28
|
+
}
|
29
|
+
|
22
30
|
get screenspace(): any {
|
23
31
|
return this.renderMode !== RenderMode.WorldSpace;
|
24
32
|
}
|
@@ -122,6 +130,10 @@
|
|
122
130
|
super.awake();
|
123
131
|
}
|
124
132
|
|
133
|
+
start() {
|
134
|
+
this.onUpdateRenderMode();
|
135
|
+
}
|
136
|
+
|
125
137
|
onEnable() {
|
126
138
|
super.onEnable();
|
127
139
|
this._updateRenderSettingsRoutine = undefined;
|
@@ -149,7 +161,29 @@
|
|
149
161
|
private _boundRenderSettingsChanged = this.onRenderSettingsChanged.bind(this);
|
150
162
|
|
151
163
|
private previousParent: Object3D | null = null;
|
164
|
+
private _lastMatrixWorld: Matrix4 | null = null;
|
165
|
+
private _rectTransforms: IRectTransform[] = [];
|
152
166
|
|
167
|
+
registerTransform(rt: IRectTransform) {
|
168
|
+
this._rectTransforms.push(rt);
|
169
|
+
}
|
170
|
+
unregisterTransform(rt: IRectTransform) {
|
171
|
+
const index = this._rectTransforms.indexOf(rt);
|
172
|
+
if (index !== -1) {
|
173
|
+
this._rectTransforms.splice(index, 1);
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
private _layoutGroups: Map<Object3D, ILayoutGroup> = new Map();
|
178
|
+
registerLayoutGroup(group: ILayoutGroup) {
|
179
|
+
const obj = group.gameObject;
|
180
|
+
this._layoutGroups.set(obj, group)
|
181
|
+
}
|
182
|
+
unregisterLayoutGroup(group: ILayoutGroup) {
|
183
|
+
const obj = group.gameObject;
|
184
|
+
this._layoutGroups.delete(obj);
|
185
|
+
}
|
186
|
+
|
153
187
|
onBeforeRenderRoutine = () => {
|
154
188
|
if (this.renderOnTop) {
|
155
189
|
// This is just a test but in reality it should be combined with all world canvases with render on top in one render pass
|
@@ -158,6 +192,7 @@
|
|
158
192
|
}
|
159
193
|
else {
|
160
194
|
this.onUpdateRenderMode();
|
195
|
+
this.handleLayoutUpdates();
|
161
196
|
// TODO: we might need to optimize this. This is here to make sure the TMUI text clipping matrices are correct. Ideally the text does use onBeforeRender and apply the clipping matrix there so we dont have to force update all the matrices here
|
162
197
|
this.shadowComponent?.updateMatrixWorld(true);
|
163
198
|
this.shadowComponent?.updateWorldMatrix(true, true);
|
@@ -171,13 +206,42 @@
|
|
171
206
|
this.context.renderer.autoClear = false;
|
172
207
|
this.context.renderer.clearDepth();
|
173
208
|
this.onUpdateRenderMode(true);
|
209
|
+
this.handleLayoutUpdates();
|
174
210
|
this.shadowComponent?.updateMatrixWorld(true);
|
211
|
+
// this.handleLayoutUpdates();
|
175
212
|
EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context);
|
176
213
|
this.context.renderer.render(this.gameObject, this.context.mainCamera);
|
177
214
|
this.context.renderer.autoClear = true;
|
178
215
|
}
|
216
|
+
this._lastMatrixWorld?.copy(this.gameObject.matrixWorld);
|
179
217
|
}
|
180
218
|
|
219
|
+
private handleLayoutUpdates() {
|
220
|
+
if (this._lastMatrixWorld === null) {
|
221
|
+
this._lastMatrixWorld = new Matrix4();
|
222
|
+
}
|
223
|
+
const matrixWorldChanged = !this._lastMatrixWorld.equals(this.gameObject.matrixWorld);
|
224
|
+
if (debugLayout && matrixWorldChanged) console.log("Canvas Layout changed", this.context.time.frameCount, this.name);
|
225
|
+
|
226
|
+
// TODO: optimize this, we should only need to update a subhierarchy of the parts where layout has changed
|
227
|
+
let didLog = false;
|
228
|
+
for (const ch of this._rectTransforms) {
|
229
|
+
if (matrixWorldChanged) ch.markDirty();
|
230
|
+
let layout = this._layoutGroups.get(ch.gameObject);
|
231
|
+
if(ch.isDirty && !layout){
|
232
|
+
layout = ch.gameObject.getComponentInParent(LayoutGroup) as LayoutGroup;
|
233
|
+
}
|
234
|
+
if (ch.isDirty || layout?.isDirty) {
|
235
|
+
if (debugLayout && !didLog) {
|
236
|
+
console.log("CANVAS UPDATE ### " + ch.name + " ##################################### " + this.context.time.frame);
|
237
|
+
// didLog = true;
|
238
|
+
}
|
239
|
+
layout?.updateLayout();
|
240
|
+
ch.updateTransform();
|
241
|
+
}
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
181
245
|
applyRenderSettings() {
|
182
246
|
this.onRenderSettingsChanged();
|
183
247
|
}
|
@@ -205,6 +269,7 @@
|
|
205
269
|
private _lastWidth: number = -1;
|
206
270
|
private _lastHeight: number = -1;
|
207
271
|
|
272
|
+
|
208
273
|
private onUpdateRenderMode(force: boolean = false) {
|
209
274
|
if (!force) {
|
210
275
|
if (this._renderMode === this._activeRenderMode && this._lastWidth === this.context.domWidth && this._lastHeight === this.context.domHeight) {
|
@@ -241,6 +306,13 @@
|
|
241
306
|
canvas.quaternion.identity();
|
242
307
|
|
243
308
|
const rect = this.gameObject.getComponent(RectTransform)!;
|
309
|
+
let hasChanged = false;
|
310
|
+
if (rect.sizeDelta.x !== this.context.domWidth) {
|
311
|
+
hasChanged = true;
|
312
|
+
}
|
313
|
+
if (rect.sizeDelta.y !== this.context.domHeight) {
|
314
|
+
hasChanged = true;
|
315
|
+
}
|
244
316
|
|
245
317
|
const vFOV = camera.fieldOfView! * Math.PI / 180;
|
246
318
|
const h = 2 * Math.tan(vFOV / 2) * Math.abs(plane);
|
@@ -248,11 +320,14 @@
|
|
248
320
|
canvas.scale.y = h / this.context.domHeight;
|
249
321
|
// Set scale.z, otherwise small offsets in screenspace mode have different visual results based on export scale and other settings
|
250
322
|
canvas.scale.z = .01;
|
251
|
-
rect.sizeDelta.x = this.context.domWidth;
|
252
|
-
rect.sizeDelta.y = this.context.domHeight;
|
253
|
-
rect?.markDirty();
|
254
323
|
|
324
|
+
if (hasChanged) {
|
325
|
+
rect.sizeDelta.x = this.context.domWidth;
|
326
|
+
rect.sizeDelta.y = this.context.domHeight;
|
327
|
+
rect?.markDirty();
|
328
|
+
}
|
255
329
|
|
330
|
+
|
256
331
|
// this.context.scene.add(this.gameObject)
|
257
332
|
// this.gameObject.scale.multiplyScalar(.01);
|
258
333
|
// this.gameObject.position.set(0,0,0);
|
@@ -1,5 +1,8 @@
|
|
1
1
|
// Export types
|
2
2
|
export class __Ignore {}
|
3
|
+
export { ActionBuilder } from "../export/usdz/extensions/behavior/BehavioursBuilder";
|
4
|
+
export { ActionCollection } from "../export/usdz/extensions/behavior/Actions";
|
5
|
+
export { ActionModel } from "../export/usdz/extensions/behavior/BehavioursBuilder";
|
3
6
|
export { AlignmentConstraint } from "../AlignmentConstraint";
|
4
7
|
export { Animation } from "../Animation";
|
5
8
|
export { AnimationCurve } from "../AnimationCurve";
|
@@ -24,6 +27,8 @@
|
|
24
27
|
export { AxesHelper } from "../AxesHelper";
|
25
28
|
export { BaseUIComponent } from "../ui/BaseUIComponent";
|
26
29
|
export { BasicIKConstraint } from "../BasicIKConstraint";
|
30
|
+
export { BehaviorExtension } from "../export/usdz/extensions/behavior/Behaviour";
|
31
|
+
export { BehaviorModel } from "../export/usdz/extensions/behavior/BehavioursBuilder";
|
27
32
|
export { Behaviour } from "../Component";
|
28
33
|
export { Bloom } from "../postprocessing/Effects/Bloom";
|
29
34
|
export { BoxCollider } from "../Collider";
|
@@ -35,6 +40,8 @@
|
|
35
40
|
export { Canvas } from "../ui/Canvas";
|
36
41
|
export { CanvasGroup } from "../ui/CanvasGroup";
|
37
42
|
export { CapsuleCollider } from "../Collider";
|
43
|
+
export { ChangeMaterialOnClick } from "../export/usdz/extensions/behavior/BehaviourComponents";
|
44
|
+
export { ChangeTransformOnClick } from "../export/usdz/extensions/behavior/BehaviourComponents";
|
38
45
|
export { CharacterController } from "../CharacterController";
|
39
46
|
export { CharacterControllerInput } from "../CharacterController";
|
40
47
|
export { ChromaticAberration } from "../postprocessing/Effects/ChromaticAberration";
|
@@ -48,10 +55,12 @@
|
|
48
55
|
export { DeleteBox } from "../DeleteBox";
|
49
56
|
export { DepthOfField } from "../postprocessing/Effects/DepthOfField";
|
50
57
|
export { DeviceFlag } from "../DeviceFlag";
|
58
|
+
export { DocumentExtension } from "../export/usdz/extensions/DocumentExtension";
|
51
59
|
export { DragControls } from "../DragControls";
|
52
60
|
export { DropListener } from "../DropListener";
|
53
61
|
export { Duplicatable } from "../Duplicatable";
|
54
62
|
export { EmissionModule } from "../ParticleSystemModules";
|
63
|
+
export { EmphasizeOnClick } from "../export/usdz/extensions/behavior/BehaviourComponents";
|
55
64
|
export { EventList } from "../EventList";
|
56
65
|
export { EventListEvent } from "../EventList";
|
57
66
|
export { EventSystem } from "../ui/EventSystem";
|
@@ -68,13 +77,14 @@
|
|
68
77
|
export { GridHelper } from "../GridHelper";
|
69
78
|
export { GridLayoutGroup } from "../ui/Layout";
|
70
79
|
export { GroundProjectedEnv } from "../GroundProjection";
|
80
|
+
export { GroupActionModel } from "../export/usdz/extensions/behavior/BehavioursBuilder";
|
81
|
+
export { HideOnStart } from "../export/usdz/extensions/behavior/BehaviourComponents";
|
71
82
|
export { HingeJoint } from "../Joints";
|
72
83
|
export { HorizontalLayoutGroup } from "../ui/Layout";
|
73
84
|
export { Image } from "../ui/Image";
|
74
85
|
export { InheritVelocityModule } from "../ParticleSystemModules";
|
75
86
|
export { InputField } from "../ui/InputField";
|
76
87
|
export { Interactable } from "../Interactable";
|
77
|
-
export { LayoutGroup } from "../ui/Layout";
|
78
88
|
export { Light } from "../Light";
|
79
89
|
export { LimitVelocityOverLifetimeModule } from "../ParticleSystemModules";
|
80
90
|
export { LODGroup } from "../LODGroup";
|
@@ -96,15 +106,19 @@
|
|
96
106
|
export { OpenURL } from "../utils/OpenURL";
|
97
107
|
export { OrbitControls } from "../OrbitControls";
|
98
108
|
export { Outline } from "../ui/Outline";
|
109
|
+
export { Padding } from "../ui/Layout";
|
99
110
|
export { ParticleBurst } from "../ParticleSystemModules";
|
100
111
|
export { ParticleSubEmitter } from "../ParticleSystemSubEmitter";
|
101
112
|
export { ParticleSystem } from "../ParticleSystem";
|
102
113
|
export { ParticleSystemRenderer } from "../ParticleSystem";
|
103
114
|
export { PixelationEffect } from "../postprocessing/Effects/Pixelation";
|
104
115
|
export { PlayableDirector } from "../timeline/PlayableDirector";
|
116
|
+
export { PlayAnimationOnClick } from "../export/usdz/extensions/behavior/BehaviourComponents";
|
105
117
|
export { PlayerColor } from "../PlayerColor";
|
106
118
|
export { PointerEventData } from "../ui/PointerEvents";
|
107
119
|
export { PostProcessingHandler } from "../postprocessing/PostProcessingHandler";
|
120
|
+
export { PreliminaryAction } from "../export/usdz/extensions/behavior/BehaviourComponents";
|
121
|
+
export { PreliminaryTrigger } from "../export/usdz/extensions/behavior/BehaviourComponents";
|
108
122
|
export { QuickLookOverlay } from "../export/usdz/USDZExporter";
|
109
123
|
export { RawImage } from "../ui/Image";
|
110
124
|
export { Raycaster } from "../ui/Raycaster";
|
@@ -122,6 +136,7 @@
|
|
122
136
|
export { SceneSwitcher } from "../SceneSwitcher";
|
123
137
|
export { ScreenCapture } from "../ScreenCapture";
|
124
138
|
export { ScreenSpaceAmbientOcclusion } from "../postprocessing/Effects/ScreenspaceAmbientOcclusion";
|
139
|
+
export { SetActiveOnClick } from "../export/usdz/extensions/behavior/BehaviourComponents";
|
125
140
|
export { ShadowCatcher } from "../ShadowCatcher";
|
126
141
|
export { ShapeModule } from "../ParticleSystemModules";
|
127
142
|
export { SignalAsset } from "../timeline/SignalAsset";
|
@@ -146,24 +161,33 @@
|
|
146
161
|
export { SyncedCamera } from "../SyncedCamera";
|
147
162
|
export { SyncedRoom } from "../SyncedRoom";
|
148
163
|
export { SyncedTransform } from "../SyncedTransform";
|
164
|
+
export { TapGestureTrigger } from "../export/usdz/extensions/behavior/BehaviourComponents";
|
149
165
|
export { TeleportTarget } from "../webxr/WebXRController";
|
150
166
|
export { TestRunner } from "../TestRunner";
|
151
167
|
export { TestSimulateUserData } from "../TestRunner";
|
152
168
|
export { Text } from "../ui/Text";
|
169
|
+
export { TextBuilder } from "../export/usdz/extensions/USDZText";
|
170
|
+
export { TextExtension } from "../export/usdz/extensions/USDZText";
|
153
171
|
export { TextureSheetAnimationModule } from "../ParticleSystemModules";
|
154
172
|
export { TiltShiftEffect } from "../postprocessing/Effects/TiltShiftEffect";
|
155
173
|
export { ToneMapping } from "../postprocessing/Effects/Tonemapping";
|
156
174
|
export { TrailModule } from "../ParticleSystemModules";
|
157
175
|
export { TransformData } from "../export/usdz/extensions/Animation";
|
158
176
|
export { TransformGizmo } from "../TransformGizmo";
|
177
|
+
export { TriggerBuilder } from "../export/usdz/extensions/behavior/BehavioursBuilder";
|
178
|
+
export { TriggerModel } from "../export/usdz/extensions/behavior/BehavioursBuilder";
|
159
179
|
export { UIRaycastUtils } from "../ui/RaycastUtils";
|
160
180
|
export { UIRootComponent } from "../ui/BaseUIComponent";
|
161
181
|
export { UsageMarker } from "../Interactable";
|
182
|
+
export { USDZBehaviours } from "../export/usdz/extensions/behavior/Behaviour";
|
162
183
|
export { USDZExporter } from "../export/usdz/USDZExporter";
|
184
|
+
export { USDZText } from "../export/usdz/extensions/USDZText";
|
185
|
+
export { VariantAction } from "../export/usdz/extensions/behavior/Actions";
|
163
186
|
export { VelocityOverLifetimeModule } from "../ParticleSystemModules";
|
164
187
|
export { VerticalLayoutGroup } from "../ui/Layout";
|
165
188
|
export { VideoPlayer } from "../VideoPlayer";
|
166
189
|
export { Vignette } from "../postprocessing/Effects/Vignette";
|
190
|
+
export { VisibilityAction } from "../export/usdz/extensions/behavior/BehaviourComponents";
|
167
191
|
export { Voip } from "../Voip";
|
168
192
|
export { Volume } from "../postprocessing/Volume";
|
169
193
|
export { VolumeParameter } from "../postprocessing/VolumeParameter";
|
@@ -163,8 +163,9 @@
|
|
163
163
|
return internalForEachComponent(instance, cb, recursive);
|
164
164
|
}
|
165
165
|
|
166
|
-
export function* foreachComponentEnumerator<T extends IComponent>(instance: Object3D, type?: Constructor<T>, includeChildren: boolean = false): Generator<T> {
|
166
|
+
export function* foreachComponentEnumerator<T extends IComponent>(instance: Object3D, type?: Constructor<T>, includeChildren: boolean = false, maxLevel: number = 999, _currentLevel: number = 0): Generator<T> {
|
167
167
|
if (!instance?.userData.components) return;
|
168
|
+
if (_currentLevel > maxLevel) return;
|
168
169
|
for (const comp of instance.userData.components) {
|
169
170
|
if (type && comp?.isComponent === true && comp instanceof type) {
|
170
171
|
yield comp;
|
@@ -175,7 +176,7 @@
|
|
175
176
|
}
|
176
177
|
if (includeChildren === true) {
|
177
178
|
for (const ch of instance.children) {
|
178
|
-
yield* foreachComponentEnumerator(ch, type, true);
|
179
|
+
yield* foreachComponentEnumerator(ch, type, true, maxLevel, _currentLevel + 1);
|
179
180
|
}
|
180
181
|
}
|
181
182
|
}
|
@@ -98,8 +98,8 @@
|
|
98
98
|
const tempVec = _worldScale2;
|
99
99
|
const obj2 = obj.parent;
|
100
100
|
obj2.getWorldScale(tempVec);
|
101
|
-
|
102
|
-
obj.scale.
|
101
|
+
obj.scale.copy(vec);
|
102
|
+
obj.scale.divide(tempVec);
|
103
103
|
}
|
104
104
|
|
105
105
|
const _forward = new Vector3();
|
@@ -1,12 +1,11 @@
|
|
1
|
-
import {
|
1
|
+
import { USDObject } from "./ThreeUSDZExporter";
|
2
2
|
|
3
|
+
export interface IUSDExporterExtension {
|
3
4
|
|
4
|
-
export interface IUSDZExporterExtension {
|
5
|
-
|
6
5
|
get extensionName(): string;
|
7
6
|
onBeforeBuildDocument?(context);
|
8
7
|
onAfterBuildDocument?(context);
|
9
|
-
onExportObject?(object, model :
|
8
|
+
onExportObject?(object, model : USDObject, context);
|
10
9
|
onAfterSerialize?(context);
|
11
|
-
onAfterHierarchy?(context);
|
10
|
+
onAfterHierarchy?(context, writer : any);
|
12
11
|
}
|
@@ -9,6 +9,8 @@
|
|
9
9
|
import { GameObject } from '../Component';
|
10
10
|
import SimpleStateBehavior from "three-mesh-ui/examples/behaviors/states/SimpleStateBehavior"
|
11
11
|
import { Outline } from './Outline';
|
12
|
+
import { BehaviorExtension, UsdzBehaviour } from '../../engine-components/export/usdz/extensions/behavior/Behaviour';
|
13
|
+
import { USDObject } from '../../engine-components/export/usdz/ThreeUSDZExporter';
|
12
14
|
|
13
15
|
const _colorStateObject: { backgroundColor: Color, backgroundOpacity: number } = {
|
14
16
|
backgroundColor: new Color(1, 1, 1),
|
@@ -49,9 +49,9 @@
|
|
49
49
|
if(this.sprite?.texture?.name === "Knob") {
|
50
50
|
opts.borderRadius = 999;
|
51
51
|
}
|
52
|
-
opts.borderColor = new Color(.4, .4, .4);
|
53
|
-
opts.borderOpacity = this.color.alpha;
|
54
|
-
opts.borderWidth = .3;
|
52
|
+
// opts.borderColor = new Color(.4, .4, .4);
|
53
|
+
// opts.borderOpacity = this.color.alpha;
|
54
|
+
// opts.borderWidth = .3;
|
55
55
|
}
|
56
56
|
|
57
57
|
}
|
@@ -1,24 +1,48 @@
|
|
1
|
+
import { Behaviour } from "../Component";
|
1
2
|
import { IComponent } from "../../engine/engine_types";
|
2
3
|
|
3
4
|
export interface ICanvas {
|
4
|
-
get
|
5
|
+
get isCanvas(): boolean;
|
6
|
+
get screenspace(): boolean;
|
7
|
+
registerTransform(rt: IRectTransform);
|
8
|
+
unregisterTransform(rt: IRectTransform);
|
5
9
|
}
|
6
10
|
|
7
11
|
export interface ICanvasGroup {
|
8
|
-
get isCanvasGroup()
|
12
|
+
get isCanvasGroup(): boolean;
|
9
13
|
blocksRaycasts: boolean;
|
10
14
|
interactable: boolean;
|
11
15
|
}
|
12
16
|
|
13
17
|
export interface IGraphic extends IComponent {
|
14
|
-
get isGraphic()
|
18
|
+
get isGraphic(): boolean;
|
15
19
|
raycastTarget: boolean;
|
16
20
|
}
|
17
21
|
|
18
22
|
export interface IRectTransform extends IComponent {
|
23
|
+
get isDirty(): boolean;
|
24
|
+
markDirty();
|
25
|
+
updateTransform();
|
26
|
+
}
|
19
27
|
|
28
|
+
export interface IRectTransformChangedReceiver {
|
29
|
+
onParentRectTransformChanged(comp: IRectTransform): void;
|
20
30
|
}
|
21
31
|
|
22
|
-
export interface
|
23
|
-
|
24
|
-
|
32
|
+
export interface ILayoutGroup extends IComponent {
|
33
|
+
get isLayoutGroup(): boolean;
|
34
|
+
get isDirty(): boolean;
|
35
|
+
updateLayout();
|
36
|
+
}
|
37
|
+
|
38
|
+
// export abstract class LayoutGroup extends Behaviour implements IRectTransformChangedReceiver, ILayoutGroup {
|
39
|
+
// get isLayoutGroup(): boolean {
|
40
|
+
// return true;
|
41
|
+
// }
|
42
|
+
// updateLayout() {
|
43
|
+
// throw new Error("Method not implemented.");
|
44
|
+
// }
|
45
|
+
// onParentRectTransformChanged(comp: IRectTransform): void {
|
46
|
+
// throw new Error("Method not implemented.");
|
47
|
+
// }
|
48
|
+
// }
|
@@ -1,17 +1,316 @@
|
|
1
|
-
import {
|
1
|
+
import { ILayoutGroup, IRectTransform, IRectTransformChangedReceiver } from "./Interfaces";
|
2
|
+
import { Behaviour, GameObject } from "../Component";
|
3
|
+
import { serializable } from "../../engine/engine_serialization";
|
4
|
+
import { Canvas } from "./Canvas";
|
5
|
+
import { RectTransform } from "./RectTransform";
|
6
|
+
import { getParam } from "../../engine/engine_utils";
|
2
7
|
|
3
|
-
|
8
|
+
const debug = getParam("debuguilayout");
|
9
|
+
|
10
|
+
export class Padding {
|
11
|
+
@serializable()
|
12
|
+
left: number = 0;
|
13
|
+
@serializable()
|
14
|
+
right: number = 0;
|
15
|
+
@serializable()
|
16
|
+
top: number = 0;
|
17
|
+
@serializable()
|
18
|
+
bottom: number = 0;
|
19
|
+
|
20
|
+
get vertical() {
|
21
|
+
return this.top + this.bottom;
|
22
|
+
}
|
23
|
+
get horizontal() {
|
24
|
+
return this.left + this.right;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
export enum TextAnchor {
|
29
|
+
UpperLeft = 0,
|
30
|
+
UpperCenter = 1,
|
31
|
+
UpperRight = 2,
|
32
|
+
MiddleLeft = 3,
|
33
|
+
MiddleCenter = 4,
|
34
|
+
MiddleRight = 5,
|
35
|
+
LowerLeft = 6,
|
36
|
+
LowerCenter = 7,
|
37
|
+
LowerRight = 8,
|
38
|
+
Custom = 9
|
39
|
+
}
|
40
|
+
|
41
|
+
enum Axis {
|
42
|
+
Horizontal = "x",
|
43
|
+
Vertical = "y"
|
44
|
+
}
|
45
|
+
|
46
|
+
export abstract class LayoutGroup extends Behaviour implements ILayoutGroup {
|
47
|
+
|
48
|
+
private _rectTransform: RectTransform | null = null;
|
49
|
+
private get rectTransform() {
|
50
|
+
return this._rectTransform;
|
51
|
+
}
|
52
|
+
|
53
|
+
onParentRectTransformChanged(_comp: IRectTransform): void {
|
54
|
+
this._needsUpdate = true;
|
55
|
+
}
|
56
|
+
|
57
|
+
private _needsUpdate: boolean = false;
|
58
|
+
get isDirty(): boolean {
|
59
|
+
return this._needsUpdate;
|
60
|
+
}
|
61
|
+
|
62
|
+
get isLayoutGroup(): boolean {
|
63
|
+
return true;
|
64
|
+
}
|
65
|
+
|
66
|
+
updateLayout() {
|
67
|
+
if (!this._rectTransform) return;
|
68
|
+
if (debug)
|
69
|
+
console.warn("Layout Update", this.context.time.frame, this.name);
|
70
|
+
this._needsUpdate = false;
|
71
|
+
this.onCalculateLayout(this._rectTransform);
|
72
|
+
}
|
73
|
+
|
74
|
+
// onBeforeRender(): void {
|
75
|
+
// this.updateLayout();
|
76
|
+
// }
|
77
|
+
|
78
|
+
@serializable()
|
79
|
+
childAlignment: TextAnchor = TextAnchor.UpperLeft;
|
80
|
+
|
81
|
+
@serializable()
|
4
82
|
reverseArrangement: boolean = false;
|
83
|
+
|
84
|
+
@serializable()
|
85
|
+
spacing: number = 0;
|
86
|
+
@serializable(Padding)
|
87
|
+
padding!: Padding;
|
88
|
+
|
89
|
+
@serializable()
|
90
|
+
minWidth: number = 0;
|
91
|
+
@serializable()
|
92
|
+
minHeight: number = 0;
|
93
|
+
|
94
|
+
@serializable()
|
95
|
+
flexibleHeight: number = 0;
|
96
|
+
@serializable()
|
97
|
+
flexibleWidth: number = 0;
|
98
|
+
|
99
|
+
@serializable()
|
100
|
+
preferredHeight: number = 0;
|
101
|
+
@serializable()
|
102
|
+
preferredWidth: number = 0;
|
103
|
+
|
104
|
+
start() {
|
105
|
+
this._needsUpdate = true;
|
106
|
+
}
|
107
|
+
|
108
|
+
onEnable(): void {
|
109
|
+
if(debug) console.log(this.name, this);
|
110
|
+
this._rectTransform = this.gameObject.getComponent(RectTransform);
|
111
|
+
const canvas = this.gameObject.getComponentInParent(Canvas);
|
112
|
+
if (canvas) {
|
113
|
+
canvas.registerLayoutGroup(this);
|
114
|
+
}
|
115
|
+
this._needsUpdate = true;
|
116
|
+
}
|
117
|
+
|
118
|
+
onDisable(): void {
|
119
|
+
const canvas = this.gameObject.getComponentInParent(Canvas);
|
120
|
+
if (canvas) {
|
121
|
+
canvas.unregisterLayoutGroup(this);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
protected abstract onCalculateLayout(rt: RectTransform);
|
126
|
+
|
127
|
+
|
128
|
+
|
129
|
+
// for animation:
|
130
|
+
private set m_Spacing(val) {
|
131
|
+
if (val === this.spacing) return;
|
132
|
+
this._needsUpdate = true;
|
133
|
+
this.spacing = val;
|
134
|
+
}
|
135
|
+
get m_Spacing() {
|
136
|
+
return this.spacing;
|
137
|
+
}
|
5
138
|
}
|
6
139
|
|
7
|
-
export class
|
140
|
+
export abstract class HorizontalOrVerticalLayoutGroup extends LayoutGroup {
|
8
141
|
|
142
|
+
@serializable()
|
143
|
+
childControlHeight: boolean = true;
|
144
|
+
@serializable()
|
145
|
+
childControlWidth: boolean = true;
|
146
|
+
@serializable()
|
147
|
+
childForceExpandHeight: boolean = false;
|
148
|
+
@serializable()
|
149
|
+
childForceExpandWidth: boolean = false;
|
150
|
+
@serializable()
|
151
|
+
childScaleHeight: boolean = false;
|
152
|
+
@serializable()
|
153
|
+
childScaleWidth: boolean = false;
|
154
|
+
|
155
|
+
protected abstract get primaryAxis(): Axis;
|
156
|
+
|
157
|
+
protected onCalculateLayout(rect: RectTransform) {
|
158
|
+
const axis = this.primaryAxis;
|
159
|
+
|
160
|
+
const totalWidth = rect.width;
|
161
|
+
let actualWidth = totalWidth;
|
162
|
+
const totalHeight = rect.height;
|
163
|
+
let actualHeight = totalHeight;
|
164
|
+
actualWidth -= this.padding.horizontal;
|
165
|
+
actualHeight -= this.padding.vertical;
|
166
|
+
|
167
|
+
// console.log(rt.name, "width=" + totalWidth + ", height=" + totalHeight)
|
168
|
+
|
169
|
+
const paddingAxis = axis === Axis.Horizontal ? this.padding.horizontal : this.padding.vertical;
|
170
|
+
const isHorizontal = axis === Axis.Horizontal;
|
171
|
+
const isVertical = !isHorizontal;
|
172
|
+
const otherAxis = isHorizontal ? "y" : "x";
|
173
|
+
const controlSize = isHorizontal ? this.childControlWidth : this.childControlHeight;
|
174
|
+
const controlSizeOtherAxis = isHorizontal ? this.childControlHeight : this.childControlWidth;
|
175
|
+
const forceExpandSize = isHorizontal ? this.childForceExpandWidth : this.childForceExpandHeight;
|
176
|
+
const forceExpandSizeOtherAxis = isHorizontal ? this.childForceExpandHeight : this.childForceExpandWidth;
|
177
|
+
const actualExpandSize = isHorizontal ? actualHeight : actualWidth;
|
178
|
+
const totalSpace = isHorizontal ? totalWidth : totalHeight;
|
179
|
+
// 0 is left/top, 0.5 is middle, 1 is right/bottom
|
180
|
+
const alignmentOnAxis = 0.5 * (isHorizontal ? this.childAlignment % 3 : Math.floor(this.childAlignment / 3));
|
181
|
+
|
182
|
+
let start = 0;
|
183
|
+
if (isHorizontal) {
|
184
|
+
start += this.padding.left;
|
185
|
+
}
|
186
|
+
else
|
187
|
+
start += this.padding.top;
|
188
|
+
|
189
|
+
|
190
|
+
// Calculate total size of the elements
|
191
|
+
let totalChildSize = 0;
|
192
|
+
let actualRectTransformChildCount = 0;
|
193
|
+
for (let i = 0; i < this.gameObject.children.length; i++) {
|
194
|
+
const ch = this.gameObject.children[i];
|
195
|
+
const rt = GameObject.getComponent(ch, RectTransform);
|
196
|
+
if (rt?.activeAndEnabled) {
|
197
|
+
actualRectTransformChildCount += 1;
|
198
|
+
if (isHorizontal) {
|
199
|
+
totalChildSize += rt.width;
|
200
|
+
}
|
201
|
+
else {
|
202
|
+
totalChildSize += rt.height;
|
203
|
+
}
|
204
|
+
}
|
205
|
+
}
|
206
|
+
|
207
|
+
let sizePerChild = 0;
|
208
|
+
const totalSpacing = this.spacing * (actualRectTransformChildCount - 1)
|
209
|
+
if (forceExpandSize || controlSize) {
|
210
|
+
let size = 0;
|
211
|
+
if (isHorizontal) {
|
212
|
+
size = actualWidth -= totalSpacing;
|
213
|
+
}
|
214
|
+
else {
|
215
|
+
size = actualHeight -= totalSpacing;
|
216
|
+
}
|
217
|
+
if (actualRectTransformChildCount > 0)
|
218
|
+
sizePerChild = size / actualRectTransformChildCount;
|
219
|
+
}
|
220
|
+
|
221
|
+
let leftOffset = 0;
|
222
|
+
leftOffset += this.padding.left;
|
223
|
+
leftOffset -= this.padding.right;
|
224
|
+
|
225
|
+
if (alignmentOnAxis !== 0) {
|
226
|
+
start = totalSpace - totalChildSize;
|
227
|
+
start *= alignmentOnAxis;
|
228
|
+
start -= totalSpacing * alignmentOnAxis;
|
229
|
+
if (isHorizontal) {
|
230
|
+
start -= this.padding.right * alignmentOnAxis;
|
231
|
+
start += this.padding.left * (1 - alignmentOnAxis);
|
232
|
+
if (start < this.padding.left) {
|
233
|
+
start = this.padding.left;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
else {
|
237
|
+
start -= this.padding.bottom * alignmentOnAxis;
|
238
|
+
start += this.padding.top * (1 - alignmentOnAxis);
|
239
|
+
if (start < this.padding.top) {
|
240
|
+
start = this.padding.top;
|
241
|
+
}
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
// Apply layout
|
246
|
+
let k = 0;
|
247
|
+
for (let i = 0; i < this.gameObject.children.length; i++) {
|
248
|
+
const ch = this.gameObject.children[i];
|
249
|
+
const rt = GameObject.getComponent(ch, RectTransform);
|
250
|
+
if (rt?.activeAndEnabled) {
|
251
|
+
rt.pivot?.set(.5, .5);
|
252
|
+
// Horizontal padding
|
253
|
+
const x = totalWidth * .5 + leftOffset * .5;
|
254
|
+
if (rt.anchoredPosition.x !== x)
|
255
|
+
rt.anchoredPosition.x = x;
|
256
|
+
const y = totalHeight * -.5
|
257
|
+
if (rt.anchoredPosition.y !== y)
|
258
|
+
rt.anchoredPosition.y = y;
|
259
|
+
// Set the size for the secondary axis (e.g. height for a horizontal layout group)
|
260
|
+
if (forceExpandSizeOtherAxis && controlSizeOtherAxis && rt.sizeDelta[otherAxis] !== actualExpandSize) {
|
261
|
+
rt.sizeDelta[otherAxis] = actualExpandSize;
|
262
|
+
}
|
263
|
+
// Set the size for the primary axis (e.g. width for a horizontal layout group)
|
264
|
+
if (forceExpandSize && controlSize && rt.sizeDelta[axis] !== sizePerChild) {
|
265
|
+
rt.sizeDelta[axis] = sizePerChild
|
266
|
+
}
|
267
|
+
|
268
|
+
const size = isHorizontal ? rt.width : rt.height;
|
269
|
+
let halfSize = size * .5;
|
270
|
+
start += halfSize;
|
271
|
+
|
272
|
+
if (forceExpandSize) {
|
273
|
+
let preferredStart = sizePerChild * (k + 1) - sizePerChild * .5;
|
274
|
+
if (preferredStart > start)
|
275
|
+
start = preferredStart;
|
276
|
+
}
|
277
|
+
|
278
|
+
let value = start;
|
279
|
+
if (axis === Axis.Vertical)
|
280
|
+
value = -value;
|
281
|
+
// Only set the position if it's not already the correct one to avoid triggering the rectTransform dirty event
|
282
|
+
if (rt.anchoredPosition[axis] !== value) {
|
283
|
+
rt.anchoredPosition[axis] = value
|
284
|
+
}
|
285
|
+
|
286
|
+
start += halfSize;
|
287
|
+
start += this.spacing;
|
288
|
+
k += 1;
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
|
293
|
+
|
9
294
|
}
|
10
295
|
|
11
|
-
export class
|
296
|
+
export class VerticalLayoutGroup extends HorizontalOrVerticalLayoutGroup {
|
12
297
|
|
298
|
+
protected get primaryAxis() {
|
299
|
+
return Axis.Vertical;
|
300
|
+
}
|
301
|
+
|
13
302
|
}
|
14
303
|
|
304
|
+
export class HorizontalLayoutGroup extends HorizontalOrVerticalLayoutGroup {
|
305
|
+
|
306
|
+
protected get primaryAxis() {
|
307
|
+
return Axis.Horizontal;
|
308
|
+
}
|
309
|
+
|
310
|
+
}
|
311
|
+
|
15
312
|
export class GridLayoutGroup extends LayoutGroup {
|
313
|
+
protected onCalculateLayout() {
|
314
|
+
}
|
16
315
|
|
17
316
|
}
|
@@ -1,21 +1,74 @@
|
|
1
1
|
import { serializable } from "../../engine/engine_serialization";
|
2
2
|
import { Behaviour } from "../Component";
|
3
|
-
import { Object3D } from "three";
|
4
|
-
import { getWorldPosition,
|
3
|
+
import { Matrix4, Object3D, Quaternion, Vector3 } from "three";
|
4
|
+
import { getWorldPosition, getWorldQuaternion, setWorldQuaternion } from "../../engine/engine_three_utils";
|
5
5
|
|
6
|
-
|
6
|
+
import { USDObject } from "../../engine-components/export/usdz/ThreeUSDZExporter";
|
7
|
+
import { UsdzBehaviour } from "../../engine-components/export/usdz/extensions/behavior/Behaviour";
|
8
|
+
import { ActionBuilder, BehaviorModel, TriggerBuilder, USDVec3 } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
|
7
9
|
|
10
|
+
export class LookAt extends Behaviour implements UsdzBehaviour {
|
11
|
+
|
8
12
|
@serializable(Object3D)
|
9
13
|
target?: Object3D;
|
10
14
|
|
11
15
|
@serializable()
|
12
16
|
invertForward: boolean = false;
|
13
17
|
|
18
|
+
@serializable()
|
19
|
+
keepUpDirection: boolean = true;
|
20
|
+
|
21
|
+
@serializable()
|
22
|
+
copyTargetRotation: boolean = false;
|
23
|
+
|
24
|
+
private static flipYQuat: Quaternion = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
|
25
|
+
|
14
26
|
onBeforeRender(): void {
|
15
|
-
|
16
|
-
if (!this.
|
17
|
-
|
27
|
+
let target: Object3D | null | undefined = this.target;
|
28
|
+
if (!target) target = this.context.mainCamera;
|
29
|
+
if (!target) return;
|
30
|
+
|
31
|
+
const lookTarget = getWorldPosition(target);
|
32
|
+
const lookFrom = getWorldPosition(this.gameObject);
|
33
|
+
|
34
|
+
if (this.keepUpDirection)
|
35
|
+
lookTarget.y = lookFrom.y;
|
36
|
+
|
37
|
+
if (this.copyTargetRotation)
|
38
|
+
setWorldQuaternion(this.gameObject, getWorldQuaternion(target));
|
18
39
|
else
|
19
|
-
|
40
|
+
this.gameObject.lookAt(lookTarget);
|
41
|
+
|
42
|
+
if (this.invertForward)
|
43
|
+
this.gameObject.quaternion.multiply(LookAt.flipYQuat);
|
20
44
|
}
|
45
|
+
|
46
|
+
createBehaviours(ext, model: USDObject, _context) {
|
47
|
+
if (model.uuid === this.gameObject.uuid) {
|
48
|
+
let alignmentTarget = model;
|
49
|
+
|
50
|
+
// not entirely sure why we need to do this - looks like LookAt with up vector doesn't work properly in
|
51
|
+
// QuickLook, so we need to introduce an empty parent and rotate the model by 90° around Y
|
52
|
+
if (this.keepUpDirection) {
|
53
|
+
const parent = USDObject.createEmptyParent(model);
|
54
|
+
alignmentTarget = parent;
|
55
|
+
|
56
|
+
// rotate by 90° - counter-rotation on the parent makes sure
|
57
|
+
// that without Preliminary Behaviours it still looks right
|
58
|
+
parent.matrix.multiply(new Matrix4().makeRotationZ(Math.PI / 2));
|
59
|
+
model.matrix.multiply(new Matrix4().makeRotationZ(-Math.PI / 2));
|
60
|
+
}
|
61
|
+
|
62
|
+
const lookAt = new BehaviorModel("lookat " + this.name,
|
63
|
+
TriggerBuilder.sceneStartTrigger(),
|
64
|
+
ActionBuilder.lookAtCameraAction(
|
65
|
+
alignmentTarget,
|
66
|
+
undefined,
|
67
|
+
this.invertForward ? USDVec3.back : USDVec3.forward,
|
68
|
+
this.keepUpDirection ? USDVec3.up : USDVec3.zero
|
69
|
+
),
|
70
|
+
);
|
71
|
+
ext.addBehavior(lookAt);
|
72
|
+
}
|
73
|
+
}
|
21
74
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { HalfFloatType
|
1
|
+
import { HalfFloatType } from "three";
|
2
2
|
import { Context } from "../../engine/engine_setup";
|
3
3
|
import { getParam, isMobileDevice } from "../../engine/engine_utils";
|
4
4
|
import { BloomEffect, BrightnessContrastEffect, ChromaticAberrationEffect, DepthDownsamplingPass, DepthOfFieldEffect, Effect, EffectComposer, EffectPass, HueSaturationEffect, NormalPass, Pass, PixelationEffect, RenderPass, SelectiveBloomEffect, SSAOEffect, VignetteEffect } from "postprocessing";
|
@@ -7,10 +7,11 @@
|
|
7
7
|
import { getParam } from "../../engine/engine_utils";
|
8
8
|
import { onChange } from "./Utils";
|
9
9
|
import { foreachComponentEnumerator } from "../../engine/engine_gameobject";
|
10
|
-
import { ICanvas, IRectTransform, IRectTransformChangedReceiver } from "./Interfaces";
|
10
|
+
import { ICanvas, IRectTransform, IRectTransformChangedReceiver, ILayoutGroup } from "./Interfaces";
|
11
11
|
import { GameObject } from '../Component';
|
12
12
|
|
13
13
|
const debug = getParam("debugui");
|
14
|
+
const debugLayout = getParam("debuguilayout");
|
14
15
|
|
15
16
|
export class Size {
|
16
17
|
width!: number;
|
@@ -30,7 +31,7 @@
|
|
30
31
|
|
31
32
|
export class RectTransform extends BaseUIComponent implements IRectTransform, IRectTransformChangedReceiver {
|
32
33
|
|
33
|
-
offset: number = 0.
|
34
|
+
offset: number = 0.1;
|
34
35
|
|
35
36
|
// @serializable(Object3D)
|
36
37
|
// root? : Object3D;
|
@@ -94,14 +95,13 @@
|
|
94
95
|
return this.sizeDelta.y;
|
95
96
|
}
|
96
97
|
|
97
|
-
private lastMatrixWorld!: Matrix4;
|
98
|
+
// private lastMatrixWorld!: Matrix4;
|
98
99
|
private lastMatrix!: Matrix4;
|
99
100
|
private rectBlock!: Object3D;
|
100
101
|
private _transformNeedsUpdate: boolean = false;
|
101
102
|
|
102
103
|
awake() {
|
103
104
|
super.awake();
|
104
|
-
this.lastMatrixWorld = new Matrix4();
|
105
105
|
this.lastMatrix = new Matrix4();
|
106
106
|
this.rectBlock = new Object3D();;
|
107
107
|
this.rectBlock.position.z = .1;
|
@@ -112,9 +112,10 @@
|
|
112
112
|
|
113
113
|
// TODO: we need to replace this with the watch that e.g. Rigibody is using (or the one in utils?)
|
114
114
|
// perhaps we can also just manually check the few properties in the update loops?
|
115
|
-
|
116
|
-
onChange(this, "
|
117
|
-
onChange(this, "
|
115
|
+
// TODO: check if value actually changed, this is called on assignment
|
116
|
+
onChange(this, "_anchoredPosition", () => { this.markDirty(); });
|
117
|
+
onChange(this, "sizeDelta", () => { this.markDirty(); });
|
118
|
+
onChange(this, "pivot", () => { this.markDirty(); });
|
118
119
|
|
119
120
|
// When exported with an anchored position offset we remove it here
|
120
121
|
// because it would otherwise be applied twice when the anchoring is animated
|
@@ -135,29 +136,68 @@
|
|
135
136
|
super.onEnable();
|
136
137
|
this.addShadowComponent(this.rectBlock);
|
137
138
|
this._transformNeedsUpdate = true;
|
139
|
+
this.Canvas?.registerTransform(this);
|
138
140
|
}
|
139
141
|
|
140
142
|
onDisable() {
|
141
143
|
super.onDisable();
|
142
144
|
this.removeShadowComponent();
|
145
|
+
this.Canvas?.unregisterTransform(this);
|
143
146
|
}
|
144
147
|
|
145
|
-
onParentRectTransformChanged(
|
148
|
+
onParentRectTransformChanged(comp: IRectTransform) {
|
149
|
+
if (this._transformNeedsUpdate) return;
|
146
150
|
// When the parent rect transform changes we have to to recalculate our transform
|
151
|
+
this.onApplyTransform(debugLayout ? `${comp.name} changed` : undefined);
|
152
|
+
}
|
153
|
+
|
154
|
+
get isDirty() {
|
155
|
+
if(!this._transformNeedsUpdate) this._transformNeedsUpdate = !this.lastMatrix.equals(this.gameObject.matrix);
|
156
|
+
return this._transformNeedsUpdate;
|
157
|
+
}
|
158
|
+
|
159
|
+
// private _copyMatrixAfterRender: boolean = false;
|
160
|
+
|
161
|
+
markDirty() {
|
162
|
+
if (this._transformNeedsUpdate) return;
|
163
|
+
if (debugLayout) console.warn("RectTransform markDirty()", this.name)
|
147
164
|
this._transformNeedsUpdate = true;
|
148
|
-
|
165
|
+
// If mark dirty is called explictly we want to allow updating the transform again when updateTransform is called
|
166
|
+
// if we dont reset it here we get delayed layout updates
|
167
|
+
this._lastUpdateFrame = -1;
|
149
168
|
}
|
150
169
|
|
170
|
+
|
171
|
+
/** Will update the transforms if it changed or is dirty */
|
172
|
+
updateTransform() {
|
173
|
+
// TODO: instead of checking matrix again it would perhaps be better to test if position, rotation or scale have changed individually?
|
174
|
+
const transformChanged = this._transformNeedsUpdate || !this.lastMatrix.equals(this.gameObject.matrix);// || !this.lastMatrixWorld.equals(this.gameObject.matrixWorld);
|
175
|
+
if (transformChanged && this.canUpdate()) {
|
176
|
+
this.onApplyTransform(this._transformNeedsUpdate ? "Marked dirty" : "Matrix changed");
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
151
180
|
private _parentRectTransform?: RectTransform;
|
181
|
+
private _lastUpdateFrame: number = -1;
|
152
182
|
|
153
|
-
private
|
183
|
+
private canUpdate() {
|
184
|
+
return this._transformNeedsUpdate && this.activeAndEnabled && this._lastUpdateFrame !== this.context.time.frame;
|
185
|
+
}
|
186
|
+
|
187
|
+
private onApplyTransform(reason?: string) {
|
188
|
+
// TODO: need to improve the update logic, with this UI updates have some frame delay but dont happen exponentially per hierarchy
|
189
|
+
if (this.context.time.frameCount === this._lastUpdateFrame) return;
|
190
|
+
this._lastUpdateFrame = this.context.time.frameCount;
|
191
|
+
|
154
192
|
const uiobject = this.shadowComponent;
|
155
193
|
if (!uiobject) return;
|
156
|
-
this._transformNeedsUpdate = false;
|
157
194
|
this._parentRectTransform = GameObject.getComponentInParent(this.gameObject.parent!, RectTransform) as RectTransform;
|
158
195
|
|
159
|
-
|
196
|
+
this._transformNeedsUpdate = false;
|
197
|
+
this.lastMatrix.copy(this.gameObject.matrix);
|
160
198
|
|
199
|
+
if (debugLayout) console.warn("RectTransform → ApplyTransform", this.name + " because " + reason);
|
200
|
+
|
161
201
|
if (!this.isRoot()) {
|
162
202
|
// Reset temp matrix
|
163
203
|
uiobject.matrix.identity();
|
@@ -183,8 +223,7 @@
|
|
183
223
|
tempMatrix.setPosition(tempVec.x, tempVec.y, tempVec.z);
|
184
224
|
uiobject.matrix.premultiply(tempMatrix);
|
185
225
|
// apply scale if necessary
|
186
|
-
|
187
|
-
uiobject.matrix.scale(this.gameObject.scale);
|
226
|
+
uiobject.matrix.scale(this.gameObject.scale);
|
188
227
|
}
|
189
228
|
else {
|
190
229
|
// We have to rotate the canvas when it's in worldspace
|
@@ -192,43 +231,29 @@
|
|
192
231
|
if (!canvas.screenspace) uiobject.rotation.y = Math.PI;
|
193
232
|
}
|
194
233
|
|
195
|
-
this._copyMatrixAfterRender = true;
|
196
|
-
this.lastMatrix.copy(this.gameObject.matrix);
|
197
|
-
|
198
234
|
// iterate other components on this object that might need to know about the transform change
|
199
235
|
// e.g. Graphic components should update their width and height
|
200
236
|
const includeChildren = true;
|
201
|
-
for (const comp of foreachComponentEnumerator(this.gameObject, BaseUIComponent, includeChildren)) {
|
237
|
+
for (const comp of foreachComponentEnumerator(this.gameObject, BaseUIComponent, includeChildren, 1)) {
|
202
238
|
if (comp === this) continue;
|
239
|
+
if (!comp.activeAndEnabled) continue;
|
203
240
|
const callback = comp as any as IRectTransformChangedReceiver;
|
204
|
-
if (callback.onParentRectTransformChanged)
|
241
|
+
if (callback.onParentRectTransformChanged) {
|
242
|
+
// if (debugLayout) console.log(`RectTransform ${this.name} → call`, comp.name + "/" + comp.constructor.name)
|
205
243
|
callback.onParentRectTransformChanged(this);
|
244
|
+
}
|
206
245
|
}
|
207
|
-
}
|
208
246
|
|
209
|
-
|
210
|
-
|
211
|
-
markDirty() {
|
212
|
-
this._transformNeedsUpdate = true;
|
247
|
+
// const layout = GameObject.getComponentInParent(this.gameObject, ILayoutGroup);
|
213
248
|
}
|
214
249
|
|
250
|
+
// onAfterRender() {
|
251
|
+
// if (this._copyMatrixAfterRender) {
|
252
|
+
// // can we only have this event when the transform changed in this frame? Otherwise all RectTransforms will be iterated. Not sure what is better
|
253
|
+
// this.lastMatrixWorld.copy(this.gameObject.matrixWorld);
|
254
|
+
// }
|
255
|
+
// }
|
215
256
|
|
216
|
-
onBeforeRender() {
|
217
|
-
// TODO: instead of checking matrix again it would perhaps be better to test if position, rotation or scale have changed individually?
|
218
|
-
const transformChanged = this.gameObject.matrixWorldNeedsUpdate || this._transformNeedsUpdate || !this.lastMatrixWorld.equals(this.gameObject.matrixWorld) || !this.lastMatrix.equals(this.gameObject.matrix);
|
219
|
-
if (transformChanged)
|
220
|
-
{
|
221
|
-
this.applyTransform();
|
222
|
-
}
|
223
|
-
}
|
224
|
-
|
225
|
-
onAfterRender() {
|
226
|
-
if (this._copyMatrixAfterRender) {
|
227
|
-
// can we only have this event when the transform changed in this frame? Otherwise all RectTransforms will be iterated. Not sure what is better
|
228
|
-
this.lastMatrixWorld.copy(this.gameObject.matrixWorld);
|
229
|
-
}
|
230
|
-
}
|
231
|
-
|
232
257
|
/** applies the position offset to the passed in vector */
|
233
258
|
private applyAnchoring(pos: Vector3) {
|
234
259
|
pos.x += this.anchoredPosition.x;
|
@@ -1,39 +0,0 @@
|
|
1
|
-
import { Object3D, Matrix4, Material, BufferGeometry } from "three";
|
2
|
-
|
3
|
-
// keep in sync with USDZExporter.js
|
4
|
-
|
5
|
-
/** implementation is in three */
|
6
|
-
export declare class USDZDocument {
|
7
|
-
name: string;
|
8
|
-
get isDocumentRoot(): boolean;
|
9
|
-
add(obj: USDZObject);
|
10
|
-
remove(obj: USDZObject);
|
11
|
-
traverse(callback: (obj: USDZObject) => void);
|
12
|
-
findById(uuid: string): USDZObject | undefined;
|
13
|
-
get isDynamic(): boolean;
|
14
|
-
}
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
/** implementation is in three */
|
19
|
-
export declare class USDZObject {
|
20
|
-
static createEmptyParent(parent: USDZObject);
|
21
|
-
uuid: string;
|
22
|
-
name: string;
|
23
|
-
matrix: Matrix4;
|
24
|
-
material: Material;
|
25
|
-
geometry: BufferGeometry;
|
26
|
-
parent: USDZObject | USDZDocument | null;
|
27
|
-
children: USDZObject[];
|
28
|
-
_eventListeners: { [event: string]: Function[] };
|
29
|
-
isDynamic: boolean;
|
30
|
-
|
31
|
-
is(obj: Object3D): boolean;
|
32
|
-
isEmpty(): boolean;
|
33
|
-
clone();
|
34
|
-
getPath();
|
35
|
-
add(child: USDZObject);
|
36
|
-
remove(child: USDZObject);
|
37
|
-
addEventListener(evt: string, listener: Function);
|
38
|
-
removeEventListener(evt: string, listener: Function);
|
39
|
-
}
|
@@ -1,15 +1,15 @@
|
|
1
1
|
import { delay, getParam, isiOS, isMobileDevice, isSafari } from "../../../engine/engine_utils";
|
2
|
-
import { Object3D,
|
3
|
-
import { USDZExporter as ThreeUSDZExporter } from "
|
2
|
+
import { Object3D, Mesh, Matrix4 } from "three";
|
3
|
+
import { USDZExporter as ThreeUSDZExporter } from "./ThreeUSDZExporter";
|
4
4
|
import { AnimationExtension } from "./extensions/Animation"
|
5
5
|
import { ensureQuicklookLinkIsCreated } from "./utils/quicklook";
|
6
6
|
import { getFormattedDate } from "./utils/timeutils";
|
7
7
|
import { registerAnimatorsImplictly } from "./utils/animationutils";
|
8
|
-
import {
|
8
|
+
import { IUSDExporterExtension } from "./Extension";
|
9
9
|
import { Behaviour, GameObject } from "../../Component";
|
10
10
|
import { WebXR } from "../../webxr/WebXR"
|
11
11
|
import { serializable } from "../../../engine/engine_serialization";
|
12
|
-
import {
|
12
|
+
import { showBalloonMessage, showBalloonWarning } from "../../../engine/debug/debug";
|
13
13
|
import { Context } from "../../../engine/engine_setup";
|
14
14
|
import { WebARSessionRoot } from "../../webxr/WebARSessionRoot";
|
15
15
|
import { hasProLicense } from "../../../engine/engine_license";
|
@@ -32,7 +32,7 @@
|
|
32
32
|
export class USDZExporter extends Behaviour {
|
33
33
|
|
34
34
|
@serializable(Object3D)
|
35
|
-
objectToExport?:
|
35
|
+
objectToExport?: Object3D;
|
36
36
|
|
37
37
|
@serializable()
|
38
38
|
autoExportAnimations: boolean = false;
|
@@ -43,13 +43,20 @@
|
|
43
43
|
@serializable(QuickLookOverlay)
|
44
44
|
overlay?: QuickLookOverlay;
|
45
45
|
|
46
|
-
|
46
|
+
// Currently not exposed to integrations - not fully tested. Set from code (e.g. image tracking)
|
47
|
+
@serializable()
|
48
|
+
anchoringType: "plane" | "image" | "face" | "none" = "plane";
|
47
49
|
|
50
|
+
// Currently not exposed to integrations - not fully tested. Set from code (e.g. image tracking)
|
51
|
+
@serializable()
|
52
|
+
planeAnchoringAlignment: "horizontal" | "vertical" | "any" = "horizontal";
|
53
|
+
|
54
|
+
extensions: IUSDExporterExtension[] = [];
|
55
|
+
|
48
56
|
private link!: HTMLAnchorElement;
|
49
57
|
private webxr?: WebXR;
|
50
58
|
private webARSessionRoot: WebARSessionRoot | undefined;
|
51
59
|
|
52
|
-
|
53
60
|
start() {
|
54
61
|
if (debug) {
|
55
62
|
console.log(this);
|
@@ -72,13 +79,11 @@
|
|
72
79
|
this.exportAsync();
|
73
80
|
});
|
74
81
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
if (
|
79
|
-
|
80
|
-
console.warn("USDZExporter has no objects to export assigned:", this)
|
81
|
-
}
|
82
|
+
// fall back to this object or to the scene if it's empty and doesn't have a mesh
|
83
|
+
if (!this.objectToExport)
|
84
|
+
this.objectToExport = this.gameObject;
|
85
|
+
if (!this.objectToExport?.children?.length && !(this.objectToExport as Mesh)?.isMesh)
|
86
|
+
this.objectToExport = this.context.scene;
|
82
87
|
}
|
83
88
|
|
84
89
|
|
@@ -135,13 +140,30 @@
|
|
135
140
|
this.dispatchEvent(new CustomEvent("before-export", { detail: eventArgs }))
|
136
141
|
|
137
142
|
let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
|
138
|
-
if (
|
139
|
-
|
143
|
+
if (!hasProLicense()) name += "-MadeWithNeedle";
|
144
|
+
name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
|
140
145
|
|
141
146
|
//@ts-ignore
|
142
147
|
exporter.debug = debug;
|
148
|
+
|
149
|
+
// sanitize anchoring types
|
150
|
+
if (this.anchoringType !== "plane" && this.anchoringType !== "none" && this.anchoringType !== "image" && this.anchoringType !== "face")
|
151
|
+
this.anchoringType = "plane";
|
152
|
+
if (this.planeAnchoringAlignment !== "horizontal" && this.planeAnchoringAlignment !== "vertical" && this.planeAnchoringAlignment !== "any")
|
153
|
+
this.planeAnchoringAlignment = "horizontal";
|
154
|
+
|
143
155
|
//@ts-ignore
|
144
|
-
const arraybuffer = await exporter.parse(this.objectToExport,
|
156
|
+
const arraybuffer = await exporter.parse(this.objectToExport, {
|
157
|
+
ar: {
|
158
|
+
anchoring: {
|
159
|
+
type: this.anchoringType,
|
160
|
+
}
|
161
|
+
},
|
162
|
+
planeAnchoring: {
|
163
|
+
alignment: this.planeAnchoringAlignment,
|
164
|
+
},
|
165
|
+
extensions: extensions
|
166
|
+
});
|
145
167
|
const blob = new Blob([arraybuffer], { type: 'application/octet-stream' });
|
146
168
|
|
147
169
|
this.dispatchEvent(new CustomEvent("after-export", { detail: eventArgs }))
|
@@ -1,10 +1,14 @@
|
|
1
|
-
import { WebXR } from "./WebXR";
|
1
|
+
import { WebXR, WebXREvent } from "./WebXR";
|
2
2
|
import { serializable } from "../../engine/engine_serialization";
|
3
3
|
import { Behaviour, GameObject } from "../Component";
|
4
4
|
import { Object3D, Quaternion, Vector3 } from "three";
|
5
5
|
import { CircularBuffer, getParam } from "../../engine/engine_utils";
|
6
6
|
import { AssetReference } from "../../engine/engine_addressables";
|
7
|
+
import { showBalloonWarning } from "../../engine/debug";
|
7
8
|
|
9
|
+
import { USDZExporter } from "../../engine-components/export/usdz/USDZExporter";
|
10
|
+
import { USDZExporterContext, USDWriter, imageToCanvas } from "../../engine-components/export/usdz/ThreeUSDZExporter";
|
11
|
+
|
8
12
|
// https://github.com/immersive-web/marker-tracking/blob/main/explainer.md
|
9
13
|
|
10
14
|
const debug = getParam("debugimagetracking");
|
@@ -18,16 +22,6 @@
|
|
18
22
|
readonly measuredSize: number;
|
19
23
|
readonly state: "tracked" | "emulated";
|
20
24
|
|
21
|
-
// private _matrix: Matrix4 | null = null;
|
22
|
-
// private get matrix(): Matrix4 {
|
23
|
-
// if (!this._matrix) {
|
24
|
-
// // this._matrix = WebXRTrackedImage._matrixBuffer.get();
|
25
|
-
// // const matrix = this._pose.transform.matrix;
|
26
|
-
// // this._matrix.fromArray(matrix);
|
27
|
-
// }
|
28
|
-
// return this._matrix!;
|
29
|
-
// }
|
30
|
-
|
31
25
|
/** Copy the image position to a vector */
|
32
26
|
getPosition(vec: Vector3) {
|
33
27
|
this.ensureTransformData();
|
@@ -42,13 +36,23 @@
|
|
42
36
|
return quat;
|
43
37
|
}
|
44
38
|
|
45
|
-
|
39
|
+
private static y180 = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
|
40
|
+
applyToObject(object: Object3D, t01: number | undefined = undefined) {
|
46
41
|
this.ensureTransformData();
|
47
|
-
|
48
|
-
object.quaternion.
|
42
|
+
// check if position/_position or rotation/_rotation changed more than just a little bit
|
43
|
+
const haveChanged = object.position.distanceToSquared(this._position) > 0.05 || object.quaternion.angleTo(this._rotation) > 0.05;
|
44
|
+
if (t01 === undefined || t01 >= 1 || haveChanged) {
|
45
|
+
object.position.copy(this._position);
|
46
|
+
object.quaternion.copy(this._rotation);
|
47
|
+
}
|
48
|
+
else {
|
49
|
+
t01 = Math.max(0, Math.min(1, t01));
|
50
|
+
object.position.lerp(this._position, t01);
|
51
|
+
object.quaternion.slerp(this._rotation, t01);
|
52
|
+
}
|
53
|
+
object.quaternion.multiply(WebXRTrackedImage.y180);
|
49
54
|
}
|
50
55
|
|
51
|
-
// private static _matrixBuffer: CircularBuffer<Matrix4> = new CircularBuffer(() => new Matrix4(), 20);
|
52
56
|
private static _positionBuffer: CircularBuffer<Vector3> = new CircularBuffer(() => new Vector3(), 20);
|
53
57
|
private static _rotationBuffer: CircularBuffer<Quaternion> = new CircularBuffer(() => new Quaternion(), 20);
|
54
58
|
private _position!: Vector3;
|
@@ -58,8 +62,14 @@
|
|
58
62
|
this._position = WebXRTrackedImage._positionBuffer.get();
|
59
63
|
this._rotation = WebXRTrackedImage._rotationBuffer.get();
|
60
64
|
const t = this._pose.transform;
|
61
|
-
|
62
|
-
|
65
|
+
|
66
|
+
// when parented to the world, we need to flip data here
|
67
|
+
//this._position.set(-t.position.x, t.position.y, -t.position.z);
|
68
|
+
// this._rotation.set(-t.orientation.x, t.orientation.y, -t.orientation.z, t.orientation.w);
|
69
|
+
|
70
|
+
// for some reason when parented to the XRRig, we need the original data
|
71
|
+
this._position.set(t.position.x, t.position.y, t.position.z);
|
72
|
+
this._rotation.set(t.orientation.x, t.orientation.y, t.orientation.z, t.orientation.w);
|
63
73
|
}
|
64
74
|
}
|
65
75
|
|
@@ -99,6 +109,32 @@
|
|
99
109
|
imageDoesNotMove: boolean = false;
|
100
110
|
}
|
101
111
|
|
112
|
+
class ImageTrackingExtension {
|
113
|
+
|
114
|
+
get extensionName() { return "image-tracking"; }
|
115
|
+
|
116
|
+
private filename: string;
|
117
|
+
private widthInMeters: number;
|
118
|
+
private imageData: Uint8Array;
|
119
|
+
|
120
|
+
constructor(filename: string, imageData: Uint8Array, widthInMeters: number) {
|
121
|
+
this.filename = filename;
|
122
|
+
this.imageData = imageData;
|
123
|
+
this.widthInMeters = widthInMeters;
|
124
|
+
}
|
125
|
+
|
126
|
+
onAfterHierarchy(_context: USDZExporterContext, writer: USDWriter) {
|
127
|
+
writer.beginBlock(`def Preliminary_ReferenceImage "AnchoringReferenceImage"`);
|
128
|
+
writer.appendLine(`uniform asset image = @tracker/` + this.filename + `@`);
|
129
|
+
writer.appendLine(`uniform double physicalWidth = ` + (this.widthInMeters * 100).toFixed(8));
|
130
|
+
writer.closeBlock();
|
131
|
+
}
|
132
|
+
|
133
|
+
onAfterSerialize(context: USDZExporterContext) {
|
134
|
+
context.files['tracker/' + this.filename] = this.imageData;
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
102
138
|
export class WebXRImageTracking extends Behaviour {
|
103
139
|
|
104
140
|
@serializable(WebXRImageTrackingModel)
|
@@ -121,22 +157,43 @@
|
|
121
157
|
imageElement.addEventListener("load", async () => {
|
122
158
|
const img = await createImageBitmap(imageElement);
|
123
159
|
WebXRImageTracking._imageElements.set(url, img);
|
160
|
+
|
161
|
+
// read back Uint8Array to use in USDZ -
|
162
|
+
// TODO better would be to do that once we actually need it
|
163
|
+
const canvas = await imageToCanvas( img );
|
164
|
+
if (canvas) {
|
165
|
+
const blob = await new Promise( resolve => canvas.toBlob( resolve, 'image/png', 1 ) ) as any;
|
166
|
+
const arrayBuffer = await blob.arrayBuffer();
|
167
|
+
|
168
|
+
const exporter = GameObject.findObjectOfType(USDZExporter);
|
169
|
+
if (exporter) {
|
170
|
+
exporter.extensions.push(
|
171
|
+
new ImageTrackingExtension("marker.png", new Uint8Array(arrayBuffer), this.trackedImages[0].widthInMeters)
|
172
|
+
);
|
173
|
+
exporter.anchoringType = "image";
|
174
|
+
}
|
175
|
+
}
|
124
176
|
});
|
125
177
|
}
|
126
178
|
}
|
127
179
|
}
|
128
180
|
}
|
129
181
|
|
182
|
+
private xr: WebXR | null = null;
|
183
|
+
|
130
184
|
onEnable(): void {
|
131
|
-
|
132
|
-
WebXR.addEventListener(
|
133
|
-
|
185
|
+
this.xr = GameObject.findObjectOfType(WebXR);
|
186
|
+
WebXR.addEventListener(WebXREvent.ModifyAROptions, this.onModifyAROptions);
|
187
|
+
WebXR.addEventListener(WebXREvent.XRStarted, this.onXRStarted);
|
188
|
+
WebXR.addEventListener(WebXREvent.XRUpdate, this.onXRUpdate);
|
189
|
+
this.addEventListener("image-tracking", this.onImageTrackingUpdate);
|
134
190
|
}
|
135
191
|
|
136
192
|
onDisable(): void {
|
137
|
-
WebXR.removeEventListener(
|
138
|
-
WebXR.removeEventListener(
|
139
|
-
|
193
|
+
WebXR.removeEventListener(WebXREvent.ModifyAROptions, this.onModifyAROptions);
|
194
|
+
WebXR.removeEventListener(WebXREvent.XRStarted, this.onXRStarted);
|
195
|
+
WebXR.removeEventListener(WebXREvent.XRUpdate, this.onXRUpdate);
|
196
|
+
this.removeEventListener("image-tracking", this.onImageTrackingUpdate);
|
140
197
|
}
|
141
198
|
|
142
199
|
private onModifyAROptions = (event: any) => {
|
@@ -197,8 +254,12 @@
|
|
197
254
|
|
198
255
|
if (asset) {
|
199
256
|
trackedData!.object = asset;
|
200
|
-
|
201
|
-
|
257
|
+
|
258
|
+
// make sure to parent to the WebXR.rig
|
259
|
+
if (this.xr) {
|
260
|
+
this.xr.Rig.add(asset);
|
261
|
+
}
|
262
|
+
|
202
263
|
image.applyToObject(asset);
|
203
264
|
if (!asset.activeSelf)
|
204
265
|
GameObject.setActive(asset, true);
|
@@ -215,6 +276,10 @@
|
|
215
276
|
|
216
277
|
if (!trackedData.object) continue;
|
217
278
|
|
279
|
+
if (this.xr) {
|
280
|
+
this.xr.Rig.add(trackedData.object);
|
281
|
+
}
|
282
|
+
|
218
283
|
image.applyToObject(trackedData.object);
|
219
284
|
if (!trackedData.object.activeSelf)
|
220
285
|
GameObject.setActive(trackedData.object, true);
|
@@ -229,9 +294,17 @@
|
|
229
294
|
}
|
230
295
|
};
|
231
296
|
|
232
|
-
|
297
|
+
private onXRUpdate = (evt): void => {
|
298
|
+
const frame = evt.frame;
|
299
|
+
if (!frame) return;
|
300
|
+
|
301
|
+
if (frame.session && !("getImageTrackingResults" in frame)) {
|
302
|
+
showBalloonWarning("Image tracking is currently not supported on this device. On Chrome for Android, you can enable the <a href=\"chrome://flags/#webxr-incubations\">chrome://flags/#webxr-incubations</a> flag.");
|
303
|
+
return;
|
304
|
+
}
|
305
|
+
|
233
306
|
//@ts-ignore
|
234
|
-
if (frame
|
307
|
+
if (frame.session && typeof frame.getImageTrackingResults === "function") {
|
235
308
|
//@ts-ignore
|
236
309
|
const results = frame.getImageTrackingResults();
|
237
310
|
if (results.length) {
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import { Object3D, Matrix4, Material, BufferGeometry } from "three";
|
2
|
+
import { ActionBuilder, ActionModel } from "./BehavioursBuilder";
|
3
|
+
import { USDObject, USDDocument } from "../../ThreeUSDZExporter";
|
4
|
+
|
5
|
+
export abstract class DocumentAction {
|
6
|
+
|
7
|
+
get id(): string { return this.object.uuid; }
|
8
|
+
|
9
|
+
protected object: Object3D;
|
10
|
+
protected model?: USDObject;
|
11
|
+
|
12
|
+
constructor(obj: Object3D) {
|
13
|
+
this.object = obj;
|
14
|
+
}
|
15
|
+
|
16
|
+
apply(document: USDDocument) {
|
17
|
+
if (!this.model) {
|
18
|
+
this.model = document.findById(this.object.uuid);
|
19
|
+
if (!this.model) {
|
20
|
+
console.error("could not find model with id " + this.object.uuid);
|
21
|
+
return;
|
22
|
+
}
|
23
|
+
}
|
24
|
+
this.onApply(document);
|
25
|
+
}
|
26
|
+
|
27
|
+
protected abstract onApply(document: USDDocument);
|
28
|
+
}
|
29
|
+
|
30
|
+
export class VariantAction extends DocumentAction {
|
31
|
+
constructor(obj: Object3D, matrix?: Matrix4, material?: Material, geometry?: BufferGeometry) {
|
32
|
+
super(obj);
|
33
|
+
this.matrix = matrix;
|
34
|
+
this.material = material;
|
35
|
+
this.geometry = geometry;
|
36
|
+
}
|
37
|
+
|
38
|
+
private matrix: Matrix4 | undefined;
|
39
|
+
private material: Material | undefined;
|
40
|
+
private geometry: BufferGeometry | undefined;
|
41
|
+
|
42
|
+
protected onApply(_: USDDocument) {
|
43
|
+
const model = this.model;
|
44
|
+
if (!model) return;
|
45
|
+
if (!model.parent?.isDynamic) {
|
46
|
+
USDObject.createEmptyParent(model);
|
47
|
+
}
|
48
|
+
const clone = model.clone();
|
49
|
+
if (this.matrix) clone.matrix = this.matrix;
|
50
|
+
if (this.material) clone.material = this.material;
|
51
|
+
if (this.geometry) clone.geometry = this.geometry;
|
52
|
+
model.parent?.add(clone);
|
53
|
+
}
|
54
|
+
|
55
|
+
private _enableAction?: ActionModel;
|
56
|
+
private _disableAction?: ActionModel;
|
57
|
+
|
58
|
+
enable(): ActionModel {
|
59
|
+
if (this._enableAction) return this._enableAction;
|
60
|
+
this._enableAction = ActionBuilder.fadeAction(this.object, 0, true);;
|
61
|
+
return this._enableAction;
|
62
|
+
}
|
63
|
+
|
64
|
+
disable(): ActionModel {
|
65
|
+
if (this._disableAction) return this._disableAction;
|
66
|
+
this._disableAction = ActionBuilder.fadeAction(this.object, 0, false);;
|
67
|
+
return this._disableAction;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
export class ActionCollection {
|
72
|
+
|
73
|
+
private actions: DocumentAction[];
|
74
|
+
private sortedActions?: { [key: string]: DocumentAction[] };
|
75
|
+
|
76
|
+
constructor(actions: DocumentAction[]) {
|
77
|
+
this.actions = [...actions]
|
78
|
+
}
|
79
|
+
|
80
|
+
// organize is called once when getting an action for the first time
|
81
|
+
// the sorted actions are baked then and adding new actions will not be added anymore
|
82
|
+
private organize() {
|
83
|
+
this.sortedActions = {};
|
84
|
+
for (const action of this.actions) {
|
85
|
+
const id = action.id;
|
86
|
+
if (!this.sortedActions[id]) {
|
87
|
+
this.sortedActions[id] = [];
|
88
|
+
}
|
89
|
+
this.sortedActions[id].push(action);
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
/** returns all document actions affecting the object passed in */
|
94
|
+
getActions(obj: Object3D): DocumentAction[] | null {
|
95
|
+
if (!this.sortedActions) this.organize();
|
96
|
+
return this.sortedActions![obj.uuid];
|
97
|
+
}
|
98
|
+
|
99
|
+
}
|
@@ -0,0 +1,181 @@
|
|
1
|
+
import { Behaviour, GameObject } from "../../../../Component";
|
2
|
+
import { USDZExporter } from "../../USDZExporter";
|
3
|
+
import { IUSDExporterExtension } from "../../Extension";
|
4
|
+
import { USDObject, USDWriter } from "../../ThreeUSDZExporter";
|
5
|
+
import { BehaviorModel } from "./BehavioursBuilder";
|
6
|
+
import { IContext } from "../../../../../engine/engine_types";
|
7
|
+
|
8
|
+
export interface UsdzBehaviour {
|
9
|
+
createBehaviours?(ext: BehaviorExtension, model: USDObject, context: IContext): void;
|
10
|
+
beforeCreateDocument?(ext: BehaviorExtension, context: IContext): void;
|
11
|
+
afterCreateDocument?(ext: BehaviorExtension, context: IContext): void;
|
12
|
+
}
|
13
|
+
|
14
|
+
export class USDZBehaviours extends Behaviour {
|
15
|
+
start() {
|
16
|
+
const exporter = GameObject.findObjectOfType(USDZExporter);
|
17
|
+
if (exporter) {
|
18
|
+
exporter.extensions.push(new BehaviorExtension());
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
export class BehaviorExtension implements IUSDExporterExtension {
|
24
|
+
|
25
|
+
get extensionName(): string {
|
26
|
+
return "Behaviour";
|
27
|
+
}
|
28
|
+
|
29
|
+
private behaviours: BehaviorModel[] = [];
|
30
|
+
|
31
|
+
addBehavior(beh: BehaviorModel) {
|
32
|
+
this.behaviours.push(beh);
|
33
|
+
}
|
34
|
+
|
35
|
+
behaviourComponents: Array<UsdzBehaviour> = [];
|
36
|
+
|
37
|
+
|
38
|
+
onBeforeBuildDocument(context) {
|
39
|
+
context.root.traverse(e => {
|
40
|
+
GameObject.foreachComponent(e, (comp) => {
|
41
|
+
const c = comp as unknown as UsdzBehaviour;
|
42
|
+
if (
|
43
|
+
typeof c.createBehaviours === "function" ||
|
44
|
+
typeof c.beforeCreateDocument === "function" ||
|
45
|
+
typeof c.afterCreateDocument === "function"
|
46
|
+
) {
|
47
|
+
this.behaviourComponents.push(c);
|
48
|
+
c.beforeCreateDocument?.call(c, this, context);
|
49
|
+
}
|
50
|
+
}, false);
|
51
|
+
});
|
52
|
+
}
|
53
|
+
|
54
|
+
onExportObject(_object, model: USDObject, context) {
|
55
|
+
|
56
|
+
for (const beh of this.behaviourComponents) {
|
57
|
+
beh.createBehaviours?.call(beh, this, model, context);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
onAfterBuildDocument(context) {
|
62
|
+
for (const beh of this.behaviourComponents) {
|
63
|
+
if (typeof beh.afterCreateDocument === "function")
|
64
|
+
beh.afterCreateDocument(this, context);
|
65
|
+
}
|
66
|
+
this.behaviourComponents.length = 0;
|
67
|
+
}
|
68
|
+
|
69
|
+
onAfterHierarchy(context, writer : USDWriter) {
|
70
|
+
if (this.behaviours?.length) {
|
71
|
+
|
72
|
+
// this.combineBehavioursWithSameTapActions();
|
73
|
+
|
74
|
+
writer.beginBlock('def Scope "Behaviors"');
|
75
|
+
|
76
|
+
for (const beh of this.behaviours)
|
77
|
+
beh.writeTo(this, context.document, writer);
|
78
|
+
|
79
|
+
writer.closeBlock();
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
// combine behaviours that have tap triggers on the same object
|
84
|
+
// private combineBehavioursWithSameTapActions() {
|
85
|
+
// // TODO: if behaviours have different settings (e.g. one is exclusive and another one is not) this wont work - we need more logic for that
|
86
|
+
|
87
|
+
// const combined: { [key: string]: { behaviorId: string, trigger: TriggerModel, actions: IBehaviorElement[] } } = {};
|
88
|
+
|
89
|
+
// for (let i = this.behaviours.length - 1; i >= 0; i--) {
|
90
|
+
// const beh = this.behaviours[i];
|
91
|
+
// const trigger = beh.trigger as TriggerModel;
|
92
|
+
// if (!Array.isArray(trigger) && TriggerBuilder.isTapTrigger(trigger)) {
|
93
|
+
// const targetObject = trigger.targetId;
|
94
|
+
// if (!targetObject) continue;
|
95
|
+
// if (!combined[targetObject]) {
|
96
|
+
// combined[targetObject] = { behaviorId: beh.id, trigger: trigger, actions: [] };
|
97
|
+
// }
|
98
|
+
// const action = beh.action;
|
99
|
+
// combined[targetObject].actions.push(action);
|
100
|
+
// this.behaviours.splice(i, 1);
|
101
|
+
// }
|
102
|
+
// }
|
103
|
+
// for (const key in combined) {
|
104
|
+
// const val = combined[key];
|
105
|
+
// console.log("Combine " + val.actions.length + " actions on " + val.trigger.id, val.actions);
|
106
|
+
// const beh = new BehaviorModel(val.behaviorId, val.trigger, ActionBuilder.sequence(...val.actions));
|
107
|
+
// this.behaviours.push(beh);
|
108
|
+
// }
|
109
|
+
// }
|
110
|
+
}
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
// const playAnimationOnTap = new BehaviorModel("b_" + model.name + "_playanim", TriggerBuilder.tapTrigger(model),
|
116
|
+
// ActionBuilder.parallel(
|
117
|
+
// ActionBuilder.lookAtCameraAction(model),
|
118
|
+
// ActionBuilder.sequence(
|
119
|
+
// //ActionBuilder.startAnimationAction(model, 0, 0, 1, false, true),
|
120
|
+
// ActionBuilder.emphasize(model, 1, MotionType.Float),
|
121
|
+
// ActionBuilder.waitAction(1),
|
122
|
+
// ActionBuilder.emphasize(model, 1, MotionType.Blink),
|
123
|
+
// ActionBuilder.waitAction(1),
|
124
|
+
// ActionBuilder.emphasize(model, 1, MotionType.Jiggle),
|
125
|
+
// ActionBuilder.waitAction(1),
|
126
|
+
// ActionBuilder.emphasize(model, 1, MotionType.Pulse),
|
127
|
+
// ActionBuilder.waitAction(1),
|
128
|
+
// ActionBuilder.emphasize(model, 1, MotionType.Spin),
|
129
|
+
// ActionBuilder.waitAction(1),
|
130
|
+
// ActionBuilder.emphasize(model, 1, MotionType.Bounce),
|
131
|
+
// ActionBuilder.waitAction(1),
|
132
|
+
// ActionBuilder.emphasize(model, 1, MotionType.Flip),
|
133
|
+
// ActionBuilder.waitAction(1),
|
134
|
+
// ).makeLooping()
|
135
|
+
// ).makeLooping()
|
136
|
+
// );
|
137
|
+
// this.behaviours.push(playAnimationOnTap);
|
138
|
+
// return;
|
139
|
+
|
140
|
+
// const identityMatrix = new Matrix4().identity();
|
141
|
+
|
142
|
+
// const emptyParent = new USDZObject(model.name + "_empty", model.matrix);
|
143
|
+
// const parent = model.parent;
|
144
|
+
// parent.add(emptyParent);
|
145
|
+
// model.matrix = identityMatrix;
|
146
|
+
// emptyParent.add(model);
|
147
|
+
|
148
|
+
|
149
|
+
// const geometry = new THREE.SphereGeometry(.6, 32, 16);
|
150
|
+
// const modelVariant = new USDZObject(model.name + "_variant", identityMatrix, geometry, new MeshStandardMaterial({ color: 0xff0000 }));
|
151
|
+
// emptyParent.add(modelVariant);
|
152
|
+
|
153
|
+
// const matrix2 = new Matrix4();
|
154
|
+
// matrix2.makeTranslation(.5, 0, 0);
|
155
|
+
// const modelVariant2 = new USDZObject(model.name + "_variant2", matrix2, geometry, new MeshStandardMaterial({ color: 0xffff00 }));
|
156
|
+
// emptyParent.add(modelVariant2);
|
157
|
+
|
158
|
+
// const hideVariantOnStart = new BehaviorModel("b_" + model.name + "_start", TriggerBuilder.sceneStartTrigger(), ActionBuilder.fadeAction(modelVariant, 0, false));
|
159
|
+
// this.behaviours.push(hideVariantOnStart);
|
160
|
+
|
161
|
+
// const showVariant = new BehaviorModel("b_" + model.name + "_show_variant", [TriggerBuilder.tapTrigger(model)], new GroupActionModel("group", [
|
162
|
+
// ActionBuilder.fadeAction(model, 0, false),
|
163
|
+
// ActionBuilder.fadeAction(modelVariant, 0, true),
|
164
|
+
// ]));
|
165
|
+
// this.behaviours.push(showVariant);
|
166
|
+
|
167
|
+
// const showOriginal = new BehaviorModel("b_" + model.name + "_show_original", [
|
168
|
+
// TriggerBuilder.tapTrigger(modelVariant),
|
169
|
+
// TriggerBuilder.tapTrigger(modelVariant2)
|
170
|
+
// ],
|
171
|
+
// new GroupActionModel("group", [
|
172
|
+
// ActionBuilder.fadeAction([modelVariant, modelVariant2], 0, false),
|
173
|
+
// //ActionBuilder.waitAction(1),
|
174
|
+
// ActionBuilder.fadeAction(model, 0, true),
|
175
|
+
// //ActionBuilder.waitAction(.2),
|
176
|
+
// ActionBuilder.startAnimationAction(model, 0, 1000, 1, false, true),
|
177
|
+
// //ActionBuilder.lookAtCameraAction(model, 2, Vec3.forward, Vec3.up),
|
178
|
+
// //ActionBuilder.waitAction(1),
|
179
|
+
// //ActionBuilder.fadeAction(modelVariant2, 0, true),
|
180
|
+
// ]).makeSequence());
|
181
|
+
// this.behaviours.push(showOriginal);
|
@@ -0,0 +1,503 @@
|
|
1
|
+
import { Behaviour, GameObject } from "../../../../Component";
|
2
|
+
import { Animator } from "../../../../Animator";
|
3
|
+
import { Renderer } from "../../../../Renderer";
|
4
|
+
import { serializable } from "../../../../../engine/engine_serialization_decorator";
|
5
|
+
import { IPointerClickHandler } from "../../../../ui/PointerEvents";
|
6
|
+
import { RegisteredAnimationInfo, UsdzAnimation } from "../Animation";
|
7
|
+
import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPosition, setWorldQuaternion, setWorldScale } from "../../../../../engine/engine_three_utils";
|
8
|
+
|
9
|
+
import { Object3D, Material, Vector3, Quaternion } from "three";
|
10
|
+
import { USDObject } from "../../ThreeUSDZExporter";
|
11
|
+
|
12
|
+
import { BehaviorExtension, UsdzBehaviour } from "./Behaviour";
|
13
|
+
import { ActionBuilder, ActionModel, BehaviorModel, MotionType, Space, TriggerBuilder } from "./BehavioursBuilder";
|
14
|
+
|
15
|
+
export class ChangeTransformOnClick extends Behaviour implements IPointerClickHandler, UsdzBehaviour {
|
16
|
+
|
17
|
+
@serializable(Object3D)
|
18
|
+
object?: Object3D;
|
19
|
+
|
20
|
+
@serializable(Object3D)
|
21
|
+
target?: Object3D;
|
22
|
+
|
23
|
+
@serializable()
|
24
|
+
duration: number = 1;
|
25
|
+
|
26
|
+
@serializable()
|
27
|
+
relativeMotion: boolean = false;
|
28
|
+
|
29
|
+
private coroutine: Generator | null = null;
|
30
|
+
|
31
|
+
private targetPos = new Vector3();
|
32
|
+
private targetRot = new Quaternion();
|
33
|
+
private targetScale = new Vector3();
|
34
|
+
|
35
|
+
private *moveToTarget() {
|
36
|
+
|
37
|
+
if (!this.target || !this.object) return;
|
38
|
+
|
39
|
+
const thisPos = getWorldPosition(this.object).clone();
|
40
|
+
const targetPos = getWorldPosition(this.target).clone();
|
41
|
+
|
42
|
+
const thisRot = getWorldQuaternion(this.object).clone();
|
43
|
+
const targetRot = getWorldQuaternion(this.target).clone();
|
44
|
+
|
45
|
+
const thisScale = getWorldScale(this.object).clone();
|
46
|
+
const targetScale = getWorldScale(this.target).clone();
|
47
|
+
|
48
|
+
const dist = thisPos.distanceTo(targetPos);
|
49
|
+
const rotDist = thisRot.angleTo(targetRot);
|
50
|
+
const scaleDist = thisScale.distanceTo(targetScale);
|
51
|
+
|
52
|
+
if (dist < 0.01 && rotDist < 0.01 && scaleDist < 0.01) {
|
53
|
+
setWorldPosition(this.object, targetPos);
|
54
|
+
setWorldQuaternion(this.object, targetRot);
|
55
|
+
setWorldScale(this.object, targetScale);
|
56
|
+
this.coroutine = null;
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
|
60
|
+
let t01 = 0;
|
61
|
+
let eased = 0;
|
62
|
+
while (t01 < 1) {
|
63
|
+
|
64
|
+
t01 += this.context.time.deltaTime / this.duration;
|
65
|
+
if (t01 > 1) t01 = 1;
|
66
|
+
|
67
|
+
// apply ease-in-out
|
68
|
+
// https://easings.net/
|
69
|
+
eased = t01 < 0.5 ? 4 * t01 * t01 * t01 : 1 - Math.pow(-2 * t01 + 2, 3) / 2;
|
70
|
+
|
71
|
+
this.targetPos.lerpVectors(thisPos, targetPos, eased);
|
72
|
+
this.targetRot.slerpQuaternions(thisRot, targetRot, eased);
|
73
|
+
this.targetScale.lerpVectors(thisScale, targetScale, eased);
|
74
|
+
|
75
|
+
setWorldPosition(this.object, this.targetPos);
|
76
|
+
setWorldQuaternion(this.object, this.targetRot);
|
77
|
+
setWorldScale(this.object, this.targetScale);
|
78
|
+
|
79
|
+
yield;
|
80
|
+
}
|
81
|
+
|
82
|
+
this.coroutine = null;
|
83
|
+
}
|
84
|
+
|
85
|
+
private *moveRelative() {
|
86
|
+
|
87
|
+
if (!this.target || !this.object) return;
|
88
|
+
|
89
|
+
const thisPos = this.object.position.clone();
|
90
|
+
const thisRot = this.object.quaternion.clone();
|
91
|
+
const thisScale = this.object.scale.clone();
|
92
|
+
|
93
|
+
const posOffset = this.target.position.clone();
|
94
|
+
const rotOffset = this.target.quaternion.clone();
|
95
|
+
const scaleOffset = this.target.scale.clone();
|
96
|
+
|
97
|
+
// convert into right space
|
98
|
+
posOffset.applyQuaternion(this.object.quaternion);
|
99
|
+
|
100
|
+
this.targetPos.copy(this.object.position).add(posOffset);
|
101
|
+
this.targetRot.copy(this.object.quaternion).multiply(rotOffset);
|
102
|
+
this.targetScale.copy(this.object.scale).multiply(scaleOffset);
|
103
|
+
|
104
|
+
let t01 = 0;
|
105
|
+
let eased = 0;
|
106
|
+
while (t01 < 1) {
|
107
|
+
|
108
|
+
t01 += this.context.time.deltaTime / this.duration;
|
109
|
+
if (t01 > 1) t01 = 1;
|
110
|
+
|
111
|
+
// apply ease-in-out
|
112
|
+
// https://easings.net/
|
113
|
+
eased = t01 < 0.5 ? 4 * t01 * t01 * t01 : 1 - Math.pow(-2 * t01 + 2, 3) / 2;
|
114
|
+
|
115
|
+
this.object.position.lerpVectors(thisPos, this.targetPos, eased);
|
116
|
+
this.object.quaternion.slerpQuaternions(thisRot, this.targetRot, eased);
|
117
|
+
this.object.scale.lerpVectors(thisScale, this.targetScale, eased);
|
118
|
+
|
119
|
+
yield;
|
120
|
+
}
|
121
|
+
|
122
|
+
this.coroutine = null;
|
123
|
+
}
|
124
|
+
|
125
|
+
onPointerClick() {
|
126
|
+
if (this.coroutine) this.stopCoroutine(this.coroutine);
|
127
|
+
if (!this.relativeMotion)
|
128
|
+
this.coroutine = this.startCoroutine(this.moveToTarget());
|
129
|
+
else
|
130
|
+
this.coroutine = this.startCoroutine(this.moveRelative());
|
131
|
+
}
|
132
|
+
|
133
|
+
beforeCreateDocument(ext) {
|
134
|
+
if (this.target && this.object && this.gameObject) {
|
135
|
+
const moveForward = new BehaviorModel("Move to " + this.target?.name,
|
136
|
+
TriggerBuilder.tapTrigger(this.gameObject),
|
137
|
+
ActionBuilder.transformAction(this.object, this.target, this.duration, this.relativeMotion ? Space.Relative : Space.Absolute),
|
138
|
+
);
|
139
|
+
ext.addBehavior(moveForward);
|
140
|
+
}
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
export class ChangeMaterialOnClick extends Behaviour implements IPointerClickHandler, UsdzBehaviour {
|
145
|
+
|
146
|
+
@serializable(Material)
|
147
|
+
materialToSwitch?: Material;
|
148
|
+
|
149
|
+
@serializable(Material)
|
150
|
+
variantMaterial?: Material;
|
151
|
+
|
152
|
+
private _objectsWithThisMaterial: Renderer[] = [];
|
153
|
+
|
154
|
+
awake() {
|
155
|
+
if (this.variantMaterial && this.materialToSwitch) {
|
156
|
+
const renderer = GameObject.findObjectsOfType(Renderer);
|
157
|
+
for (const rend of renderer) {
|
158
|
+
if (rend.sharedMaterial === this.materialToSwitch) {
|
159
|
+
this._objectsWithThisMaterial.push(rend);
|
160
|
+
}
|
161
|
+
}
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
onPointerClick() {
|
166
|
+
if (!this.variantMaterial) return;
|
167
|
+
for (const rend of this._objectsWithThisMaterial) {
|
168
|
+
rend.sharedMaterial = this.variantMaterial;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
private selfModel!: USDObject;
|
173
|
+
private targetModels: USDObject[] = [];
|
174
|
+
|
175
|
+
private static _materialTriggersPerId: { [key: string]: ChangeMaterialOnClick[] } = {}
|
176
|
+
|
177
|
+
createBehaviours(_ext: BehaviorExtension, model: USDObject, _context) {
|
178
|
+
|
179
|
+
const shouldExport = this._objectsWithThisMaterial.find(o => o.gameObject.uuid === model.uuid);
|
180
|
+
if (shouldExport) {
|
181
|
+
this.targetModels.push(model);
|
182
|
+
}
|
183
|
+
if (this.gameObject.uuid === model.uuid) {
|
184
|
+
this.selfModel = model;
|
185
|
+
if (this.materialToSwitch) {
|
186
|
+
if (!ChangeMaterialOnClick._materialTriggersPerId[this.materialToSwitch.uuid])
|
187
|
+
ChangeMaterialOnClick._materialTriggersPerId[this.materialToSwitch.uuid] = [];
|
188
|
+
ChangeMaterialOnClick._materialTriggersPerId[this.materialToSwitch.uuid].push(this);
|
189
|
+
}
|
190
|
+
}
|
191
|
+
}
|
192
|
+
|
193
|
+
afterCreateDocument(ext: BehaviorExtension, _context) {
|
194
|
+
|
195
|
+
if (!this.materialToSwitch) return;
|
196
|
+
const handlers = ChangeMaterialOnClick._materialTriggersPerId[this.materialToSwitch.uuid];
|
197
|
+
if (handlers) {
|
198
|
+
const variants: { [key: string]: USDObject[] } = {}
|
199
|
+
for (const handler of handlers) {
|
200
|
+
const createdVariants = handler.createVariants();
|
201
|
+
if (createdVariants && createdVariants.length > 0)
|
202
|
+
variants[handler.selfModel.uuid] = createdVariants;
|
203
|
+
}
|
204
|
+
const otherVariants: any[] = [];
|
205
|
+
for (const handler of handlers) {
|
206
|
+
for (const key in variants) {
|
207
|
+
if (key !== handler.selfModel.uuid) {
|
208
|
+
otherVariants.push(variants[key]);
|
209
|
+
}
|
210
|
+
}
|
211
|
+
handler.createAndAttachBehaviors(ext, variants[handler.selfModel.uuid], otherVariants);
|
212
|
+
}
|
213
|
+
}
|
214
|
+
delete ChangeMaterialOnClick._materialTriggersPerId[this.materialToSwitch.uuid];
|
215
|
+
}
|
216
|
+
|
217
|
+
private createAndAttachBehaviors(ext: BehaviorExtension, myVariants, otherVariants) {
|
218
|
+
const start: ActionModel[] = [];
|
219
|
+
const select: ActionModel[] = [];
|
220
|
+
|
221
|
+
// the order here matters
|
222
|
+
for (const target of this.targetModels) {
|
223
|
+
const hideOriginal = ActionBuilder.fadeAction(target, 0, false);
|
224
|
+
select.push(hideOriginal);
|
225
|
+
}
|
226
|
+
for (const v of otherVariants) {
|
227
|
+
select.push(ActionBuilder.fadeAction(v, 0, false));
|
228
|
+
}
|
229
|
+
for (const v of myVariants) {
|
230
|
+
start.push(ActionBuilder.fadeAction(v, 0, false));
|
231
|
+
select.push(ActionBuilder.fadeAction(v, 0, true));
|
232
|
+
}
|
233
|
+
|
234
|
+
ext.addBehavior(new BehaviorModel("Select " + this.selfModel.name,
|
235
|
+
TriggerBuilder.tapTrigger(this.selfModel),
|
236
|
+
ActionBuilder.parallel(...select))
|
237
|
+
);
|
238
|
+
ext.addBehavior(new BehaviorModel("Start hidden " + this.selfModel.name,
|
239
|
+
TriggerBuilder.sceneStartTrigger(),
|
240
|
+
ActionBuilder.parallel(...start))
|
241
|
+
);
|
242
|
+
}
|
243
|
+
|
244
|
+
private createVariants() {
|
245
|
+
if (!this.variantMaterial) return null;
|
246
|
+
|
247
|
+
const variantModels: USDObject[] = [];
|
248
|
+
for (const target of this.targetModels) {
|
249
|
+
const variant = target.clone();
|
250
|
+
variant.name += " variant_" + this.variantMaterial.name;
|
251
|
+
variant.name = variant.name.replace(/\s/g, "_");
|
252
|
+
variant.material = this.variantMaterial;
|
253
|
+
variant.geometry = target.geometry;
|
254
|
+
variant.matrix = target.matrix;
|
255
|
+
|
256
|
+
if (!target.parent || !target.parent.isEmpty()) {
|
257
|
+
USDObject.createEmptyParent(target);
|
258
|
+
}
|
259
|
+
if (target.parent) target.parent.add(variant);
|
260
|
+
variantModels.push(variant);
|
261
|
+
}
|
262
|
+
return variantModels;
|
263
|
+
}
|
264
|
+
}
|
265
|
+
|
266
|
+
export class SetActiveOnClick extends Behaviour implements IPointerClickHandler, UsdzBehaviour {
|
267
|
+
|
268
|
+
@serializable(Object3D)
|
269
|
+
target?: Object3D;
|
270
|
+
|
271
|
+
// TODO bring back, doesn't work yet
|
272
|
+
private toggleOnClick: boolean = false;
|
273
|
+
|
274
|
+
@serializable()
|
275
|
+
targetState: boolean = true;
|
276
|
+
|
277
|
+
@serializable()
|
278
|
+
hideSelf: boolean = true;
|
279
|
+
|
280
|
+
onPointerClick() {
|
281
|
+
if (!this.toggleOnClick && this.hideSelf)
|
282
|
+
this.gameObject.visible = false;
|
283
|
+
if (this.target)
|
284
|
+
this.target.visible = this.toggleOnClick ? !this.target.visible : this.targetState;
|
285
|
+
}
|
286
|
+
|
287
|
+
private selfModel!: USDObject;
|
288
|
+
private otherModel?: USDObject;
|
289
|
+
private toggleModel?: USDObject;
|
290
|
+
|
291
|
+
createBehaviours(_, model, _context) {
|
292
|
+
if (model.uuid === this.gameObject.uuid) {
|
293
|
+
this.selfModel = model;
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
private stateBeforeCreatingDocument: boolean = false;
|
298
|
+
|
299
|
+
beforeCreateDocument() {
|
300
|
+
this.stateBeforeCreatingDocument = this.gameObject.visible;
|
301
|
+
this.gameObject.visible = true;
|
302
|
+
}
|
303
|
+
|
304
|
+
afterCreateDocument(ext, context) {
|
305
|
+
if (!this.target) return;
|
306
|
+
|
307
|
+
this.otherModel = context.document.findById(this.target.uuid);
|
308
|
+
|
309
|
+
if (this.selfModel && this.otherModel) {
|
310
|
+
let hideClickedObject = this.hideSelf;
|
311
|
+
let targetState = this.targetState;
|
312
|
+
if (this.toggleOnClick) {
|
313
|
+
hideClickedObject = true;
|
314
|
+
targetState = !this.target.visible;
|
315
|
+
|
316
|
+
// TODO check where we have to create the clone; here it doesn't show up
|
317
|
+
this.toggleModel = this.selfModel.clone();
|
318
|
+
this.toggleModel.name += "_toggle";
|
319
|
+
if (this.selfModel.parent)
|
320
|
+
this.selfModel.parent.add(this.toggleModel);
|
321
|
+
}
|
322
|
+
|
323
|
+
const sequence: ActionModel[] = [];
|
324
|
+
if (hideClickedObject)
|
325
|
+
sequence.push(ActionBuilder.fadeAction(this.selfModel, 0, false));
|
326
|
+
if (this.toggleModel)
|
327
|
+
sequence.push(ActionBuilder.fadeAction(this.toggleModel, 0, true));
|
328
|
+
sequence.push(ActionBuilder.fadeAction(this.otherModel, 0, this.targetState));
|
329
|
+
|
330
|
+
ext.addBehavior(new BehaviorModel("Toggle_" + this.selfModel.name + "_hideSelf",
|
331
|
+
TriggerBuilder.tapTrigger(this.selfModel),
|
332
|
+
ActionBuilder.sequence(...sequence)
|
333
|
+
));
|
334
|
+
|
335
|
+
// TODO this is relatively similar to the material variant switching logic, can we reuse that?
|
336
|
+
if (this.toggleOnClick && this.toggleModel) {
|
337
|
+
const toggleSequence: ActionModel[] = [];
|
338
|
+
toggleSequence.push(ActionBuilder.fadeAction(this.toggleModel, 0, false));
|
339
|
+
toggleSequence.push(ActionBuilder.fadeAction(this.selfModel, 0, true));
|
340
|
+
toggleSequence.push(ActionBuilder.fadeAction(this.otherModel, 0, !this.targetState));
|
341
|
+
|
342
|
+
ext.addBehavior(new BehaviorModel("Toggle_" + this.selfModel.name + "_toggleSelf",
|
343
|
+
TriggerBuilder.tapTrigger(this.toggleModel),
|
344
|
+
ActionBuilder.sequence(...toggleSequence)
|
345
|
+
));
|
346
|
+
|
347
|
+
ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
|
348
|
+
TriggerBuilder.sceneStartTrigger(),
|
349
|
+
ActionBuilder.fadeAction(this.toggleModel, 0, false)
|
350
|
+
));
|
351
|
+
}
|
352
|
+
}
|
353
|
+
|
354
|
+
this.gameObject.visible = this.stateBeforeCreatingDocument;
|
355
|
+
}
|
356
|
+
}
|
357
|
+
|
358
|
+
export class HideOnStart extends Behaviour implements UsdzBehaviour {
|
359
|
+
|
360
|
+
start() {
|
361
|
+
this.gameObject.visible = false;
|
362
|
+
}
|
363
|
+
|
364
|
+
createBehaviours(ext, model, _context) {
|
365
|
+
if (model.uuid === this.gameObject.uuid)
|
366
|
+
ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
|
367
|
+
TriggerBuilder.sceneStartTrigger(),
|
368
|
+
ActionBuilder.fadeAction(model, 0, false)
|
369
|
+
));
|
370
|
+
}
|
371
|
+
|
372
|
+
beforeCreateDocument() {
|
373
|
+
this.gameObject.visible = true;
|
374
|
+
}
|
375
|
+
|
376
|
+
afterCreateDocument() {
|
377
|
+
this.gameObject.visible = false;
|
378
|
+
}
|
379
|
+
}
|
380
|
+
|
381
|
+
export class EmphasizeOnClick extends Behaviour implements UsdzBehaviour {
|
382
|
+
|
383
|
+
@serializable()
|
384
|
+
target?: Object3D;
|
385
|
+
|
386
|
+
@serializable()
|
387
|
+
duration: number = 0.5;
|
388
|
+
|
389
|
+
@serializable()
|
390
|
+
motionType: MotionType = MotionType.bounce;
|
391
|
+
|
392
|
+
beforeCreateDocument() { }
|
393
|
+
|
394
|
+
createBehaviours(ext, model, _context) {
|
395
|
+
if (!this.target) return;
|
396
|
+
|
397
|
+
if (model.uuid === this.gameObject.uuid)
|
398
|
+
{
|
399
|
+
const emphasize = new BehaviorModel("emphasize " + this.name,
|
400
|
+
TriggerBuilder.tapTrigger(this.gameObject),
|
401
|
+
ActionBuilder.emphasize(this.target, this.duration, this.motionType, undefined, "basic"),
|
402
|
+
);
|
403
|
+
ext.addBehavior(emphasize);
|
404
|
+
}
|
405
|
+
}
|
406
|
+
|
407
|
+
afterCreateDocument(_ext, _context) { }
|
408
|
+
}
|
409
|
+
|
410
|
+
export class PlayAnimationOnClick extends Behaviour implements IPointerClickHandler, UsdzBehaviour, UsdzAnimation {
|
411
|
+
|
412
|
+
@serializable(Object3D)
|
413
|
+
target?: Object3D;
|
414
|
+
|
415
|
+
@serializable(Animator)
|
416
|
+
animator?: Animator;
|
417
|
+
|
418
|
+
@serializable()
|
419
|
+
stateName?: string;
|
420
|
+
|
421
|
+
onPointerClick() {
|
422
|
+
if (!this.target) return;
|
423
|
+
if (this.stateName)
|
424
|
+
this.animator?.play(this.stateName, 0, 0, .1);
|
425
|
+
}
|
426
|
+
|
427
|
+
private selfModel: any;
|
428
|
+
private registeredAnimationModel: any;
|
429
|
+
private registeredAnimation?: RegisteredAnimationInfo;
|
430
|
+
|
431
|
+
createBehaviours(_ext, model, _context) {
|
432
|
+
if (model.uuid === this.gameObject.uuid)
|
433
|
+
this.selfModel = model;
|
434
|
+
}
|
435
|
+
|
436
|
+
afterCreateDocument(ext, context) {
|
437
|
+
if (!this.registeredAnimation || !this.registeredAnimationModel) return;
|
438
|
+
const document = context.document;
|
439
|
+
document.traverse(model => {
|
440
|
+
if (model.uuid === this.target?.uuid && this.registeredAnimation) {
|
441
|
+
const playAnimationOnTap = new BehaviorModel("tap " + this.name + " for " + this.stateName + " on " + this.target?.name,
|
442
|
+
TriggerBuilder.tapTrigger(this.selfModel),
|
443
|
+
ActionBuilder.startAnimationAction(model, this.registeredAnimation.start, this.registeredAnimation.duration)
|
444
|
+
);
|
445
|
+
ext.addBehavior(playAnimationOnTap);
|
446
|
+
}
|
447
|
+
});
|
448
|
+
}
|
449
|
+
|
450
|
+
createAnimation(ext, model, _context) {
|
451
|
+
if (this.target && this.animator) {
|
452
|
+
const state = this.animator?.runtimeAnimatorController?.findState(this.stateName);
|
453
|
+
this.registeredAnimationModel = model;
|
454
|
+
this.registeredAnimation = ext.registerAnimation(this.target, state?.motion.clip);
|
455
|
+
}
|
456
|
+
}
|
457
|
+
|
458
|
+
}
|
459
|
+
|
460
|
+
export class PreliminaryAction extends Behaviour {
|
461
|
+
getType(): string | void { }
|
462
|
+
@serializable(Object3D)
|
463
|
+
target?: Object3D;
|
464
|
+
|
465
|
+
|
466
|
+
getDuration(): number | void { };
|
467
|
+
}
|
468
|
+
|
469
|
+
export class PreliminaryTrigger extends Behaviour {
|
470
|
+
|
471
|
+
@serializable(PreliminaryAction)
|
472
|
+
target?: PreliminaryAction;
|
473
|
+
}
|
474
|
+
|
475
|
+
export class VisibilityAction extends PreliminaryAction {
|
476
|
+
|
477
|
+
//@type int
|
478
|
+
@serializable()
|
479
|
+
type: VisibilityActionType = VisibilityActionType.Hide;
|
480
|
+
|
481
|
+
@serializable()
|
482
|
+
duration: number = 1;
|
483
|
+
|
484
|
+
getType() {
|
485
|
+
switch (this.type) {
|
486
|
+
case VisibilityActionType.Hide: return "hide";
|
487
|
+
case VisibilityActionType.Show: return "show";
|
488
|
+
}
|
489
|
+
}
|
490
|
+
|
491
|
+
getDuration() {
|
492
|
+
return this.duration;
|
493
|
+
}
|
494
|
+
}
|
495
|
+
|
496
|
+
export class TapGestureTrigger extends PreliminaryTrigger {
|
497
|
+
|
498
|
+
}
|
499
|
+
|
500
|
+
export enum VisibilityActionType {
|
501
|
+
Show = 0,
|
502
|
+
Hide = 1,
|
503
|
+
}
|
@@ -0,0 +1,459 @@
|
|
1
|
+
import { Object3D } from "three";
|
2
|
+
import { USDDocument, USDObject, USDWriter, makeNameSafeForUSD } from "../../ThreeUSDZExporter";
|
3
|
+
|
4
|
+
import { BehaviorExtension } from "./Behaviour";
|
5
|
+
|
6
|
+
// TODO: rename to usdz element
|
7
|
+
export interface IBehaviorElement {
|
8
|
+
id: string;
|
9
|
+
writeTo(document: USDDocument, writer: USDWriter);
|
10
|
+
}
|
11
|
+
|
12
|
+
export class BehaviorModel {
|
13
|
+
|
14
|
+
static global_id: number = 0;
|
15
|
+
id: string;
|
16
|
+
trigger: IBehaviorElement | IBehaviorElement[];
|
17
|
+
action: IBehaviorElement;
|
18
|
+
exclusive: boolean = false;
|
19
|
+
|
20
|
+
makeExclusive(exclusive: boolean): BehaviorModel {
|
21
|
+
this.exclusive = exclusive;
|
22
|
+
return this;
|
23
|
+
}
|
24
|
+
|
25
|
+
constructor(id: string, trigger: IBehaviorElement | IBehaviorElement[], action: IBehaviorElement) {
|
26
|
+
this.id = "Behavior_" + makeNameSafeForUSD(id) + "_" + BehaviorModel.global_id++;
|
27
|
+
this.trigger = trigger;
|
28
|
+
this.action = action;
|
29
|
+
}
|
30
|
+
|
31
|
+
writeTo(_ext: BehaviorExtension, document: USDDocument, writer: USDWriter) {
|
32
|
+
if (!this.trigger || !this.action) return;
|
33
|
+
writer.beginBlock(`def Preliminary_Behavior "${this.id}"`);
|
34
|
+
writer.appendLine(`rel actions = <${this.action.id}>`);
|
35
|
+
writer.appendLine(`uniform bool exclusive = ${this.exclusive}`);
|
36
|
+
let triggerString = "";
|
37
|
+
if (Array.isArray(this.trigger)) {
|
38
|
+
triggerString = "[";
|
39
|
+
for (let i = 0; i < this.trigger.length; i++) {
|
40
|
+
const tr = this.trigger[i];
|
41
|
+
triggerString += "<" + tr.id + ">";
|
42
|
+
if (i + 1 < this.trigger.length) triggerString += ", ";
|
43
|
+
}
|
44
|
+
triggerString += "]";
|
45
|
+
}
|
46
|
+
else
|
47
|
+
triggerString = `<${this.trigger.id}>`;
|
48
|
+
|
49
|
+
writer.appendLine(`rel triggers = ${triggerString} `);
|
50
|
+
writer.appendLine();
|
51
|
+
if (Array.isArray(this.trigger)) {
|
52
|
+
for (const trigger of this.trigger) {
|
53
|
+
trigger.writeTo(document, writer);
|
54
|
+
writer.appendLine();
|
55
|
+
}
|
56
|
+
}
|
57
|
+
else
|
58
|
+
this.trigger.writeTo(document, writer);
|
59
|
+
writer.appendLine();
|
60
|
+
this.action.writeTo(document, writer);
|
61
|
+
writer.closeBlock();
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
|
66
|
+
type Target = USDObject | USDObject[] | Object3D | Object3D[];
|
67
|
+
|
68
|
+
/** called to resolve target objects to usdz paths */
|
69
|
+
function resolve(targetObject: Target, document: USDDocument): string {
|
70
|
+
let result: string = "";
|
71
|
+
if (Array.isArray(targetObject)) {
|
72
|
+
let str = "[ ";
|
73
|
+
for (let i = 0; i < targetObject.length; i++) {
|
74
|
+
let obj = targetObject[i];
|
75
|
+
if (typeof obj === "string")
|
76
|
+
str += obj;
|
77
|
+
else if ( typeof obj === "object") {
|
78
|
+
//@ts-ignore
|
79
|
+
if (obj.isObject3D) {
|
80
|
+
//@ts-ignore
|
81
|
+
obj = document.findById(obj.uuid);
|
82
|
+
}
|
83
|
+
const res = (obj as any).getPath?.call(obj) as string;
|
84
|
+
str += res;
|
85
|
+
}
|
86
|
+
if (i + 1 < targetObject.length) str += ", ";
|
87
|
+
}
|
88
|
+
str += " ]";
|
89
|
+
result = str;
|
90
|
+
}
|
91
|
+
else if (typeof targetObject === "object") {
|
92
|
+
//@ts-ignore
|
93
|
+
if (targetObject.isObject3D) {
|
94
|
+
//@ts-ignore
|
95
|
+
targetObject = document.findById(targetObject.uuid);
|
96
|
+
}
|
97
|
+
result = (targetObject as any).getPath?.call(targetObject) as string;
|
98
|
+
}
|
99
|
+
|
100
|
+
// in three there's now a new "Scenes" parent and "Scene" xform that's injected;
|
101
|
+
// we need to add this to our path here so that we have full paths
|
102
|
+
result = result.replace(document.name, document.name + "/Scenes/Scene");
|
103
|
+
return result;
|
104
|
+
}
|
105
|
+
|
106
|
+
export class TriggerModel implements IBehaviorElement {
|
107
|
+
static global_id: number = 0;
|
108
|
+
|
109
|
+
id: string;
|
110
|
+
targetId?: string | Target;
|
111
|
+
tokenId?: string;
|
112
|
+
type?: string;
|
113
|
+
|
114
|
+
constructor(targetId?: string | Target, id?: string) {
|
115
|
+
if (targetId) this.targetId = targetId;
|
116
|
+
if (id) this.id = id;
|
117
|
+
else this.id = "Trigger_" + TriggerModel.global_id++;
|
118
|
+
}
|
119
|
+
|
120
|
+
writeTo(document: USDDocument, writer: USDWriter) {
|
121
|
+
writer.beginBlock(`def Preliminary_Trigger "${this.id}"`);
|
122
|
+
if (this.targetId) {
|
123
|
+
if (typeof this.targetId !== "string") this.targetId = resolve(this.targetId, document);
|
124
|
+
writer.appendLine(`rel affectedObjects = ` + this.targetId);
|
125
|
+
}
|
126
|
+
if (this.tokenId)
|
127
|
+
writer.appendLine(`token info:id = "${this.tokenId}"`);
|
128
|
+
if (this.type)
|
129
|
+
writer.appendLine(`token type = "${this.type}"`);
|
130
|
+
writer.closeBlock();
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
export class TriggerBuilder {
|
135
|
+
|
136
|
+
static sceneStartTrigger(): TriggerModel {
|
137
|
+
const trigger = new TriggerModel();
|
138
|
+
trigger.targetId = undefined;
|
139
|
+
trigger.tokenId = "SceneTransition";
|
140
|
+
trigger.type = "enter";
|
141
|
+
return trigger;
|
142
|
+
}
|
143
|
+
|
144
|
+
static tapTrigger(targetObject: Target): TriggerModel {
|
145
|
+
const trigger = new TriggerModel(targetObject);
|
146
|
+
trigger.tokenId = "TapGesture";
|
147
|
+
return trigger;
|
148
|
+
}
|
149
|
+
|
150
|
+
static isTapTrigger(trigger?: TriggerModel) {
|
151
|
+
return trigger?.tokenId === "TapGesture";
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
export class GroupActionModel implements IBehaviorElement {
|
156
|
+
|
157
|
+
static global_id: number = 0;
|
158
|
+
static getId(): number {
|
159
|
+
return this.global_id++;
|
160
|
+
}
|
161
|
+
|
162
|
+
id: string;
|
163
|
+
actions: IBehaviorElement[];
|
164
|
+
loops: number = 0;
|
165
|
+
performCount: number = 1;
|
166
|
+
type: string = "serial";
|
167
|
+
|
168
|
+
constructor(id: string, actions: IBehaviorElement[]) {
|
169
|
+
this.id = id;
|
170
|
+
this.actions = actions;
|
171
|
+
}
|
172
|
+
|
173
|
+
addAction(el: IBehaviorElement): GroupActionModel {
|
174
|
+
this.actions.push(el);
|
175
|
+
return this;
|
176
|
+
}
|
177
|
+
|
178
|
+
makeParallel(): GroupActionModel {
|
179
|
+
this.type = "parallel";
|
180
|
+
return this;
|
181
|
+
}
|
182
|
+
|
183
|
+
makeSequence(): GroupActionModel {
|
184
|
+
this.type = "serial";
|
185
|
+
return this;
|
186
|
+
}
|
187
|
+
|
188
|
+
makeLooping() {
|
189
|
+
this.loops = 1;
|
190
|
+
return this;
|
191
|
+
}
|
192
|
+
|
193
|
+
makeRepeat(count: number) {
|
194
|
+
this.performCount = count;
|
195
|
+
return this;
|
196
|
+
}
|
197
|
+
|
198
|
+
writeTo(document: USDDocument, writer: USDWriter) {
|
199
|
+
writer.beginBlock(`def Preliminary_Action "${this.id}"`);
|
200
|
+
writer.beginArray("rel actions");
|
201
|
+
for (const act of this.actions) {
|
202
|
+
if (!act) continue;
|
203
|
+
writer.appendLine("<" + act.id + ">,");
|
204
|
+
}
|
205
|
+
writer.closeArray();
|
206
|
+
writer.appendLine();
|
207
|
+
|
208
|
+
writer.appendLine(`token info:id = "Group"`);
|
209
|
+
writer.appendLine(`bool loops = ${this.loops} `);
|
210
|
+
writer.appendLine(`int performCount = ${this.performCount} `);
|
211
|
+
writer.appendLine(`token type = "${this.type}"`);
|
212
|
+
writer.appendLine();
|
213
|
+
|
214
|
+
for (const act of this.actions) {
|
215
|
+
if (!act) continue;
|
216
|
+
act.writeTo(document, writer);
|
217
|
+
writer.appendLine();
|
218
|
+
}
|
219
|
+
|
220
|
+
writer.closeBlock();
|
221
|
+
}
|
222
|
+
}
|
223
|
+
|
224
|
+
export enum MotionType {
|
225
|
+
pop = 0,
|
226
|
+
blink = 1,
|
227
|
+
bounce = 2,
|
228
|
+
flip = 3,
|
229
|
+
float = 4,
|
230
|
+
jiggle = 5,
|
231
|
+
pulse = 6,
|
232
|
+
spin = 7,
|
233
|
+
};
|
234
|
+
|
235
|
+
export enum Space {
|
236
|
+
Relative = "relative",
|
237
|
+
Absolute = "absolute"
|
238
|
+
};
|
239
|
+
|
240
|
+
export class ActionModel implements IBehaviorElement {
|
241
|
+
|
242
|
+
private static global_id: number = 0;
|
243
|
+
|
244
|
+
id: string;
|
245
|
+
tokenId?: string;
|
246
|
+
affectedObjects?: string | Target;
|
247
|
+
easeType?: string;;
|
248
|
+
motionType?: string;
|
249
|
+
duration?: number;
|
250
|
+
moveDistance?: number;
|
251
|
+
style?: string;
|
252
|
+
type?: string;
|
253
|
+
front?: Vec3;
|
254
|
+
up?: Vec3;
|
255
|
+
start?: number;
|
256
|
+
animationSpeed?: number;
|
257
|
+
reversed?: boolean;
|
258
|
+
pingPong?: boolean;
|
259
|
+
xFormTarget?: Target | string;
|
260
|
+
|
261
|
+
clone(): ActionModel {
|
262
|
+
const copy = new ActionModel();
|
263
|
+
const id = copy.id;
|
264
|
+
Object.assign(copy, this);
|
265
|
+
copy.id = id;
|
266
|
+
return copy;
|
267
|
+
}
|
268
|
+
|
269
|
+
constructor(affectedObjects?: string | Target, id?: string) {
|
270
|
+
if (affectedObjects) this.affectedObjects = affectedObjects;
|
271
|
+
if (id) this.id = id;
|
272
|
+
/*else if (affectedObjects) {
|
273
|
+
this.id = "Action_" + sanitizeId(affectedObjects);
|
274
|
+
}
|
275
|
+
else */
|
276
|
+
else
|
277
|
+
this.id = "Action";
|
278
|
+
this.id += "_" + ActionModel.global_id++;
|
279
|
+
}
|
280
|
+
|
281
|
+
writeTo(document: USDDocument, writer: USDWriter) {
|
282
|
+
writer.beginBlock(`def Preliminary_Action "${this.id}"`);
|
283
|
+
if (this.affectedObjects) {
|
284
|
+
if (typeof this.affectedObjects !== "string") this.affectedObjects = resolve(this.affectedObjects, document);
|
285
|
+
writer.appendLine('rel affectedObjects = ' + this.affectedObjects);
|
286
|
+
}
|
287
|
+
if (typeof this.duration === "number")
|
288
|
+
writer.appendLine(`double duration = ${this.duration} `);
|
289
|
+
if (this.easeType)
|
290
|
+
writer.appendLine(`token easeType = "${this.easeType}"`);
|
291
|
+
if (this.tokenId)
|
292
|
+
writer.appendLine(`token info:id = "${this.tokenId}"`);
|
293
|
+
if (this.motionType)
|
294
|
+
writer.appendLine(`token motionType = "${this.motionType}"`);
|
295
|
+
if (typeof this.moveDistance === "number")
|
296
|
+
writer.appendLine(`double moveDistance = ${this.moveDistance} `);
|
297
|
+
if (this.style)
|
298
|
+
writer.appendLine(`token style = "${this.style}"`);
|
299
|
+
if (this.type)
|
300
|
+
writer.appendLine(`token type = "${this.type}"`);
|
301
|
+
if (this.front)
|
302
|
+
writer.appendLine(`vector3d front = (${this.front.x}, ${this.front.y}, ${this.front.z})`);
|
303
|
+
if (this.up)
|
304
|
+
writer.appendLine(`vector3d upVector = (${this.up.x}, ${this.up.y}, ${this.up.z})`);
|
305
|
+
if (typeof this.start === "number") {
|
306
|
+
writer.appendLine(`double start = ${this.start} `);
|
307
|
+
}
|
308
|
+
if (typeof this.animationSpeed === "number") {
|
309
|
+
writer.appendLine(`double animationSpeed = ${this.animationSpeed} `);
|
310
|
+
}
|
311
|
+
if (typeof this.reversed === "boolean") {
|
312
|
+
writer.appendLine(`bool reversed = ${this.reversed}`)
|
313
|
+
}
|
314
|
+
if (typeof this.pingPong === "boolean") {
|
315
|
+
writer.appendLine(`bool reverses = ${this.pingPong}`)
|
316
|
+
}
|
317
|
+
if (this.xFormTarget) {
|
318
|
+
if (typeof this.xFormTarget !== "string")
|
319
|
+
this.xFormTarget = resolve(this.xFormTarget, document);
|
320
|
+
writer.appendLine(`rel xformTarget = ${this.xFormTarget}`)
|
321
|
+
}
|
322
|
+
writer.closeBlock();
|
323
|
+
}
|
324
|
+
}
|
325
|
+
|
326
|
+
class Vec3 {
|
327
|
+
x: number = 0;
|
328
|
+
y: number = 0;
|
329
|
+
z: number = 0;
|
330
|
+
|
331
|
+
constructor(x: number, y: number, z: number) {
|
332
|
+
this.x = x;
|
333
|
+
this.y = y;
|
334
|
+
this.z = z;
|
335
|
+
}
|
336
|
+
|
337
|
+
static get up(): Vec3 {
|
338
|
+
return new Vec3(0, 1, 0);
|
339
|
+
}
|
340
|
+
|
341
|
+
static get right(): Vec3 {
|
342
|
+
return new Vec3(1, 0, 0);
|
343
|
+
}
|
344
|
+
|
345
|
+
static get forward(): Vec3 {
|
346
|
+
return new Vec3(0, 0, 1);
|
347
|
+
}
|
348
|
+
|
349
|
+
static get back(): Vec3 {
|
350
|
+
return new Vec3(0, 0, -1);
|
351
|
+
}
|
352
|
+
|
353
|
+
static get zero(): Vec3 {
|
354
|
+
return new Vec3(0, 0, 0);
|
355
|
+
}
|
356
|
+
}
|
357
|
+
|
358
|
+
export class ActionBuilder {
|
359
|
+
|
360
|
+
static sequence(...params: IBehaviorElement[]) {
|
361
|
+
const group = new GroupActionModel("group_" + GroupActionModel.getId(), params);
|
362
|
+
return group.makeSequence();
|
363
|
+
}
|
364
|
+
|
365
|
+
static parallel(...params: IBehaviorElement[]) {
|
366
|
+
const group = new GroupActionModel("group_" + GroupActionModel.getId(), params);
|
367
|
+
return group.makeParallel();
|
368
|
+
}
|
369
|
+
|
370
|
+
static fadeAction(targetObject: Target, duration: number, show: boolean): ActionModel {
|
371
|
+
const act = new ActionModel(targetObject);
|
372
|
+
act.tokenId = "Visibility";
|
373
|
+
act.type = show ? "show" : "hide";
|
374
|
+
act.duration = duration;
|
375
|
+
|
376
|
+
act.style = "basic";
|
377
|
+
act.motionType = "none";
|
378
|
+
act.moveDistance = 0;
|
379
|
+
act.easeType = "none";
|
380
|
+
return act;
|
381
|
+
}
|
382
|
+
|
383
|
+
/**
|
384
|
+
* creates an action that plays an animation
|
385
|
+
* @param start offset in seconds!
|
386
|
+
* @param duration in seconds! 0 means play to end
|
387
|
+
*/
|
388
|
+
static startAnimationAction(targetObject: Target, start: number, duration: number = 0, animationSpeed: number = 1, reversed: boolean = false, pingPong: boolean = false): IBehaviorElement {
|
389
|
+
const act = new ActionModel(targetObject);
|
390
|
+
act.tokenId = "StartAnimation";
|
391
|
+
act.start = start;
|
392
|
+
// start is time in seconds, the documentation is not right here
|
393
|
+
act.duration = duration;
|
394
|
+
// duration of 0 is play to end
|
395
|
+
act.animationSpeed = animationSpeed;
|
396
|
+
act.reversed = reversed;
|
397
|
+
act.pingPong = pingPong;
|
398
|
+
if (reversed) {
|
399
|
+
act.start -= duration;
|
400
|
+
//console.warn("Reversed animation does currently not work. The resulting file will most likely not playback.", act.id, targetObject);
|
401
|
+
}
|
402
|
+
if (pingPong) {
|
403
|
+
act.pingPong = false;
|
404
|
+
const back = act.clone();
|
405
|
+
back.reversed = !reversed;
|
406
|
+
back.start = act.start;
|
407
|
+
if (back.reversed) {
|
408
|
+
back.start -= duration;
|
409
|
+
}
|
410
|
+
const group = ActionBuilder.sequence(act, back);
|
411
|
+
return group;
|
412
|
+
}
|
413
|
+
return act;
|
414
|
+
}
|
415
|
+
|
416
|
+
static waitAction(duration: number): ActionModel {
|
417
|
+
const act = new ActionModel();
|
418
|
+
act.tokenId = "Wait";
|
419
|
+
act.duration = duration;
|
420
|
+
return act;
|
421
|
+
}
|
422
|
+
|
423
|
+
static lookAtCameraAction(targets: Target, duration: number = 9999999999999, front?: Vec3, up?: Vec3): ActionModel {
|
424
|
+
const act = new ActionModel(targets);
|
425
|
+
act.tokenId = "LookAtCamera";
|
426
|
+
act.duration = duration;
|
427
|
+
act.front = front ?? Vec3.forward;
|
428
|
+
// 0,0,0 is a special case for "free look"
|
429
|
+
// 0,1,0 is for "y-locked look-at"
|
430
|
+
act.up = up ?? Vec3.up;
|
431
|
+
return act;
|
432
|
+
}
|
433
|
+
|
434
|
+
static emphasize(targets: Target, duration: number, motionType: MotionType = MotionType.bounce, moveDistance: number = 1, style: string = "basic") {
|
435
|
+
const act = new ActionModel(targets);
|
436
|
+
act.tokenId = "Emphasize";
|
437
|
+
act.duration = duration;
|
438
|
+
act.style = style ?? "basic";
|
439
|
+
act.motionType = MotionType[motionType];
|
440
|
+
act.moveDistance = moveDistance;
|
441
|
+
return act;
|
442
|
+
}
|
443
|
+
|
444
|
+
static transformAction(targets: Target, transformTarget: Target, duration: number, transformType: Space, easeType: string = "inout") {
|
445
|
+
const act = new ActionModel(targets);
|
446
|
+
act.tokenId = "Transform";
|
447
|
+
act.duration = duration;
|
448
|
+
act.type = transformType;
|
449
|
+
act.easeType = easeType;
|
450
|
+
if (Array.isArray(transformTarget)) {
|
451
|
+
console.error("Transform target must not be an array", transformTarget);
|
452
|
+
}
|
453
|
+
act.xFormTarget = transformTarget;
|
454
|
+
return act;
|
455
|
+
}
|
456
|
+
|
457
|
+
}
|
458
|
+
|
459
|
+
export { Vec3 as USDVec3 }
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { IUSDExporterExtension } from "../Extension";
|
2
|
+
|
3
|
+
export class DocumentExtension implements IUSDExporterExtension {
|
4
|
+
|
5
|
+
get extensionName(): string {
|
6
|
+
return "DocumentExtension";
|
7
|
+
}
|
8
|
+
|
9
|
+
onAfterBuildDocument(_context: any) { }
|
10
|
+
}
|
@@ -0,0 +1,1280 @@
|
|
1
|
+
import {
|
2
|
+
PlaneGeometry,
|
3
|
+
Texture,
|
4
|
+
Uniform,
|
5
|
+
PerspectiveCamera,
|
6
|
+
Scene,
|
7
|
+
Mesh,
|
8
|
+
ShaderMaterial,
|
9
|
+
WebGLRenderer,
|
10
|
+
MathUtils,
|
11
|
+
Matrix4,
|
12
|
+
RepeatWrapping,
|
13
|
+
MirroredRepeatWrapping,
|
14
|
+
DoubleSide,
|
15
|
+
BufferGeometry,
|
16
|
+
Material,
|
17
|
+
Camera,
|
18
|
+
Color,
|
19
|
+
MeshStandardMaterial,
|
20
|
+
} from 'three';
|
21
|
+
import * as fflate from 'three/examples/jsm/libs/fflate.module.js';
|
22
|
+
|
23
|
+
function makeNameSafe( str ) {
|
24
|
+
str = str.replace( /[^a-zA-Z0-9_]/g, '' );
|
25
|
+
|
26
|
+
// if str does not start with a-zA-Z_ add _ to the beginning
|
27
|
+
if ( !str.match( /^[a-zA-Z_]/ ) )
|
28
|
+
str = '_' + str;
|
29
|
+
|
30
|
+
return str;
|
31
|
+
}
|
32
|
+
|
33
|
+
class USDObject {
|
34
|
+
|
35
|
+
static USDObject_export_id = 0;
|
36
|
+
|
37
|
+
uuid: string;
|
38
|
+
name: string;
|
39
|
+
matrix: Matrix4;
|
40
|
+
private _isDynamic: boolean;
|
41
|
+
get isDynamic() { return this._isDynamic; }
|
42
|
+
private set isDynamic( value ) { this._isDynamic = value; }
|
43
|
+
geometry: BufferGeometry | null;
|
44
|
+
material: Material | null;
|
45
|
+
camera: Camera | null;
|
46
|
+
parent: USDObject | null;
|
47
|
+
children: Array<USDObject | null> = [];
|
48
|
+
_eventListeners: {};
|
49
|
+
mesh: any;
|
50
|
+
|
51
|
+
static createEmptyParent( object ) {
|
52
|
+
|
53
|
+
const emptyParent = new USDObject( MathUtils.generateUUID(), object.name + '_empty_' + ( USDObject.USDObject_export_id ++ ), object.matrix );
|
54
|
+
const parent = object.parent;
|
55
|
+
parent.add( emptyParent );
|
56
|
+
emptyParent.add( object );
|
57
|
+
emptyParent.isDynamic = true;
|
58
|
+
object.matrix = new Matrix4().identity();
|
59
|
+
return emptyParent;
|
60
|
+
|
61
|
+
}
|
62
|
+
|
63
|
+
constructor( id, name, matrix, mesh: BufferGeometry | null = null, material: Material | null = null, camera: Camera | null = null ) {
|
64
|
+
|
65
|
+
this.uuid = id;
|
66
|
+
this.name = makeNameSafe( name );
|
67
|
+
this.matrix = matrix;
|
68
|
+
this.geometry = mesh;
|
69
|
+
this.material = material;
|
70
|
+
this.camera = camera;
|
71
|
+
this.parent = null;
|
72
|
+
this.children = [];
|
73
|
+
this._eventListeners = {};
|
74
|
+
this._isDynamic = false;
|
75
|
+
|
76
|
+
}
|
77
|
+
|
78
|
+
is( obj ) {
|
79
|
+
|
80
|
+
if ( ! obj ) return false;
|
81
|
+
return this.uuid === obj.uuid;
|
82
|
+
|
83
|
+
}
|
84
|
+
|
85
|
+
isEmpty() {
|
86
|
+
|
87
|
+
return ! this.geometry;
|
88
|
+
|
89
|
+
}
|
90
|
+
|
91
|
+
clone() {
|
92
|
+
|
93
|
+
const clone = new USDObject( MathUtils.generateUUID(), this.name, this.matrix, this.mesh, this.material );
|
94
|
+
clone.isDynamic = this.isDynamic;
|
95
|
+
return clone;
|
96
|
+
|
97
|
+
}
|
98
|
+
|
99
|
+
getPath() {
|
100
|
+
|
101
|
+
let current = this.parent;
|
102
|
+
let path = this.name;
|
103
|
+
while ( current ) {
|
104
|
+
|
105
|
+
path = current.name + '/' + path;
|
106
|
+
current = current.parent;
|
107
|
+
|
108
|
+
}
|
109
|
+
|
110
|
+
return '</' + path + '>';
|
111
|
+
|
112
|
+
}
|
113
|
+
|
114
|
+
add( child ) {
|
115
|
+
|
116
|
+
if ( child.parent ) {
|
117
|
+
|
118
|
+
child.parent.remove( child );
|
119
|
+
|
120
|
+
}
|
121
|
+
|
122
|
+
child.parent = this;
|
123
|
+
this.children.push( child );
|
124
|
+
|
125
|
+
}
|
126
|
+
|
127
|
+
remove( child ) {
|
128
|
+
|
129
|
+
const index = this.children.indexOf( child );
|
130
|
+
if ( index >= 0 ) {
|
131
|
+
|
132
|
+
if ( child.parent === this ) child.parent = null;
|
133
|
+
this.children.splice( index, 1 );
|
134
|
+
|
135
|
+
}
|
136
|
+
|
137
|
+
}
|
138
|
+
|
139
|
+
addEventListener( evt, listener ) {
|
140
|
+
|
141
|
+
if ( ! this._eventListeners[ evt ] ) this._eventListeners[ evt ] = [];
|
142
|
+
this._eventListeners[ evt ].push( listener );
|
143
|
+
|
144
|
+
}
|
145
|
+
|
146
|
+
removeEventListener( evt, listener ) {
|
147
|
+
|
148
|
+
if ( ! this._eventListeners[ evt ] ) return;
|
149
|
+
const index = this._eventListeners[ evt ].indexOf( listener );
|
150
|
+
if ( index >= 0 ) {
|
151
|
+
|
152
|
+
this._eventListeners[ evt ].splice( index, 1 );
|
153
|
+
|
154
|
+
}
|
155
|
+
|
156
|
+
}
|
157
|
+
|
158
|
+
onSerialize( writer, context ) {
|
159
|
+
|
160
|
+
const listeners = this._eventListeners[ 'serialize' ];
|
161
|
+
if ( listeners ) listeners.forEach( listener => listener( writer, context ) );
|
162
|
+
|
163
|
+
}
|
164
|
+
|
165
|
+
}
|
166
|
+
|
167
|
+
|
168
|
+
class USDDocument extends USDObject {
|
169
|
+
|
170
|
+
stageLength: number;
|
171
|
+
|
172
|
+
get isDocumentRoot() {
|
173
|
+
|
174
|
+
return true;
|
175
|
+
|
176
|
+
}
|
177
|
+
get isDynamic() {
|
178
|
+
|
179
|
+
return false;
|
180
|
+
|
181
|
+
}
|
182
|
+
|
183
|
+
constructor() {
|
184
|
+
|
185
|
+
super(undefined, 'StageRoot', new Matrix4(), null, null, null);
|
186
|
+
this.children = [];
|
187
|
+
this.stageLength = 200;
|
188
|
+
|
189
|
+
}
|
190
|
+
|
191
|
+
add( child: USDObject ) {
|
192
|
+
|
193
|
+
child.parent = this;
|
194
|
+
this.children.push( child );
|
195
|
+
|
196
|
+
}
|
197
|
+
|
198
|
+
remove( child: USDObject ) {
|
199
|
+
|
200
|
+
const index = this.children.indexOf( child );
|
201
|
+
if ( index >= 0 ) {
|
202
|
+
|
203
|
+
if ( child.parent === this ) child.parent = null;
|
204
|
+
this.children.splice( index, 1 );
|
205
|
+
|
206
|
+
}
|
207
|
+
|
208
|
+
}
|
209
|
+
|
210
|
+
traverse( callback, current: USDObject | null = null ) {
|
211
|
+
|
212
|
+
if ( current !== null ) callback( current );
|
213
|
+
else current = this;
|
214
|
+
if ( current.children ) {
|
215
|
+
|
216
|
+
for ( const child of current.children ) {
|
217
|
+
|
218
|
+
this.traverse( callback, child );
|
219
|
+
|
220
|
+
}
|
221
|
+
|
222
|
+
}
|
223
|
+
|
224
|
+
}
|
225
|
+
|
226
|
+
findById( uuid ) {
|
227
|
+
|
228
|
+
let found = false;
|
229
|
+
function search( current ) {
|
230
|
+
|
231
|
+
if ( found ) return;
|
232
|
+
if ( current.uuid === uuid ) {
|
233
|
+
|
234
|
+
found = true;
|
235
|
+
return current;
|
236
|
+
|
237
|
+
}
|
238
|
+
|
239
|
+
if ( current.children ) {
|
240
|
+
|
241
|
+
for ( const child of current.children ) {
|
242
|
+
|
243
|
+
const res = search( child );
|
244
|
+
if ( res ) return res;
|
245
|
+
|
246
|
+
}
|
247
|
+
|
248
|
+
}
|
249
|
+
|
250
|
+
}
|
251
|
+
|
252
|
+
return search( this );
|
253
|
+
|
254
|
+
}
|
255
|
+
|
256
|
+
|
257
|
+
buildHeader() {
|
258
|
+
|
259
|
+
return `#usda 1.0
|
260
|
+
(
|
261
|
+
customLayerData = {
|
262
|
+
string creator = "Three.js USDZExporter"
|
263
|
+
}
|
264
|
+
defaultPrim = "${makeNameSafe( this.name )}"
|
265
|
+
metersPerUnit = 1
|
266
|
+
upAxis = "Y"
|
267
|
+
startTimeCode = 0
|
268
|
+
endTimeCode = ${this.stageLength}
|
269
|
+
timeCodesPerSecond = 60
|
270
|
+
framesPerSecond = 60
|
271
|
+
)
|
272
|
+
`;
|
273
|
+
|
274
|
+
}
|
275
|
+
|
276
|
+
}
|
277
|
+
|
278
|
+
const newLine = '\n';
|
279
|
+
|
280
|
+
class USDWriter {
|
281
|
+
str: string;
|
282
|
+
indent: number;
|
283
|
+
|
284
|
+
constructor() {
|
285
|
+
|
286
|
+
this.str = '';
|
287
|
+
this.indent = 0;
|
288
|
+
|
289
|
+
}
|
290
|
+
|
291
|
+
clear() {
|
292
|
+
|
293
|
+
this.str = '';
|
294
|
+
this.indent = 0;
|
295
|
+
|
296
|
+
}
|
297
|
+
|
298
|
+
beginBlock( str ) {
|
299
|
+
|
300
|
+
str = this.applyIndent( str );
|
301
|
+
this.str += str;
|
302
|
+
this.str += newLine;
|
303
|
+
this.str += this.applyIndent( '{' );
|
304
|
+
this.str += newLine;
|
305
|
+
this.indent += 1;
|
306
|
+
|
307
|
+
}
|
308
|
+
|
309
|
+
closeBlock() {
|
310
|
+
|
311
|
+
this.indent -= 1;
|
312
|
+
this.str += this.applyIndent( '}' ) + newLine;
|
313
|
+
|
314
|
+
}
|
315
|
+
|
316
|
+
beginArray( str ) {
|
317
|
+
|
318
|
+
str = this.applyIndent( str + ' = [' );
|
319
|
+
this.str += str;
|
320
|
+
this.str += newLine;
|
321
|
+
this.indent += 1;
|
322
|
+
|
323
|
+
}
|
324
|
+
|
325
|
+
closeArray() {
|
326
|
+
|
327
|
+
this.indent -= 1;
|
328
|
+
this.str += this.applyIndent( ']' ) + newLine;
|
329
|
+
|
330
|
+
}
|
331
|
+
|
332
|
+
appendLine( str = '' ) {
|
333
|
+
|
334
|
+
str = this.applyIndent( str );
|
335
|
+
this.str += str;
|
336
|
+
this.str += newLine;
|
337
|
+
|
338
|
+
}
|
339
|
+
|
340
|
+
toString() {
|
341
|
+
|
342
|
+
return this.str;
|
343
|
+
|
344
|
+
}
|
345
|
+
|
346
|
+
applyIndent( str ) {
|
347
|
+
|
348
|
+
let indents = '';
|
349
|
+
for ( let i = 0; i < this.indent; i ++ ) indents += '\t';
|
350
|
+
return indents + str;
|
351
|
+
|
352
|
+
}
|
353
|
+
|
354
|
+
}
|
355
|
+
|
356
|
+
class USDZExporterContext {
|
357
|
+
root: any;
|
358
|
+
exporter: any;
|
359
|
+
extensions: any;
|
360
|
+
materials: {};
|
361
|
+
textures: {};
|
362
|
+
files: {};
|
363
|
+
document: USDDocument;
|
364
|
+
output: string;
|
365
|
+
|
366
|
+
constructor( root, exporter, extensions ) {
|
367
|
+
|
368
|
+
this.root = root;
|
369
|
+
this.exporter = exporter;
|
370
|
+
|
371
|
+
if ( extensions )
|
372
|
+
this.extensions = extensions;
|
373
|
+
|
374
|
+
this.materials = {};
|
375
|
+
this.textures = {};
|
376
|
+
this.files = {};
|
377
|
+
this.document = new USDDocument();
|
378
|
+
this.output = '';
|
379
|
+
|
380
|
+
}
|
381
|
+
|
382
|
+
}
|
383
|
+
|
384
|
+
class USDZExporterOptions {
|
385
|
+
ar: { anchoring: { type: string } } = { anchoring: { type: 'plane' } };
|
386
|
+
planeAnchoring: { alignment: string } = { alignment: 'horizontal' };
|
387
|
+
extensions: any[] = [];
|
388
|
+
}
|
389
|
+
|
390
|
+
class USDZExporter {
|
391
|
+
debug: boolean;
|
392
|
+
sceneAnchoringOptions: {} = {};
|
393
|
+
extensions: any;
|
394
|
+
|
395
|
+
constructor() {
|
396
|
+
|
397
|
+
this.debug = false;
|
398
|
+
|
399
|
+
}
|
400
|
+
|
401
|
+
async parse( scene, options: USDZExporterOptions = new USDZExporterOptions() ) {
|
402
|
+
|
403
|
+
options = Object.assign( {
|
404
|
+
ar: {
|
405
|
+
anchoring: { type: 'plane' },
|
406
|
+
planeAnchoring: { alignment: 'horizontal' }
|
407
|
+
},
|
408
|
+
extensions: []
|
409
|
+
}, options );
|
410
|
+
|
411
|
+
this.sceneAnchoringOptions = options;
|
412
|
+
// @ts-ignore
|
413
|
+
const context = new USDZExporterContext( scene, this, options.extensions );
|
414
|
+
this.extensions = context.extensions;
|
415
|
+
|
416
|
+
const files = context.files;
|
417
|
+
const modelFileName = 'model.usda';
|
418
|
+
|
419
|
+
// model file should be first in USDZ archive so we init it here
|
420
|
+
files[ modelFileName ] = null;
|
421
|
+
|
422
|
+
const materials = context.materials;
|
423
|
+
const textures = context.textures;
|
424
|
+
|
425
|
+
invokeAll( context, 'onBeforeBuildDocument' );
|
426
|
+
|
427
|
+
traverseVisible( scene, context.document, context );
|
428
|
+
|
429
|
+
invokeAll( context, 'onAfterBuildDocument' );
|
430
|
+
|
431
|
+
parseDocument( context );
|
432
|
+
|
433
|
+
invokeAll( context, 'onAfterSerialize' );
|
434
|
+
|
435
|
+
context.output += buildMaterials( materials, textures );
|
436
|
+
|
437
|
+
const header = context.document.buildHeader();
|
438
|
+
const final = header + '\n' + context.output;
|
439
|
+
|
440
|
+
// full output file
|
441
|
+
if ( this.debug )
|
442
|
+
console.log( final );
|
443
|
+
|
444
|
+
files[ modelFileName ] = fflate.strToU8( final );
|
445
|
+
context.output = '';
|
446
|
+
|
447
|
+
for ( const id in textures ) {
|
448
|
+
|
449
|
+
let texture = textures[ id ];
|
450
|
+
const isRGBA = texture.format === 1023;
|
451
|
+
if ( texture.isCompressedTexture ) {
|
452
|
+
|
453
|
+
texture = copyTexture( texture );
|
454
|
+
|
455
|
+
}
|
456
|
+
|
457
|
+
// TODO add readback options for textures that don't have texture.image
|
458
|
+
const canvas = await imageToCanvas( texture.image );
|
459
|
+
|
460
|
+
if ( canvas ) {
|
461
|
+
|
462
|
+
const blob = await new Promise( resolve => canvas.toBlob( resolve, isRGBA ? 'image/png' : 'image/jpeg', 1 ) ) as any;
|
463
|
+
files[ `textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}` ] = new Uint8Array( await blob.arrayBuffer() );
|
464
|
+
|
465
|
+
} else {
|
466
|
+
|
467
|
+
console.warn( 'Can`t export texture: ', texture );
|
468
|
+
|
469
|
+
}
|
470
|
+
|
471
|
+
}
|
472
|
+
|
473
|
+
// 64 byte alignment
|
474
|
+
// https://github.com/101arrowz/fflate/issues/39#issuecomment-777263109
|
475
|
+
|
476
|
+
let offset = 0;
|
477
|
+
|
478
|
+
for ( const filename in files ) {
|
479
|
+
|
480
|
+
const file = files[ filename ];
|
481
|
+
const headerSize = 34 + filename.length;
|
482
|
+
|
483
|
+
offset += headerSize;
|
484
|
+
|
485
|
+
const offsetMod64 = offset & 63;
|
486
|
+
|
487
|
+
if ( offsetMod64 !== 4 ) {
|
488
|
+
|
489
|
+
const padLength = 64 - offsetMod64;
|
490
|
+
const padding = new Uint8Array( padLength );
|
491
|
+
|
492
|
+
files[ filename ] = [ file, { extra: { 12345: padding } } ];
|
493
|
+
|
494
|
+
}
|
495
|
+
|
496
|
+
offset = file.length;
|
497
|
+
|
498
|
+
}
|
499
|
+
|
500
|
+
return fflate.zipSync( files, { level: 0 } );
|
501
|
+
|
502
|
+
}
|
503
|
+
|
504
|
+
}
|
505
|
+
|
506
|
+
function traverseVisible( object, parentModel, context ) {
|
507
|
+
|
508
|
+
if ( ! object.visible ) return;
|
509
|
+
|
510
|
+
let model: USDObject | undefined = undefined;
|
511
|
+
const geometry = object.geometry;
|
512
|
+
const material = object.material;
|
513
|
+
|
514
|
+
|
515
|
+
if ( object.isMesh && material && (material.isMeshStandardMaterial || material.isMeshBasicMaterial) && ! object.isSkinnedMesh ) {
|
516
|
+
|
517
|
+
const name = getObjectId( object );
|
518
|
+
model = new USDObject( object.uuid, name, object.matrix, geometry, material );
|
519
|
+
|
520
|
+
} else if ( object.isCamera ) {
|
521
|
+
|
522
|
+
const name = getObjectId( object );
|
523
|
+
model = new USDObject( object.uuid, name, object.matrix, undefined, undefined, object );
|
524
|
+
|
525
|
+
} else {
|
526
|
+
|
527
|
+
const name = getObjectId( object );
|
528
|
+
model = new USDObject( object.uuid, name, object.matrix );
|
529
|
+
|
530
|
+
}
|
531
|
+
|
532
|
+
if ( model ) {
|
533
|
+
|
534
|
+
if ( parentModel ) {
|
535
|
+
|
536
|
+
parentModel.add( model );
|
537
|
+
|
538
|
+
}
|
539
|
+
|
540
|
+
parentModel = model;
|
541
|
+
|
542
|
+
if ( context.extensions ) {
|
543
|
+
|
544
|
+
for ( const ext of context.extensions ) {
|
545
|
+
|
546
|
+
if ( ext.onExportObject ) ext.onExportObject.call( ext, object, model, context );
|
547
|
+
|
548
|
+
}
|
549
|
+
|
550
|
+
}
|
551
|
+
|
552
|
+
} else {
|
553
|
+
|
554
|
+
const name = getObjectId( object );
|
555
|
+
const empty = new USDObject( object.uuid, name, object.matrix );
|
556
|
+
if ( parentModel ) {
|
557
|
+
|
558
|
+
parentModel.add( empty );
|
559
|
+
|
560
|
+
}
|
561
|
+
|
562
|
+
parentModel = empty;
|
563
|
+
|
564
|
+
}
|
565
|
+
|
566
|
+
for ( const ch of object.children ) {
|
567
|
+
|
568
|
+
traverseVisible( ch, parentModel, context );
|
569
|
+
|
570
|
+
}
|
571
|
+
|
572
|
+
}
|
573
|
+
|
574
|
+
function parseDocument( context: USDZExporterContext ) {
|
575
|
+
|
576
|
+
for ( const child of context.document.children ) {
|
577
|
+
|
578
|
+
addResources( child, context );
|
579
|
+
|
580
|
+
}
|
581
|
+
|
582
|
+
const writer = new USDWriter();
|
583
|
+
|
584
|
+
writer.beginBlock( `def Xform "${context.document.name}"` );
|
585
|
+
|
586
|
+
writer.beginBlock( `def Scope "Scenes" (
|
587
|
+
kind = "sceneLibrary"
|
588
|
+
)` );
|
589
|
+
|
590
|
+
writer.beginBlock( `def Xform "Scene" (
|
591
|
+
apiSchemas = ["Preliminary_AnchoringAPI"]
|
592
|
+
customData = {
|
593
|
+
bool preliminary_collidesWithEnvironment = 0
|
594
|
+
string sceneName = "Scene"
|
595
|
+
}
|
596
|
+
sceneName = "Scene"
|
597
|
+
)` );
|
598
|
+
|
599
|
+
writer.appendLine( `token preliminary:anchoring:type = "${context.exporter.sceneAnchoringOptions.ar.anchoring.type}"` );
|
600
|
+
if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'plane')
|
601
|
+
writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.ar.planeAnchoring.alignment}"` );
|
602
|
+
// 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.
|
603
|
+
if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'image')
|
604
|
+
writer.appendLine( `rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>` );
|
605
|
+
writer.appendLine();
|
606
|
+
|
607
|
+
for ( const child of context.document.children ) {
|
608
|
+
|
609
|
+
buildXform( child, writer, context );
|
610
|
+
|
611
|
+
}
|
612
|
+
|
613
|
+
invokeAll( context, 'onAfterHierarchy', writer );
|
614
|
+
|
615
|
+
writer.closeBlock();
|
616
|
+
writer.closeBlock();
|
617
|
+
writer.closeBlock();
|
618
|
+
|
619
|
+
context.output += writer.toString();
|
620
|
+
|
621
|
+
}
|
622
|
+
|
623
|
+
function addResources( object, context: USDZExporterContext ) {
|
624
|
+
|
625
|
+
const geometry = object.geometry;
|
626
|
+
let material = object.material;
|
627
|
+
|
628
|
+
if ( geometry ) {
|
629
|
+
|
630
|
+
if ( material.isMeshStandardMaterial ) { // || material.isMeshBasicMaterial // TODO convert unlit to lit+emissive
|
631
|
+
|
632
|
+
const geometryFileName = 'geometries/Geometry_' + geometry.id + '.usd';
|
633
|
+
|
634
|
+
if ( ! ( geometryFileName in context.files ) ) {
|
635
|
+
|
636
|
+
const meshObject = buildMeshObject( geometry );
|
637
|
+
context.files[ geometryFileName ] = buildUSDFileAsString( meshObject, context );
|
638
|
+
|
639
|
+
}
|
640
|
+
|
641
|
+
if ( ! ( material.uuid in context.materials ) ) {
|
642
|
+
|
643
|
+
context.materials[ material.uuid ] = material;
|
644
|
+
|
645
|
+
}
|
646
|
+
|
647
|
+
} else {
|
648
|
+
|
649
|
+
console.warn( 'THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)', name );
|
650
|
+
|
651
|
+
}
|
652
|
+
|
653
|
+
}
|
654
|
+
|
655
|
+
for ( const ch of object.children ) {
|
656
|
+
|
657
|
+
addResources( ch, context );
|
658
|
+
|
659
|
+
}
|
660
|
+
|
661
|
+
}
|
662
|
+
|
663
|
+
function invokeAll( context: USDZExporterContext, name: string, writer: USDWriter | null = null ) {
|
664
|
+
|
665
|
+
if ( context.extensions ) {
|
666
|
+
|
667
|
+
for ( const ext of context.extensions ) {
|
668
|
+
|
669
|
+
if ( typeof ext[ name ] === 'function' )
|
670
|
+
ext[ name ]( context, writer );
|
671
|
+
|
672
|
+
}
|
673
|
+
|
674
|
+
}
|
675
|
+
|
676
|
+
}
|
677
|
+
|
678
|
+
function copyTexture( texture ) {
|
679
|
+
|
680
|
+
const geometry = new PlaneGeometry( 2, 2, 1, 1 );
|
681
|
+
const material = new ShaderMaterial( {
|
682
|
+
uniforms: { blitTexture: new Uniform( texture ) },
|
683
|
+
vertexShader: `
|
684
|
+
varying vec2 vUv;
|
685
|
+
void main(){
|
686
|
+
vUv = uv;
|
687
|
+
gl_Position = vec4(position.xy * 1.0,0.,.999999);
|
688
|
+
}`,
|
689
|
+
fragmentShader: `
|
690
|
+
uniform sampler2D blitTexture;
|
691
|
+
varying vec2 vUv;
|
692
|
+
void main(){
|
693
|
+
gl_FragColor = vec4(vUv.xy, 0, 1);
|
694
|
+
gl_FragColor = texture2D( blitTexture, vUv);
|
695
|
+
}`
|
696
|
+
} );
|
697
|
+
|
698
|
+
const mesh = new Mesh( geometry, material );
|
699
|
+
mesh.frustumCulled = false;
|
700
|
+
const cam = new PerspectiveCamera();
|
701
|
+
const scene = new Scene();
|
702
|
+
scene.add( mesh );
|
703
|
+
const renderer = new WebGLRenderer( { antialias: false } );
|
704
|
+
renderer.setSize( texture.image.width, texture.image.height );
|
705
|
+
renderer.clear();
|
706
|
+
renderer.render( scene, cam );
|
707
|
+
|
708
|
+
return new Texture( renderer.domElement );
|
709
|
+
|
710
|
+
}
|
711
|
+
|
712
|
+
|
713
|
+
function isImageBitmap( image ) {
|
714
|
+
|
715
|
+
return ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
|
716
|
+
( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
|
717
|
+
( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) ||
|
718
|
+
( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap );
|
719
|
+
|
720
|
+
}
|
721
|
+
|
722
|
+
async function imageToCanvas( image, color: string | undefined = undefined, flipY = false ) {
|
723
|
+
|
724
|
+
if ( isImageBitmap( image ) ) {
|
725
|
+
|
726
|
+
const scale = 1024 / Math.max( image.width, image.height );
|
727
|
+
|
728
|
+
const canvas = document.createElement( 'canvas' );
|
729
|
+
canvas.width = image.width * Math.min( 1, scale );
|
730
|
+
canvas.height = image.height * Math.min( 1, scale );
|
731
|
+
|
732
|
+
const context = canvas.getContext( '2d' );
|
733
|
+
if (!context) throw new Error('Could not get canvas 2D context');
|
734
|
+
|
735
|
+
if ( flipY === true ) {
|
736
|
+
|
737
|
+
context.translate( 0, canvas.height );
|
738
|
+
context.scale( 1, - 1 );
|
739
|
+
|
740
|
+
}
|
741
|
+
|
742
|
+
context.drawImage( image, 0, 0, canvas.width, canvas.height );
|
743
|
+
|
744
|
+
// TODO remove, not used anymore
|
745
|
+
if ( color !== undefined ) {
|
746
|
+
|
747
|
+
const hex = parseInt( color, 16 );
|
748
|
+
|
749
|
+
const r = ( hex >> 16 & 255 ) / 255;
|
750
|
+
const g = ( hex >> 8 & 255 ) / 255;
|
751
|
+
const b = ( hex & 255 ) / 255;
|
752
|
+
|
753
|
+
const imagedata = context.getImageData( 0, 0, canvas.width, canvas.height );
|
754
|
+
const data = imagedata.data;
|
755
|
+
|
756
|
+
for ( let i = 0; i < data.length; i += 4 ) {
|
757
|
+
|
758
|
+
data[ i + 0 ] = data[ i + 0 ] * r;
|
759
|
+
data[ i + 1 ] = data[ i + 1 ] * g;
|
760
|
+
data[ i + 2 ] = data[ i + 2 ] * b;
|
761
|
+
|
762
|
+
}
|
763
|
+
|
764
|
+
context.putImageData( imagedata, 0, 0 );
|
765
|
+
|
766
|
+
}
|
767
|
+
|
768
|
+
return canvas;
|
769
|
+
|
770
|
+
} else {
|
771
|
+
|
772
|
+
throw new Error( 'THREE.USDZExporter: No valid image data found. Unable to process texture.' );
|
773
|
+
|
774
|
+
}
|
775
|
+
|
776
|
+
}
|
777
|
+
|
778
|
+
//
|
779
|
+
|
780
|
+
const PRECISION = 7;
|
781
|
+
|
782
|
+
function buildHeader() {
|
783
|
+
|
784
|
+
return `#usda 1.0
|
785
|
+
(
|
786
|
+
customLayerData = {
|
787
|
+
string creator = "Three.js USDZExporter"
|
788
|
+
}
|
789
|
+
metersPerUnit = 1
|
790
|
+
upAxis = "Y"
|
791
|
+
)
|
792
|
+
`;
|
793
|
+
|
794
|
+
}
|
795
|
+
|
796
|
+
function buildUSDFileAsString( dataToInsert, _context: USDZExporterContext ) {
|
797
|
+
|
798
|
+
let output = buildHeader();
|
799
|
+
output += dataToInsert;
|
800
|
+
return fflate.strToU8( output );
|
801
|
+
|
802
|
+
}
|
803
|
+
|
804
|
+
function getObjectId( object ) {
|
805
|
+
|
806
|
+
return object.name.replace( /[-<>\(\)\[\]§$%&\/\\\=\?\,\;]/g, '' ) + '_' + object.id;
|
807
|
+
|
808
|
+
}
|
809
|
+
|
810
|
+
// Xform
|
811
|
+
|
812
|
+
export function buildXform( model, writer, context ) {
|
813
|
+
|
814
|
+
const matrix = model.matrix;
|
815
|
+
const geometry = model.geometry;
|
816
|
+
const material = model.material;
|
817
|
+
const camera = model.camera;
|
818
|
+
const name = model.name;
|
819
|
+
const transform = buildMatrix( matrix );
|
820
|
+
|
821
|
+
if ( matrix.determinant() < 0 ) {
|
822
|
+
|
823
|
+
console.warn( 'THREE.USDZExporter: USDZ does not support negative scales', name );
|
824
|
+
|
825
|
+
}
|
826
|
+
|
827
|
+
if ( geometry )
|
828
|
+
writer.beginBlock( `def Xform "${name}" (prepend references = @./geometries/Geometry_${geometry.id}.usd@</Geometry>)` );
|
829
|
+
else if ( camera )
|
830
|
+
writer.beginBlock( `def Camera "${name}"` );
|
831
|
+
else
|
832
|
+
writer.beginBlock( `def Xform "${name}"` );
|
833
|
+
|
834
|
+
if ( material )
|
835
|
+
writer.appendLine( `rel material:binding = </Materials/Material_${material.id}>` );
|
836
|
+
writer.appendLine( `matrix4d xformOp:transform = ${transform}` );
|
837
|
+
writer.appendLine( 'uniform token[] xformOpOrder = ["xformOp:transform"]' );
|
838
|
+
|
839
|
+
if ( camera ) {
|
840
|
+
|
841
|
+
if ( camera.isOrthographicCamera ) {
|
842
|
+
|
843
|
+
writer.appendLine( `float2 clippingRange = (${camera.near}, ${camera.far})` );
|
844
|
+
writer.appendLine( `float horizontalAperture = ${( ( Math.abs( camera.left ) + Math.abs( camera.right ) ) * 10 ).toPrecision( PRECISION )}` );
|
845
|
+
writer.appendLine( `float verticalAperture = ${( ( Math.abs( camera.top ) + Math.abs( camera.bottom ) ) * 10 ).toPrecision( PRECISION )}` );
|
846
|
+
writer.appendLine( 'token projection = "orthographic"' );
|
847
|
+
|
848
|
+
} else {
|
849
|
+
|
850
|
+
writer.appendLine( `float2 clippingRange = (${camera.near.toPrecision( PRECISION )}, ${camera.far.toPrecision( PRECISION )})` );
|
851
|
+
writer.appendLine( `float focalLength = ${camera.getFocalLength().toPrecision( PRECISION )}` );
|
852
|
+
writer.appendLine( `float focusDistance = ${camera.focus.toPrecision( PRECISION )}` );
|
853
|
+
writer.appendLine( `float horizontalAperture = ${camera.getFilmWidth().toPrecision( PRECISION )}` );
|
854
|
+
writer.appendLine( 'token projection = "perspective"' );
|
855
|
+
writer.appendLine( `float verticalAperture = ${camera.getFilmHeight().toPrecision( PRECISION )}` );
|
856
|
+
|
857
|
+
}
|
858
|
+
|
859
|
+
}
|
860
|
+
|
861
|
+
if ( model.onSerialize ) {
|
862
|
+
|
863
|
+
model.onSerialize( writer, context );
|
864
|
+
|
865
|
+
}
|
866
|
+
|
867
|
+
if ( model.children ) {
|
868
|
+
|
869
|
+
writer.appendLine();
|
870
|
+
for ( const ch of model.children ) {
|
871
|
+
|
872
|
+
buildXform( ch, writer, context );
|
873
|
+
|
874
|
+
}
|
875
|
+
|
876
|
+
}
|
877
|
+
|
878
|
+
writer.closeBlock();
|
879
|
+
|
880
|
+
}
|
881
|
+
|
882
|
+
function fn( num ) {
|
883
|
+
|
884
|
+
return num.toFixed( 10 );
|
885
|
+
|
886
|
+
}
|
887
|
+
|
888
|
+
function buildMatrix( matrix ) {
|
889
|
+
|
890
|
+
const array = matrix.elements;
|
891
|
+
|
892
|
+
return `( ${buildMatrixRow( array, 0 )}, ${buildMatrixRow( array, 4 )}, ${buildMatrixRow( array, 8 )}, ${buildMatrixRow( array, 12 )} )`;
|
893
|
+
|
894
|
+
}
|
895
|
+
|
896
|
+
function buildMatrixRow( array, offset ) {
|
897
|
+
|
898
|
+
return `(${fn( array[ offset + 0 ] )}, ${fn( array[ offset + 1 ] )}, ${fn( array[ offset + 2 ] )}, ${fn( array[ offset + 3 ] )})`;
|
899
|
+
|
900
|
+
}
|
901
|
+
|
902
|
+
// Mesh
|
903
|
+
|
904
|
+
function buildMeshObject( geometry ) {
|
905
|
+
|
906
|
+
const mesh = buildMesh( geometry );
|
907
|
+
return `
|
908
|
+
def "Geometry"
|
909
|
+
{
|
910
|
+
${mesh}
|
911
|
+
}
|
912
|
+
`;
|
913
|
+
|
914
|
+
}
|
915
|
+
|
916
|
+
function buildMesh( geometry ) {
|
917
|
+
|
918
|
+
const name = 'Geometry';
|
919
|
+
const attributes = geometry.attributes;
|
920
|
+
const count = attributes.position.count;
|
921
|
+
|
922
|
+
return `
|
923
|
+
def Mesh "${name}"
|
924
|
+
{
|
925
|
+
int[] faceVertexCounts = [${buildMeshVertexCount( geometry )}]
|
926
|
+
int[] faceVertexIndices = [${buildMeshVertexIndices( geometry )}]
|
927
|
+
normal3f[] normals = [${buildVector3Array( attributes.normal, count )}] (
|
928
|
+
interpolation = "vertex"
|
929
|
+
)
|
930
|
+
point3f[] points = [${buildVector3Array( attributes.position, count )}]
|
931
|
+
${attributes.uv ?
|
932
|
+
`float2[] primvars:st = [${buildVector2Array( attributes.uv, count )}] (
|
933
|
+
interpolation = "vertex"
|
934
|
+
)` : '' }
|
935
|
+
${attributes.uv2 ?
|
936
|
+
`float2[] primvars:st2 = [${buildVector2Array( attributes.uv2, count )}] (
|
937
|
+
interpolation = "vertex"
|
938
|
+
)` : '' }
|
939
|
+
uniform token subdivisionScheme = "none"
|
940
|
+
}
|
941
|
+
`;
|
942
|
+
|
943
|
+
}
|
944
|
+
|
945
|
+
function buildMeshVertexCount( geometry ) {
|
946
|
+
|
947
|
+
const count = geometry.index !== null ? geometry.index.count : geometry.attributes.position.count;
|
948
|
+
|
949
|
+
return Array( count / 3 ).fill( 3 ).join( ', ' );
|
950
|
+
|
951
|
+
}
|
952
|
+
|
953
|
+
function buildMeshVertexIndices( geometry: BufferGeometry ) {
|
954
|
+
|
955
|
+
const index = geometry.index;
|
956
|
+
const array: Array<number> = [];
|
957
|
+
|
958
|
+
if ( index !== null ) {
|
959
|
+
|
960
|
+
for ( let i = 0; i < index.count; i ++ ) {
|
961
|
+
|
962
|
+
array.push( index.getX( i ) );
|
963
|
+
|
964
|
+
}
|
965
|
+
|
966
|
+
} else {
|
967
|
+
|
968
|
+
const length = geometry.attributes.position.count;
|
969
|
+
|
970
|
+
for ( let i = 0; i < length; i ++ ) {
|
971
|
+
|
972
|
+
array.push( i );
|
973
|
+
|
974
|
+
}
|
975
|
+
|
976
|
+
}
|
977
|
+
|
978
|
+
return array.join( ', ' );
|
979
|
+
|
980
|
+
}
|
981
|
+
|
982
|
+
function buildVector3Array( attribute, count ) {
|
983
|
+
|
984
|
+
if ( attribute === undefined ) {
|
985
|
+
|
986
|
+
console.warn( 'USDZExporter: Normals missing.' );
|
987
|
+
return Array( count ).fill( '(0, 0, 0)' ).join( ', ' );
|
988
|
+
|
989
|
+
}
|
990
|
+
|
991
|
+
const array: Array<string> = [];
|
992
|
+
|
993
|
+
for ( let i = 0; i < attribute.count; i ++ ) {
|
994
|
+
|
995
|
+
const x = attribute.getX( i );
|
996
|
+
const y = attribute.getY( i );
|
997
|
+
const z = attribute.getZ( i );
|
998
|
+
|
999
|
+
array.push( `(${x.toPrecision( PRECISION )}, ${y.toPrecision( PRECISION )}, ${z.toPrecision( PRECISION )})` );
|
1000
|
+
|
1001
|
+
}
|
1002
|
+
|
1003
|
+
return array.join( ', ' );
|
1004
|
+
|
1005
|
+
}
|
1006
|
+
|
1007
|
+
function buildVector2Array( attribute, count ) {
|
1008
|
+
|
1009
|
+
if ( attribute === undefined ) {
|
1010
|
+
|
1011
|
+
console.warn( 'USDZExporter: UVs missing.' );
|
1012
|
+
return Array( count ).fill( '(0, 0)' ).join( ', ' );
|
1013
|
+
|
1014
|
+
}
|
1015
|
+
|
1016
|
+
const array: Array<string> = [];
|
1017
|
+
|
1018
|
+
for ( let i = 0; i < attribute.count; i ++ ) {
|
1019
|
+
|
1020
|
+
const x = attribute.getX( i );
|
1021
|
+
const y = attribute.getY( i );
|
1022
|
+
|
1023
|
+
array.push( `(${x.toPrecision( PRECISION )}, ${1 - y.toPrecision( PRECISION )})` );
|
1024
|
+
|
1025
|
+
}
|
1026
|
+
|
1027
|
+
return array.join( ', ' );
|
1028
|
+
|
1029
|
+
}
|
1030
|
+
|
1031
|
+
// Materials
|
1032
|
+
|
1033
|
+
function buildMaterials( materials, textures ) {
|
1034
|
+
|
1035
|
+
const array: Array<string> = [];
|
1036
|
+
|
1037
|
+
for ( const uuid in materials ) {
|
1038
|
+
|
1039
|
+
const material = materials[ uuid ];
|
1040
|
+
|
1041
|
+
array.push( buildMaterial( material, textures ) );
|
1042
|
+
|
1043
|
+
}
|
1044
|
+
|
1045
|
+
return `def "Materials"
|
1046
|
+
{
|
1047
|
+
${array.join( '' )}
|
1048
|
+
}
|
1049
|
+
|
1050
|
+
`;
|
1051
|
+
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
function buildMaterial( material, textures ) {
|
1055
|
+
|
1056
|
+
// https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html
|
1057
|
+
|
1058
|
+
const pad = ' ';
|
1059
|
+
const inputs: Array<string> = [];
|
1060
|
+
const samplers: Array<string> = [];
|
1061
|
+
const exportForQuickLook = true;
|
1062
|
+
|
1063
|
+
function buildTexture( texture, mapType, color: Color | undefined = undefined, opacity: number | undefined = undefined ) {
|
1064
|
+
|
1065
|
+
const id = texture.id + ( color ? '_' + color.getHexString() : '' ) + ( opacity ? '_' + opacity : '' );
|
1066
|
+
const isRGBA = texture.format === 1023;
|
1067
|
+
|
1068
|
+
const wrapS = ( texture.wrapS == RepeatWrapping ) ? 'repeat' : ( texture.wrapS == MirroredRepeatWrapping ? 'mirror' : 'clamp' );
|
1069
|
+
const wrapT = ( texture.wrapT == RepeatWrapping ) ? 'repeat' : ( texture.wrapT == MirroredRepeatWrapping ? 'mirror' : 'clamp' );
|
1070
|
+
|
1071
|
+
const repeat = texture.repeat.clone();
|
1072
|
+
const offset = texture.offset.clone();
|
1073
|
+
|
1074
|
+
// texture coordinates start in the opposite corner, need to correct
|
1075
|
+
offset.y = 1 - offset.y - repeat.y;
|
1076
|
+
|
1077
|
+
// turns out QuickLook is buggy and interprets texture repeat inverted.
|
1078
|
+
// Apple Feedback: FB10036297 and FB11442287
|
1079
|
+
if ( exportForQuickLook ) {
|
1080
|
+
|
1081
|
+
offset.x = offset.x / repeat.x;
|
1082
|
+
offset.y = offset.y / repeat.y;
|
1083
|
+
|
1084
|
+
}
|
1085
|
+
|
1086
|
+
textures[ id ] = texture;
|
1087
|
+
const uvReader = mapType == 'occlusion' ? 'uvReader_st2' : 'uvReader_st';
|
1088
|
+
|
1089
|
+
const needsTextureTransform = ( repeat.x != 1 || repeat.y != 1 || offset.x != 0 || offset.y != 0 );
|
1090
|
+
const textureTransformInput = `</Materials/Material_${material.id}/${uvReader}.outputs:result>`;
|
1091
|
+
const textureTransformOutput = `</Materials/Material_${material.id}/Transform2d_${mapType}.outputs:result>`;
|
1092
|
+
|
1093
|
+
return `
|
1094
|
+
${needsTextureTransform ? `def Shader "Transform2d_${mapType}" (
|
1095
|
+
sdrMetadata = {
|
1096
|
+
string role = "math"
|
1097
|
+
}
|
1098
|
+
)
|
1099
|
+
{
|
1100
|
+
uniform token info:id = "UsdTransform2d"
|
1101
|
+
float2 inputs:in.connect = ${textureTransformInput}
|
1102
|
+
float2 inputs:scale = ${buildVector2( repeat )}
|
1103
|
+
float2 inputs:translation = ${buildVector2( offset )}
|
1104
|
+
float2 outputs:result
|
1105
|
+
}
|
1106
|
+
` : '' }
|
1107
|
+
def Shader "Texture_${texture.id}_${mapType}"
|
1108
|
+
{
|
1109
|
+
uniform token info:id = "UsdUVTexture"
|
1110
|
+
asset inputs:file = @textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}@
|
1111
|
+
float2 inputs:st.connect = ${needsTextureTransform ? textureTransformOutput : textureTransformInput}
|
1112
|
+
float4 inputs:scale = (${color ? color.r + ', ' + color.g + ', ' + color.b : '1, 1, 1'}, ${opacity ? opacity : '1'})
|
1113
|
+
token inputs:wrapS = "${wrapS}"
|
1114
|
+
token inputs:wrapT = "${wrapT}"
|
1115
|
+
float outputs:r
|
1116
|
+
float outputs:g
|
1117
|
+
float outputs:b
|
1118
|
+
float3 outputs:rgb
|
1119
|
+
${material.transparent || material.alphaTest > 0.0 ? 'float outputs:a' : ''}
|
1120
|
+
}`;
|
1121
|
+
|
1122
|
+
}
|
1123
|
+
|
1124
|
+
const effectiveOpacity = ( material.transparent || material.alphaTest ) ? material.opacity : 1;
|
1125
|
+
|
1126
|
+
if ( material.side === DoubleSide ) {
|
1127
|
+
|
1128
|
+
console.warn( 'THREE.USDZExporter: USDZ does not support double sided materials', material );
|
1129
|
+
|
1130
|
+
}
|
1131
|
+
|
1132
|
+
if ( material.map !== null ) {
|
1133
|
+
|
1134
|
+
inputs.push( `${pad}color3f inputs:diffuseColor.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:rgb>` );
|
1135
|
+
|
1136
|
+
if ( material.transparent ) {
|
1137
|
+
|
1138
|
+
inputs.push( `${pad}float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:a>` );
|
1139
|
+
|
1140
|
+
} else if ( material.alphaTest > 0.0 ) {
|
1141
|
+
|
1142
|
+
inputs.push( `${pad}float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:a>` );
|
1143
|
+
inputs.push( `${pad}float inputs:opacityThreshold = ${material.alphaTest}` );
|
1144
|
+
|
1145
|
+
}
|
1146
|
+
|
1147
|
+
samplers.push( buildTexture( material.map, 'diffuse', material.color, effectiveOpacity ) );
|
1148
|
+
|
1149
|
+
} else {
|
1150
|
+
|
1151
|
+
inputs.push( `${pad}color3f inputs:diffuseColor = ${buildColor( material.color )}` );
|
1152
|
+
|
1153
|
+
}
|
1154
|
+
|
1155
|
+
if ( material.emissiveMap ) {
|
1156
|
+
|
1157
|
+
inputs.push( `${pad}color3f inputs:emissiveColor.connect = </Materials/Material_${material.id}/Texture_${material.emissiveMap.id}_emissive.outputs:rgb>` );
|
1158
|
+
|
1159
|
+
samplers.push( buildTexture( material.emissiveMap, 'emissive' ) );
|
1160
|
+
|
1161
|
+
} else if ( material.emissive?.getHex() > 0 ) {
|
1162
|
+
|
1163
|
+
inputs.push( `${pad}color3f inputs:emissiveColor = ${buildColor( material.emissive )}` );
|
1164
|
+
|
1165
|
+
} else {
|
1166
|
+
|
1167
|
+
inputs.push( `${pad}color3f inputs:emissiveColor = (0, 0, 0)` );
|
1168
|
+
|
1169
|
+
}
|
1170
|
+
|
1171
|
+
if ( material.normalMap ) {
|
1172
|
+
|
1173
|
+
inputs.push( `${pad}normal3f inputs:normal.connect = </Materials/Material_${material.id}/Texture_${material.normalMap.id}_normal.outputs:rgb>` );
|
1174
|
+
|
1175
|
+
samplers.push( buildTexture( material.normalMap, 'normal' ) );
|
1176
|
+
|
1177
|
+
}
|
1178
|
+
|
1179
|
+
if ( material.aoMap ) {
|
1180
|
+
|
1181
|
+
inputs.push( `${pad}float inputs:occlusion.connect = </Materials/Material_${material.id}/Texture_${material.aoMap.id}_occlusion.outputs:r>` );
|
1182
|
+
|
1183
|
+
samplers.push( buildTexture( material.aoMap, 'occlusion' ) );
|
1184
|
+
|
1185
|
+
}
|
1186
|
+
|
1187
|
+
if ( material.roughnessMap && material.roughness === 1 ) {
|
1188
|
+
|
1189
|
+
inputs.push( `${pad}float inputs:roughness.connect = </Materials/Material_${material.id}/Texture_${material.roughnessMap.id}_roughness.outputs:g>` );
|
1190
|
+
|
1191
|
+
samplers.push( buildTexture( material.roughnessMap, 'roughness' ) );
|
1192
|
+
|
1193
|
+
} else {
|
1194
|
+
|
1195
|
+
inputs.push( `${pad}float inputs:roughness = ${material.roughness}` );
|
1196
|
+
|
1197
|
+
}
|
1198
|
+
|
1199
|
+
if ( material.metalnessMap && material.metalness === 1 ) {
|
1200
|
+
|
1201
|
+
inputs.push( `${pad}float inputs:metallic.connect = </Materials/Material_${material.id}/Texture_${material.metalnessMap.id}_metallic.outputs:b>` );
|
1202
|
+
|
1203
|
+
samplers.push( buildTexture( material.metalnessMap, 'metallic' ) );
|
1204
|
+
|
1205
|
+
} else {
|
1206
|
+
|
1207
|
+
inputs.push( `${pad}float inputs:metallic = ${material.metalness}` );
|
1208
|
+
|
1209
|
+
}
|
1210
|
+
|
1211
|
+
if ( material.alphaMap ) {
|
1212
|
+
|
1213
|
+
inputs.push( `${pad}float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.alphaMap.id}_opacity.outputs:r>` );
|
1214
|
+
inputs.push( `${pad}float inputs:opacityThreshold = 0.0001` );
|
1215
|
+
|
1216
|
+
samplers.push( buildTexture( material.alphaMap, 'opacity' ) );
|
1217
|
+
|
1218
|
+
} else {
|
1219
|
+
|
1220
|
+
inputs.push( `${pad}float inputs:opacity = ${effectiveOpacity}` );
|
1221
|
+
|
1222
|
+
}
|
1223
|
+
|
1224
|
+
if ( material.isMeshPhysicalMaterial ) {
|
1225
|
+
|
1226
|
+
inputs.push( `${pad}float inputs:clearcoat = ${material.clearcoat}` );
|
1227
|
+
inputs.push( `${pad}float inputs:clearcoatRoughness = ${material.clearcoatRoughness}` );
|
1228
|
+
inputs.push( `${pad}float inputs:ior = ${material.ior}` );
|
1229
|
+
|
1230
|
+
}
|
1231
|
+
|
1232
|
+
return `
|
1233
|
+
def Material "Material_${material.id}"
|
1234
|
+
{
|
1235
|
+
def Shader "PreviewSurface"
|
1236
|
+
{
|
1237
|
+
uniform token info:id = "UsdPreviewSurface"
|
1238
|
+
${inputs.join( '\n' )}
|
1239
|
+
int inputs:useSpecularWorkflow = 0
|
1240
|
+
token outputs:surface
|
1241
|
+
}
|
1242
|
+
|
1243
|
+
token outputs:surface.connect = </Materials/Material_${material.id}/PreviewSurface.outputs:surface>
|
1244
|
+
|
1245
|
+
def Shader "uvReader_st"
|
1246
|
+
{
|
1247
|
+
uniform token info:id = "UsdPrimvarReader_float2"
|
1248
|
+
token inputs:varname = "st"
|
1249
|
+
float2 inputs:fallback = (0.0, 0.0)
|
1250
|
+
float2 outputs:result
|
1251
|
+
}
|
1252
|
+
|
1253
|
+
def Shader "uvReader_st2"
|
1254
|
+
{
|
1255
|
+
uniform token info:id = "UsdPrimvarReader_float2"
|
1256
|
+
token inputs:varname = "st2"
|
1257
|
+
float2 inputs:fallback = (0.0, 0.0)
|
1258
|
+
float2 outputs:result
|
1259
|
+
}
|
1260
|
+
|
1261
|
+
${samplers.join( '\n' )}
|
1262
|
+
|
1263
|
+
}
|
1264
|
+
`;
|
1265
|
+
|
1266
|
+
}
|
1267
|
+
|
1268
|
+
function buildColor( color ) {
|
1269
|
+
|
1270
|
+
return `(${color.r}, ${color.g}, ${color.b})`;
|
1271
|
+
|
1272
|
+
}
|
1273
|
+
|
1274
|
+
function buildVector2( vector ) {
|
1275
|
+
|
1276
|
+
return `(${ vector.x }, ${ vector.y })`;
|
1277
|
+
|
1278
|
+
}
|
1279
|
+
|
1280
|
+
export { USDZExporter, USDZExporterContext, USDWriter, USDObject, buildMatrix, USDDocument, makeNameSafe as makeNameSafeForUSD, imageToCanvas };
|
@@ -0,0 +1,142 @@
|
|
1
|
+
import { IBehaviorElement } from "../extensions/behavior/BehavioursBuilder";
|
2
|
+
import { USDDocument, USDWriter } from "../ThreeUSDZExporter";
|
3
|
+
|
4
|
+
|
5
|
+
export enum TextWrapMode {
|
6
|
+
singleLine = "singleLine",
|
7
|
+
hardBreaks = "hardBreaks",
|
8
|
+
flowing = "flowing",
|
9
|
+
}
|
10
|
+
|
11
|
+
export enum HorizontalAlignment {
|
12
|
+
left = "left",
|
13
|
+
center = "center",
|
14
|
+
right = "right",
|
15
|
+
justified = "justified"
|
16
|
+
}
|
17
|
+
|
18
|
+
export enum VerticalAlignment {
|
19
|
+
top = "top",
|
20
|
+
middle = "middle",
|
21
|
+
lowerMiddle = "lowerMiddle",
|
22
|
+
baseline = "baseline",
|
23
|
+
bottom = "bottom"
|
24
|
+
}
|
25
|
+
|
26
|
+
export class USDZText implements IBehaviorElement {
|
27
|
+
|
28
|
+
static global_id: number = 0;
|
29
|
+
static getId(): number {
|
30
|
+
return this.global_id++;
|
31
|
+
}
|
32
|
+
|
33
|
+
id: string;
|
34
|
+
content: string = "";
|
35
|
+
font?: string[] = [];
|
36
|
+
pointSize: number = 144;
|
37
|
+
width?: number;
|
38
|
+
height?: number;
|
39
|
+
depth?: number;
|
40
|
+
wrapMode?: TextWrapMode;
|
41
|
+
horizontalAlignment?: HorizontalAlignment;
|
42
|
+
verticalAlignment?: VerticalAlignment;
|
43
|
+
|
44
|
+
setDepth(depth: number): USDZText {
|
45
|
+
this.depth = depth;
|
46
|
+
return this;
|
47
|
+
}
|
48
|
+
|
49
|
+
setPointSize(pointSize: number): USDZText {
|
50
|
+
this.pointSize = pointSize;
|
51
|
+
return this;
|
52
|
+
}
|
53
|
+
|
54
|
+
setHorizontalAlignment(align: HorizontalAlignment) {
|
55
|
+
this.horizontalAlignment = align;
|
56
|
+
return this;
|
57
|
+
}
|
58
|
+
|
59
|
+
setVerticalAlignment(align: VerticalAlignment) {
|
60
|
+
this.verticalAlignment = align;
|
61
|
+
return this;
|
62
|
+
}
|
63
|
+
|
64
|
+
constructor(id: string) {
|
65
|
+
this.id = id;
|
66
|
+
}
|
67
|
+
|
68
|
+
writeTo(_document: USDDocument | undefined, writer: USDWriter) {
|
69
|
+
|
70
|
+
|
71
|
+
writer.beginBlock(`def Preliminary_Text "${this.id}"`);
|
72
|
+
|
73
|
+
if (this.content)
|
74
|
+
writer.appendLine(`string content = "${this.content}"`);
|
75
|
+
|
76
|
+
if (!this.font || this.font.length <= 0) {
|
77
|
+
this.font ||= [];
|
78
|
+
this.font?.push("sans-serif");
|
79
|
+
}
|
80
|
+
const str = this.font.map(s => `"${s}"`).join(", ");
|
81
|
+
writer.appendLine(`string[] font = [ ${str} ]`);
|
82
|
+
|
83
|
+
writer.appendLine(`double pointSize = ${this.pointSize}`);
|
84
|
+
if (typeof this.width === "number")
|
85
|
+
writer.appendLine(`double width = ${this.width}`);
|
86
|
+
if (typeof this.height === "number")
|
87
|
+
writer.appendLine(`double height = ${this.height}`);
|
88
|
+
if (typeof this.depth === "number")
|
89
|
+
writer.appendLine(`double depth = ${this.depth}`);
|
90
|
+
if (this.wrapMode)
|
91
|
+
writer.appendLine(`token wrapMode = "${this.wrapMode}"`);
|
92
|
+
if (this.horizontalAlignment)
|
93
|
+
writer.appendLine(`token horizontalAlignment = "${this.horizontalAlignment}"`);
|
94
|
+
if (this.verticalAlignment)
|
95
|
+
writer.appendLine(`token verticalAlignment = "${this.verticalAlignment}"`);
|
96
|
+
|
97
|
+
writer.closeBlock();
|
98
|
+
|
99
|
+
}
|
100
|
+
|
101
|
+
}
|
102
|
+
|
103
|
+
|
104
|
+
export class TextBuilder {
|
105
|
+
static singleLine(str: string, pointSize?: number, depth?: number): USDZText {
|
106
|
+
|
107
|
+
const text = new USDZText("text_" + USDZText.getId());
|
108
|
+
text.content = str;
|
109
|
+
if (pointSize)
|
110
|
+
text.pointSize = pointSize;
|
111
|
+
if (depth)
|
112
|
+
text.depth = depth;
|
113
|
+
return text;
|
114
|
+
}
|
115
|
+
|
116
|
+
static multiLine(str: string, width: number, height: number, horizontal: HorizontalAlignment, vertical: VerticalAlignment, wrapMode?: TextWrapMode) {
|
117
|
+
const text = new USDZText("text_" + USDZText.getId());
|
118
|
+
text.content = str;
|
119
|
+
text.width = width;
|
120
|
+
text.height = height;
|
121
|
+
text.horizontalAlignment = horizontal;
|
122
|
+
text.verticalAlignment = vertical;
|
123
|
+
if (wrapMode)
|
124
|
+
text.wrapMode = wrapMode;
|
125
|
+
return text;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
|
130
|
+
export class TextExtension {
|
131
|
+
onExportObject(_object, model, _context) {
|
132
|
+
model.addEventListener("serialize", (writer, _context) => {
|
133
|
+
const text = TextBuilder.multiLine("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
|
134
|
+
1, 1, HorizontalAlignment.justified, VerticalAlignment.top);
|
135
|
+
text.pointSize = 300;
|
136
|
+
text.depth = .01;
|
137
|
+
text.writeTo(undefined, writer);
|
138
|
+
});
|
139
|
+
}
|
140
|
+
}
|
141
|
+
|
142
|
+
|