Needle Engine

Changes between version 3.3.0-alpha and 3.4.0-alpha
Files changed (27) hide show
  1. src/engine/codegen/register_types.js +52 -4
  2. plugins/vite/reload.js +13 -2
  3. src/engine-components/Animation.ts +4 -0
  4. src/engine-components/export/usdz/extensions/Animation.ts +37 -45
  5. src/engine-components/ui/BaseUIComponent.ts +7 -1
  6. src/engine-components/ui/Canvas.ts +80 -5
  7. src/engine-components/codegen/components.ts +25 -1
  8. src/engine/engine_gameobject.ts +3 -2
  9. src/engine/engine_three_utils.ts +2 -2
  10. src/engine-components/export/usdz/Extension.ts +4 -5
  11. src/engine-components/ui/Graphic.ts +2 -0
  12. src/engine-components/ui/Image.ts +3 -3
  13. src/engine-components/ui/Interfaces.ts +30 -6
  14. src/engine-components/ui/Layout.ts +303 -4
  15. src/engine-components/utils/LookAt.ts +60 -7
  16. src/engine-components/postprocessing/PostProcessingHandler.ts +1 -1
  17. src/engine-components/ui/RectTransform.ts +65 -40
  18. src/engine-components/export/usdz/types.ts +0 -39
  19. src/engine-components/export/usdz/USDZExporter.ts +39 -17
  20. src/engine-components/webxr/WebXRImageTracking.ts +100 -27
  21. src/engine-components/export/usdz/extensions/behavior/Actions.ts +99 -0
  22. src/engine-components/export/usdz/extensions/behavior/Behaviour.ts +181 -0
  23. src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +503 -0
  24. src/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.ts +459 -0
  25. src/engine-components/export/usdz/extensions/DocumentExtension.ts +10 -0
  26. src/engine-components/export/usdz/ThreeUSDZExporter.ts +1280 -0
  27. src/engine-components/export/usdz/extensions/USDZText.ts +142 -0
src/engine/codegen/register_types.js CHANGED
@@ -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);
plugins/vite/reload.js CHANGED
@@ -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
- if (file.endsWith(".vue") || file.endsWith(".ts") || file.endsWith(".js") || file.endsWith(".jsx") || file.endsWith(".tsx"))
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");
src/engine-components/Animation.ts CHANGED
@@ -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);
src/engine-components/export/usdz/extensions/Animation.ts CHANGED
@@ -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: USDZObject, context);
12
+ createAnimation(ext: AnimationExtension, model: USDObject, context);
12
13
  }
13
14
 
14
- export type AnimationClipCollection = Array<{ root: Object3D, clips: Array<THREE.AnimationClip> }>;
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: THREE.AnimationClip;
24
+ private clip: AnimationClip;
24
25
 
25
- constructor(ext: AnimationExtension, root: THREE.Object3D, clip: THREE.AnimationClip) {
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: THREE.AnimationClip;
34
- pos?: THREE.KeyframeTrack;
35
- rot?: THREE.KeyframeTrack;
36
- scale?: THREE.KeyframeTrack;
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: THREE.AnimationClip) {
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 IUSDZExporterExtension {
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: THREE.AnimationClip) {
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: THREE.AnimationClip): RegisteredAnimationInfo | null {
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: USDZObject, _context) {
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: USDZObject;
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: USDZObject) {
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 timesArray = transformData.pos?.times;
244
- if (!timesArray || transformData.rot && transformData.rot.times?.length > timesArray?.length) timesArray = transformData.rot?.times;
245
- if (!timesArray || transformData.scale && transformData.scale.times?.length > timesArray?.length) timesArray = transformData.scale?.times;
246
- if (!timesArray) {
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 scale = scaleInterpolant.evaluate(time);
280
- scale.set(scale[0], scale[1], scale[2]);
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
  }
src/engine-components/ui/BaseUIComponent.ts CHANGED
@@ -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
 
src/engine-components/ui/Canvas.ts CHANGED
@@ -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);
src/engine-components/codegen/components.ts CHANGED
@@ -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";
src/engine/engine_gameobject.ts CHANGED
@@ -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
  }
src/engine/engine_three_utils.ts CHANGED
@@ -98,8 +98,8 @@
98
98
  const tempVec = _worldScale2;
99
99
  const obj2 = obj.parent;
100
100
  obj2.getWorldScale(tempVec);
101
- tempVec.divide(vec);
102
- obj.scale.copy(tempVec);
101
+ obj.scale.copy(vec);
102
+ obj.scale.divide(tempVec);
103
103
  }
104
104
 
105
105
  const _forward = new Vector3();
src/engine-components/export/usdz/Extension.ts CHANGED
@@ -1,12 +1,11 @@
1
- import { USDZObject } from "./types";
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 : USDZObject, context);
8
+ onExportObject?(object, model : USDObject, context);
10
9
  onAfterSerialize?(context);
11
- onAfterHierarchy?(context);
10
+ onAfterHierarchy?(context, writer : any);
12
11
  }
