@needle-tools/engine 3.3.0-alpha → 3.4.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/needle-engine.js +26116 -24881
  3. package/dist/needle-engine.min.js +384 -383
  4. package/dist/needle-engine.umd.cjs +372 -371
  5. package/lib/engine/codegen/register_types.js +50 -2
  6. package/lib/engine/codegen/register_types.js.map +1 -1
  7. package/lib/engine/engine_gameobject.d.ts +1 -1
  8. package/lib/engine/engine_gameobject.js +4 -2
  9. package/lib/engine/engine_gameobject.js.map +1 -1
  10. package/lib/engine/engine_three_utils.js +2 -2
  11. package/lib/engine/engine_three_utils.js.map +1 -1
  12. package/lib/engine-components/Animation.js +4 -0
  13. package/lib/engine-components/Animation.js.map +1 -1
  14. package/lib/engine-components/codegen/components.d.ts +25 -1
  15. package/lib/engine-components/codegen/components.js +25 -1
  16. package/lib/engine-components/codegen/components.js.map +1 -1
  17. package/lib/engine-components/export/usdz/Extension.d.ts +4 -4
  18. package/lib/engine-components/export/usdz/ThreeUSDZExporter.d.ts +86 -0
  19. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +830 -0
  20. package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -0
  21. package/lib/engine-components/export/usdz/USDZExporter.d.ts +6 -3
  22. package/lib/engine-components/export/usdz/USDZExporter.js +34 -11
  23. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  24. package/lib/engine-components/export/usdz/extensions/Animation.d.ts +15 -15
  25. package/lib/engine-components/export/usdz/extensions/Animation.js +24 -29
  26. package/lib/engine-components/export/usdz/extensions/Animation.js.map +1 -1
  27. package/lib/engine-components/export/usdz/extensions/DocumentExtension.d.ts +5 -0
  28. package/lib/engine-components/export/usdz/extensions/DocumentExtension.js +7 -0
  29. package/lib/engine-components/export/usdz/extensions/DocumentExtension.js.map +1 -0
  30. package/lib/engine-components/export/usdz/extensions/USDZText.d.ts +47 -0
  31. package/lib/engine-components/export/usdz/extensions/USDZText.js +114 -0
  32. package/lib/engine-components/export/usdz/extensions/USDZText.js.map +1 -0
  33. package/lib/engine-components/export/usdz/extensions/behavior/Actions.d.ts +30 -0
  34. package/lib/engine-components/export/usdz/extensions/behavior/Actions.js +89 -0
  35. package/lib/engine-components/export/usdz/extensions/behavior/Actions.js.map +1 -0
  36. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.d.ts +23 -0
  37. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js +114 -0
  38. package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js.map +1 -0
  39. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.d.ts +96 -0
  40. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +421 -0
  41. package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -0
  42. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.d.ts +111 -0
  43. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js +409 -0
  44. package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js.map +1 -0
  45. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  46. package/lib/engine-components/ui/BaseUIComponent.d.ts +2 -0
  47. package/lib/engine-components/ui/BaseUIComponent.js +6 -0
  48. package/lib/engine-components/ui/BaseUIComponent.js.map +1 -1
  49. package/lib/engine-components/ui/Canvas.d.ts +11 -1
  50. package/lib/engine-components/ui/Canvas.js +72 -3
  51. package/lib/engine-components/ui/Canvas.js.map +1 -1
  52. package/lib/engine-components/ui/Graphic.js.map +1 -1
  53. package/lib/engine-components/ui/Image.js +4 -4
  54. package/lib/engine-components/ui/Image.js.map +1 -1
  55. package/lib/engine-components/ui/Interfaces.d.ts +11 -0
  56. package/lib/engine-components/ui/Interfaces.js +11 -0
  57. package/lib/engine-components/ui/Interfaces.js.map +1 -1
  58. package/lib/engine-components/ui/Layout.d.ts +65 -3
  59. package/lib/engine-components/ui/Layout.js +304 -3
  60. package/lib/engine-components/ui/Layout.js.map +1 -1
  61. package/lib/engine-components/ui/RectTransform.d.ts +8 -7
  62. package/lib/engine-components/ui/RectTransform.js +63 -35
  63. package/lib/engine-components/ui/RectTransform.js.map +1 -1
  64. package/lib/engine-components/utils/LookAt.d.ts +7 -1
  65. package/lib/engine-components/utils/LookAt.js +43 -6
  66. package/lib/engine-components/utils/LookAt.js.map +1 -1
  67. package/lib/engine-components/webxr/WebXRImageTracking.d.ts +4 -3
  68. package/lib/engine-components/webxr/WebXRImageTracking.js +81 -25
  69. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  70. package/lib/tsconfig.tsbuildinfo +1 -1
  71. package/package.json +1 -1
  72. package/plugins/vite/reload.js +13 -2
  73. package/src/engine/codegen/register_types.js +52 -4
  74. package/src/engine/engine_gameobject.ts +3 -2
  75. package/src/engine/engine_three_utils.ts +2 -2
  76. package/src/engine-components/Animation.ts +4 -0
  77. package/src/engine-components/codegen/components.ts +25 -1
  78. package/src/engine-components/export/usdz/Extension.ts +4 -5
  79. package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +1280 -0
  80. package/src/engine-components/export/usdz/USDZExporter.ts +39 -17
  81. package/src/engine-components/export/usdz/extensions/Animation.ts +37 -45
  82. package/src/engine-components/export/usdz/extensions/DocumentExtension.ts +10 -0
  83. package/src/engine-components/export/usdz/extensions/USDZText.ts +142 -0
  84. package/src/engine-components/export/usdz/extensions/behavior/Actions.ts +99 -0
  85. package/src/engine-components/export/usdz/extensions/behavior/Behaviour.ts +181 -0
  86. package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +503 -0
  87. package/src/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.ts +459 -0
  88. package/src/engine-components/postprocessing/PostProcessingHandler.ts +1 -1
  89. package/src/engine-components/ui/BaseUIComponent.ts +7 -1
  90. package/src/engine-components/ui/Canvas.ts +80 -5
  91. package/src/engine-components/ui/Graphic.ts +2 -0
  92. package/src/engine-components/ui/Image.ts +3 -3
  93. package/src/engine-components/ui/Interfaces.ts +30 -6
  94. package/src/engine-components/ui/Layout.ts +303 -4
  95. package/src/engine-components/ui/RectTransform.ts +65 -40
  96. package/src/engine-components/utils/LookAt.ts +60 -7
  97. package/src/engine-components/webxr/WebXRImageTracking.ts +100 -27
  98. package/lib/engine-components/export/usdz/types.d.ts +0 -34
  99. package/lib/engine-components/export/usdz/types.js +0 -2
  100. package/lib/engine-components/export/usdz/types.js.map +0 -1
  101. package/src/engine-components/export/usdz/types.ts +0 -39