src/engine-components/ui/Graphic.ts CHANGED
@@ -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),
src/engine-components/ui/Image.ts CHANGED
@@ -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
  }
src/engine-components/ui/Interfaces.ts CHANGED
@@ -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 screenspace() : boolean;
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() : boolean;
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() : boolean;
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 IRectTransformChangedReceiver {
23
- onParentRectTransformChanged(comp : IRectTransform) : void;
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
+ // }
src/engine-components/ui/Layout.ts CHANGED
@@ -1,17 +1,316 @@
1
- import { Behaviour } from "../Component";
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
- export class LayoutGroup extends Behaviour {
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 VerticalLayoutGroup extends LayoutGroup {
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 HorizontalLayoutGroup extends LayoutGroup {
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
  }
src/engine-components/utils/LookAt.ts CHANGED
@@ -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, lookAtInverse } from "../../engine/engine_three_utils";
3
+ import { Matrix4, Object3D, Quaternion, Vector3 } from "three";
4
+ import { getWorldPosition, getWorldQuaternion, setWorldQuaternion } from "../../engine/engine_three_utils";
5
5
 
6
- export class LookAt extends Behaviour {
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
- if (!this.target) return;
16
- if (!this.invertForward)
17
- this.gameObject.lookAt(getWorldPosition(this.target!));
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
- lookAtInverse(this.gameObject, getWorldPosition(this.target!));
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
  }
src/engine-components/postprocessing/PostProcessingHandler.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { HalfFloatType, sRGBEncoding, WebGLRenderTarget } from "three";
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";
src/engine-components/ui/RectTransform.ts CHANGED
@@ -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.05;
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
- onChange(this, "_anchoredPosition", () => { this._transformNeedsUpdate = true; });
116
- onChange(this, "sizeDelta", () => { this._transformNeedsUpdate = true; });
117
- onChange(this, "pivot", () => { this._transformNeedsUpdate = true; });
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(_comp: IRectTransform) {
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
- this.applyTransform();
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 applyTransform() {
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
- if (debug) console.log("RectTransform ApplyTransform", this.name, this.isRoot());
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
- if (this.gameObject.scale.x || this.gameObject.scale.y || this.gameObject.scale.z)
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
- private _copyMatrixAfterRender: boolean = false;
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;
src/engine-components/export/usdz/types.ts DELETED
@@ -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
- }
src/engine-components/export/usdz/USDZExporter.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import { delay, getParam, isiOS, isMobileDevice, isSafari } from "../../../engine/engine_utils";
2
- import { Object3D, Color, Mesh, Matrix4 } from "three";
3
- import { USDZExporter as ThreeUSDZExporter } from "three/examples/jsm/exporters/USDZExporter";
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 { IUSDZExporterExtension } from "./Extension";
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 { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/debug";
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?: THREE.Object3D;
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
- extensions: IUSDZExporterExtension[] = [];
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
- if (!this.objectToExport) this.objectToExport = this.gameObject;
76
-
77
-
78
- if (isDevEnvironment() && (!this.objectToExport?.children?.length && !(this.objectToExport as Mesh)?.isMesh)) {
79
- showBalloonWarning("USDZ Exporter has nothing to export");
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 (debug) name += "-" + getFormattedDate();
139
- else if (!hasProLicense()) name = name + " - Made with Needle";
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, extensions);
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 }))
src/engine-components/webxr/WebXRImageTracking.ts CHANGED
@@ -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
- applyToObject(object: Object3D) {
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
- object.position.copy(this._position);
48
- object.quaternion.copy(this._rotation);
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
- this._position.set(-t.position.x, t.position.y, -t.position.z);
62
- this._rotation.set(-t.orientation.x, t.orientation.y, -t.orientation.z, t.orientation.w);
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
- WebXR.addEventListener("modify-ar-options", this.onModifyAROptions);
132
- WebXR.addEventListener("xrStarted", this.onXRStarted);
133
- this.addEventListener("image-tracking", this.onImageTrackingUpdate)
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("modify-ar-options", this.onModifyAROptions);
138
- WebXR.removeEventListener("xrStarted", this.onXRStarted);
139
- this.removeEventListener("image-tracking", this.onImageTrackingUpdate)
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
- if (asset !== this.gameObject)
201
- this.gameObject.add(asset);
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
- onBeforeRender(frame: XRFrame | null): void {
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?.session && typeof frame.getImageTrackingResults === "function") {
307
+ if (frame.session && typeof frame.getImageTrackingResults === "function") {
235
308
  //@ts-ignore
236
309
  const results = frame.getImageTrackingResults();
237
310
  if (results.length) {
src/engine-components/export/usdz/extensions/behavior/Actions.ts ADDED
@@ -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
+ }
src/engine-components/export/usdz/extensions/behavior/Behaviour.ts ADDED
@@ -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);
src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts ADDED
@@ -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
+ }
src/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.ts ADDED
@@ -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
+