@@ -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 {
19
-
23
+ get isDirty(): boolean;
24
+ markDirty();
25
+ updateTransform();
20
26
  }
21
27
 
22
28
  export interface IRectTransformChangedReceiver {
23
- onParentRectTransformChanged(comp : IRectTransform) : void;
24
- }
29
+ onParentRectTransformChanged(comp: IRectTransform): void;
30
+ }
31
+
32
+ export interface ILayoutGroup extends IComponent {
33
+ get isLayoutGroup(): boolean;
34
+ get isDirty(): boolean;
35
+ updateLayout();
36
+ }
37
+
38
+ // export abstract class LayoutGroup extends Behaviour implements IRectTransformChangedReceiver, ILayoutGroup {
39
+ // get isLayoutGroup(): boolean {
40
+ // return true;
41
+ // }
42
+ // updateLayout() {
43
+ // throw new Error("Method not implemented.");
44
+ // }
45
+ // onParentRectTransformChanged(comp: IRectTransform): void {
46
+ // throw new Error("Method not implemented.");
47
+ // }
48
+ // }
@@ -1,17 +1,316 @@
1
- import { 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 {
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
+
294
+ }
295
+
296
+ export class VerticalLayoutGroup extends HorizontalOrVerticalLayoutGroup {
297
+
298
+ protected get primaryAxis() {
299
+ return Axis.Vertical;
300
+ }
8
301
 
9
302
  }
10
303
 
11
- export class HorizontalLayoutGroup extends LayoutGroup {
304
+ export class HorizontalLayoutGroup extends HorizontalOrVerticalLayoutGroup {
305
+
306
+ protected get primaryAxis() {
307
+ return Axis.Horizontal;
308
+ }
12
309
 
13
310
  }
14
311
 
15
312
  export class GridLayoutGroup extends LayoutGroup {
313
+ protected onCalculateLayout() {
314
+ }
16
315
 
17
316
  }
@@ -7,10 +7,11 @@ import { EventSystem } from "./EventSystem";
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 @@ const tempQuaternion = new Quaternion();
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 @@ export class RectTransform extends BaseUIComponent implements IRectTransform, IR
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 @@ export class RectTransform extends BaseUIComponent implements IRectTransform, IR
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,28 +136,67 @@ export class RectTransform extends BaseUIComponent implements IRectTransform, IR
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;
168
+ }
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
+ }
149
178
  }
150
179
 
151
180
  private _parentRectTransform?: RectTransform;
181
+ private _lastUpdateFrame: number = -1;
182
+
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;
152
191
 
153
- private applyTransform() {
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);
198
+
199
+ if (debugLayout) console.warn("RectTransform → ApplyTransform", this.name + " because " + reason);
160
200
 
161
201
  if (!this.isRoot()) {
162
202
  // Reset temp matrix
@@ -183,8 +223,7 @@ export class RectTransform extends BaseUIComponent implements IRectTransform, IR
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,42 +231,28 @@ export class RectTransform extends BaseUIComponent implements IRectTransform, IR
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
-
209
- private _copyMatrixAfterRender: boolean = false;
210
-
211
- markDirty() {
212
- this._transformNeedsUpdate = true;
213
- }
214
-
215
246
 
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
- }
247
+ // const layout = GameObject.getComponentInParent(this.gameObject, ILayoutGroup);
223
248
  }
224
249
 
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
- }
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
+ // }
231
256
 
232
257
  /** applies the position offset to the passed in vector */
233
258
  private applyAnchoring(pos: Vector3) {
@@ -1,9 +1,13 @@
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";
9
+
10
+ export class LookAt extends Behaviour implements UsdzBehaviour {
7
11
 
8
12
  @serializable(Object3D)
9
13
  target?: Object3D;
@@ -11,11 +15,60 @@ export class LookAt extends Behaviour {
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);
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
+ }
20
73
  }
21
74
  }