Needle Engine

Changes between version 3.5.6-alpha and 3.5.7-alpha
Files changed (14) hide show
  1. src/engine/codegen/register_types.js +2 -2
  2. src/engine-components/ui/Canvas.ts +1 -1
  3. src/engine-components/ui/CanvasGroup.ts +6 -3
  4. src/engine/engine_addressables.ts +1 -1
  5. src/engine/engine_networking.ts +65 -34
  6. src/engine-components/ui/Graphic.ts +3 -0
  7. src/engine-components/ui/Interfaces.ts +2 -2
  8. src/engine-components/ui/RectTransform.ts +32 -28
  9. src/engine-components/SyncedRoom.ts +11 -9
  10. src/engine-components/ui/Text.ts +9 -4
  11. src/engine-components/export/usdz/ThreeUSDZExporter.ts +9 -6
  12. src/engine-components/export/usdz/USDZExporter.ts +2 -0
  13. src/engine-components/export/usdz/extensions/USDZText.ts +99 -11
  14. src/engine-components/webxr/WebXR.ts +1 -1
src/engine/codegen/register_types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TypeStore } from "./../engine_typestore"
2
-
2
+
3
3
  // Import types
4
4
  import { __Ignore } from "../../engine-components/codegen/components";
5
5
  import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
@@ -215,7 +215,7 @@
215
215
  import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering";
216
216
  import { XRRig } from "../../engine-components/webxr/WebXRRig";
217
217
  import { XRState } from "../../engine-components/XRFlag";
218
-
218
+
219
219
  // Register types
220
220
  TypeStore.add("__Ignore", __Ignore);
221
221
  TypeStore.add("ActionBuilder", ActionBuilder);
src/engine-components/ui/Canvas.ts CHANGED
@@ -208,7 +208,7 @@
208
208
  }
209
209
 
210
210
  onAfterRenderRoutine = () => {
211
- if ((this.screenspace || this.renderMode) && this.previousParent && this.context.mainCamera) {
211
+ if ((this.screenspace || this.renderOnTop) && this.previousParent && this.context.mainCamera) {
212
212
  if (this.screenspace) {
213
213
  const camObj = this.context.mainCamera;
214
214
  camObj?.add(this.gameObject);
src/engine-components/ui/CanvasGroup.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { Graphic } from "./Graphic";
2
2
  import { FrameEvent } from "../../engine/engine_setup";
3
3
  import { Behaviour, GameObject } from "../Component";
4
- import { ICanvasGroup } from "./Interfaces";
4
+ import { ICanvasGroup, IHasAlphaFactor } from "./Interfaces";
5
5
  import { serializable } from "../../engine/engine_serialization_decorator";
6
+ import { BaseUIComponent } from "./BaseUIComponent";
6
7
 
7
8
 
8
9
  export class CanvasGroup extends Behaviour implements ICanvasGroup {
@@ -40,8 +41,10 @@
40
41
 
41
42
  private _buffer: Graphic[] = [];
42
43
  private applyChangesNow() {
43
- for (const ch of GameObject.getComponentsInChildren(this.gameObject, Graphic, this._buffer)) {
44
- ch.setAlphaFactor(this._alpha);
44
+ for (const ch of GameObject.getComponentsInChildren(this.gameObject, BaseUIComponent, this._buffer)) {
45
+ const hasAlphaFactor = ch as any as IHasAlphaFactor;
46
+ if (hasAlphaFactor.setAlphaFactor)
47
+ hasAlphaFactor.setAlphaFactor(this._alpha);
45
48
  }
46
49
  }
47
50
  }
src/engine/engine_addressables.ts CHANGED
@@ -212,7 +212,6 @@
212
212
 
213
213
  private async onInstantiate(parent?: Object3D | InstantiateOptions, networked: boolean = false, saveOnServer?: boolean) {
214
214
  const context = Context.Current;
215
- if (!parent) parent = context.scene;
216
215
  if (this.mustLoad) {
217
216
  await this.loadAssetAsync();
218
217
  }
@@ -235,6 +234,7 @@
235
234
  Object.assign(options, parent);
236
235
  }
237
236
  }
237
+ if (!options.parent) options.parent = context.scene;
238
238
 
239
239
  let count = AssetReference.currentlyInstantiating.get(this.uri);
240
240
  // allow up to 10000 instantiations of the same prefab in the same frame
src/engine/engine_networking.ts CHANGED
@@ -1,8 +1,6 @@
1
- // import geckos, { ClientChannel, Data } from '@geckos.io/client';
1
+ const defaultNetworkingBackendUrlProvider = "https://urls.needle.tools/default-networking-backend/index";
2
+ let serverUrl : string | undefined = "wss://needle-tiny-starter.glitch.me/socket";
2
3
 
3
- // const serverUrl = 'wss://tiny-server-1-r26roub2hq-ew.a.run.app/';
4
- let serverUrl = 'wss://needle-tiny-starter.glitch.me/socket';
5
-
6
4
  import { Websocket, WebsocketBuilder } from 'websocket-ts';
7
5
  // import { Networking } from '../engine-components/Networking';
8
6
  import { Context } from './engine_setup';
@@ -85,6 +83,8 @@
85
83
  value: boolean;
86
84
  }
87
85
 
86
+ declare type WebsocketSendType = IModel | object | boolean | null | string | number;
87
+
88
88
  export class OwnershipModel {
89
89
 
90
90
  public guid: string;
@@ -308,8 +308,9 @@
308
308
  this.send(RoomEvents.Leave, { room: room });
309
309
  }
310
310
 
311
- public send(key: string | OwnershipEvent, data: IModel | object | boolean | null | string | number = null, queue: SendQueue = SendQueue.Queued) {
311
+ public send<T extends WebsocketSendType>(key: string | OwnershipEvent, data: T | null = null, queue: SendQueue = SendQueue.Queued) {
312
312
 
313
+ //@ts-ignore
313
314
  if (data === null) data = {};
314
315
 
315
316
  if (queue === SendQueue.Queued) {
@@ -331,7 +332,7 @@
331
332
  delete this._state[guid];
332
333
  }
333
334
 
334
- public sendDeleteRemoteStateAll(){
335
+ public sendDeleteRemoteStateAll() {
335
336
  this.send("delete-all-state");
336
337
  this._state = {};
337
338
  }
@@ -404,23 +405,25 @@
404
405
  this.netWebSocketUrlProvider = prov;
405
406
  }
406
407
 
407
- public connect() {
408
- if (this.connected) return;
408
+ public async connect() {
409
+ if (this.connected) return Promise.resolve(true);
409
410
  if (debugNet)
410
411
  console.log("connecting");
411
- // this.channel = geckos({ port: 9208, url: 'http://127.0.0.1' });
412
- // this.channel.onConnect(this.onConnectGeckosIo.bind(this));
413
- // const networking = GameObject.findObjectOfType(Networking, this.context, false);
414
412
  const overrideUrl = this.netWebSocketUrlProvider?.getWebsocketUrl();
415
413
  if (overrideUrl) {
416
414
  serverUrl = overrideUrl;
417
415
  }
418
- else if(isHostedOnGlitch()) {
416
+ else if (isHostedOnGlitch()) {
419
417
  serverUrl = "wss://" + window.location.host + "/socket";
420
418
  }
421
- this.connectWebsocket();
419
+ return this.connectWebsocket();
422
420
  };
423
421
 
422
+ public disconnect() {
423
+ this._ws?.close();
424
+ this._ws = undefined;
425
+ }
426
+
424
427
  private _listeners: { [key: string]: Function[] } = {};
425
428
  private _listenersBinary: { [key: string]: BinaryCallback[] } = {};
426
429
  private connected: boolean = false;
@@ -428,7 +431,6 @@
428
431
  private _connectionId: string | null = null;
429
432
 
430
433
  // Websocket ------------------------------------------------------------
431
- private _isConnectingToWebsocket: boolean = false;
432
434
  private _ws: Websocket | undefined;
433
435
  private _waitingForSocket: { [key: string]: Array<Function> } = {};
434
436
  private _isInRoom: boolean = false;
@@ -439,26 +441,55 @@
439
441
  private _state: { [key: string]: any } = {};
440
442
  private _currentDelay: number = -1;
441
443
 
444
+ private _connectingToWebsocketPromise: Promise<boolean> | null = null;
445
+
442
446
  private connectWebsocket() {
443
- if (this._isConnectingToWebsocket) return;
444
- this._isConnectingToWebsocket = true;
445
- console.log("Connecting to " + serverUrl)
446
- const ws = new WebsocketBuilder(serverUrl)
447
- .onOpen(() => {
448
- this._ws = ws;
449
- this._isConnectingToWebsocket = false;
450
- this.connected = true;
451
- console.log("Connected to websocket");
452
- this.onSendQueued(SendQueue.OnConnection);
453
- })
454
- .onClose((_evt) => {
455
- this.connected = false;
456
- this._isInRoom = false;
457
- })
458
- .onError((i, ev) => { console.error(i, ev) })
459
- .onMessage(this.onMessage.bind(this))
460
- .onRetry(() => { console.log("websocket connection retry") })
461
- .build();
447
+ if (this._connectingToWebsocketPromise) return this._connectingToWebsocketPromise;
448
+ return this._connectingToWebsocketPromise = new Promise(async (res, _) => {
449
+ let didResolve = false;
450
+ const resolve = (val: boolean) => {
451
+ if (didResolve) return;
452
+ didResolve = true;
453
+ res(val);
454
+ }
455
+ if(serverUrl === undefined){
456
+ console.log("Fetch default backend url: " + defaultNetworkingBackendUrlProvider);
457
+ let failed = false;
458
+ const defaultUrlResponse = await fetch(defaultNetworkingBackendUrlProvider);
459
+ serverUrl = await defaultUrlResponse.text();
460
+ if(failed) return;
461
+ }
462
+
463
+ if(serverUrl === undefined){
464
+ resolve(false);
465
+ return;
466
+ }
467
+
468
+ console.log("⊡ Connecting to networking backend on\n" + serverUrl)
469
+ const ws = new WebsocketBuilder(serverUrl)
470
+ .onOpen(() => {
471
+ this._connectingToWebsocketPromise = null;
472
+ this._ws = ws;
473
+ this.connected = true;
474
+ console.log("⊞ Connected to networking backend\n" + serverUrl);
475
+ resolve(true);
476
+ this.onSendQueued(SendQueue.OnConnection);
477
+ })
478
+ .onClose((_evt) => {
479
+ this._connectingToWebsocketPromise = null;
480
+ this.connected = false;
481
+ this._isInRoom = false;
482
+ resolve(false);
483
+ })
484
+ .onError((i, ev) => {
485
+ console.error("⊠ Websocket error", i, ev);
486
+ resolve(false);
487
+ })
488
+ .onMessage(this.onMessage.bind(this))
489
+ .onRetry(() => { console.log("Retry connecting to networking websocket") })
490
+ .build();
491
+
492
+ });
462
493
  }
463
494
 
464
495
  private onMessage(_, ev) {
@@ -575,7 +606,7 @@
575
606
  break;
576
607
 
577
608
  case "all-room-state-deleted":
578
- if(debugNet) console.log("RECEIVED all-room-state-deleted");
609
+ if (debugNet) console.log("RECEIVED all-room-state-deleted");
579
610
  this._state = {};
580
611
  break;
581
612
 
src/engine-components/ui/Graphic.ts CHANGED
@@ -40,6 +40,9 @@
40
40
  this._alphaFactor = factor;
41
41
  this.onColorChanged();
42
42
  }
43
+ get alphaFactor() {
44
+ return this._alphaFactor;
45
+ }
43
46
 
44
47
  protected onColorChanged() {
45
48
  if (this.uiObject) {
src/engine-components/ui/Interfaces.ts CHANGED
@@ -14,11 +14,11 @@
14
14
  interactable: boolean;
15
15
  }
16
16
 
17
- export interface IHasImage {
17
+ export interface IHasAlphaFactor {
18
18
  setAlphaFactor(val: number);
19
19
  }
20
20
 
21
- export interface IGraphic extends IComponent, IHasImage {
21
+ export interface IGraphic extends IComponent, IHasAlphaFactor {
22
22
  get isGraphic(): boolean;
23
23
  raycastTarget: boolean;
24
24
  }
src/engine-components/ui/RectTransform.ts CHANGED
@@ -2,12 +2,11 @@
2
2
  import { BaseUIComponent } from "./BaseUIComponent";
3
3
  import { DocumentedOptions as ThreeMeshUIEveryOptions } from "three-mesh-ui/build/types/core/elements/MeshUIBaseElement";
4
4
  import { serializable } from "../../engine/engine_serialization_decorator";
5
- import { Color, Matrix4, Object3D, Quaternion, Vector2, Vector3 } from "three";
6
- import { EventSystem } from "./EventSystem";
5
+ import { Matrix4, Object3D, Quaternion, Vector2, Vector3 } from "three";
7
6
  import { getParam } from "../../engine/engine_utils";
8
7
  import { onChange } from "./Utils";
9
8
  import { foreachComponentEnumerator } from "../../engine/engine_gameobject";
10
- import { ICanvas, IRectTransform, IRectTransformChangedReceiver, ILayoutGroup } from "./Interfaces";
9
+ import { ICanvas, IRectTransform, IRectTransformChangedReceiver } from "./Interfaces";
11
10
  import { GameObject } from '../Component';
12
11
 
13
12
  const debug = getParam("debugui");
@@ -43,14 +42,14 @@
43
42
  get rotation() { return this.gameObject.quaternion; }
44
43
  get scale(): Vector3 { return this.gameObject.scale; }
45
44
 
46
- private _anchoredPosition!: Vector3;
45
+ private _anchoredPosition!: Vector2;
47
46
 
48
- @serializable(Vector3)
47
+ @serializable(Vector2)
49
48
  get anchoredPosition() {
50
- if (!this._anchoredPosition) this._anchoredPosition = new Vector3();
49
+ if (!this._anchoredPosition) this._anchoredPosition = new Vector2();
51
50
  return this._anchoredPosition;
52
51
  }
53
- private set anchoredPosition(value: Vector3) {
52
+ private set anchoredPosition(value: Vector2) {
54
53
  this._anchoredPosition = value;
55
54
  }
56
55
 
@@ -99,17 +98,24 @@
99
98
  private lastMatrix!: Matrix4;
100
99
  private rectBlock!: Object3D;
101
100
  private _transformNeedsUpdate: boolean = false;
101
+ private _initialPosition!: Vector3;
102
102
 
103
103
  awake() {
104
104
  super.awake();
105
+ // this is required if an animator animated the transform anchoring
106
+ if (!this._anchoredPosition)
107
+ this._anchoredPosition = new Vector2();
108
+
105
109
  this.lastMatrix = new Matrix4();
106
110
  this.rectBlock = new Object3D();
107
111
  // Is this legacy? Not sure if this is still needed
108
112
  this.rectBlock.position.z = .1;
109
113
  this.rectBlock.name = this.name;
110
114
 
111
- // this is required if an animator animated the transform anchoring
112
- if (!this._anchoredPosition) this._anchoredPosition = new Vector3();
115
+ // TODO: get rid of the initial position
116
+ this._initialPosition = this.gameObject.position.clone();
117
+ this._initialPosition.z = 0;
118
+ this.onApplyTransform("RectTransform awake");
113
119
 
114
120
  // TODO: we need to replace this with the watch that e.g. Rigibody is using (or the one in utils?)
115
121
  // perhaps we can also just manually check the few properties in the update loops?
@@ -119,20 +125,6 @@
119
125
  onChange(this, "pivot", () => { this.markDirty(); });
120
126
  onChange(this, "anchorMin", () => { this.markDirty(); });
121
127
  onChange(this, "anchorMax", () => { this.markDirty(); });
122
-
123
- // When exported with an anchored position offset we remove it here
124
- // because it would otherwise be applied twice when the anchoring is animated
125
- // Maybe we can get rid of this workaround if we just set the mesh ui position from the
126
- // anchored position value but then we would have to make sure if a user/the engine updates
127
- // "position" the change would also land in anchoredPosition
128
- // Another solution would perhaps be to get rid of the extra "anchoredPosition" vector3
129
- // and instead use the same vector3 instance on both "position" and "anchoredPosition"
130
- // But I'm also not sure if this will not cause issues elsewhere later / be confusing?
131
- // (that being said we can make anchoredPosition hidden)
132
- if (!this.isRoot()) {
133
- this.gameObject.position.x += this.anchoredPosition.x;
134
- this.gameObject.position.y -= this.anchoredPosition.y;
135
- }
136
128
  }
137
129
 
138
130
  onEnable() {
@@ -155,7 +147,7 @@
155
147
  }
156
148
 
157
149
  get isDirty() {
158
- if(!this._transformNeedsUpdate) this._transformNeedsUpdate = !this.lastMatrix.equals(this.gameObject.matrix);
150
+ if (!this._transformNeedsUpdate) this._transformNeedsUpdate = !this.lastMatrix.equals(this.gameObject.matrix);
159
151
  return this._transformNeedsUpdate;
160
152
  }
161
153
 
@@ -199,7 +191,6 @@
199
191
  else
200
192
  this._parentRectTransform = undefined;
201
193
  this._transformNeedsUpdate = false;
202
- this.lastMatrix.copy(this.gameObject.matrix);
203
194
 
204
195
  if (debugLayout) console.warn("RectTransform → ApplyTransform", this.name + " because " + reason);
205
196
 
@@ -223,7 +214,8 @@
223
214
  tempVec.set(0, 0, 0);
224
215
  this.applyAnchoring(tempVec);
225
216
  tempVec.z += this.offset;
226
- tempVec.z -= this.gameObject.position.z;
217
+ // tempVec.z -= this.gameObject.position.z;
218
+
227
219
  tempMatrix.identity();
228
220
  tempMatrix.setPosition(tempVec.x, tempVec.y, tempVec.z);
229
221
  uiobject.matrix.premultiply(tempMatrix);
@@ -236,6 +228,8 @@
236
228
  if (!canvas.screenspace) uiobject.rotation.y = Math.PI;
237
229
  }
238
230
 
231
+ this.lastMatrix.copy(this.gameObject.matrix);
232
+
239
233
  // iterate other components on this object that might need to know about the transform change
240
234
  // e.g. Graphic components should update their width and height
241
235
  const includeChildren = true;
@@ -259,11 +253,21 @@
259
253
  // }
260
254
  // }
261
255
 
256
+ private _lastAnchoring!: Vector2;
257
+
262
258
  /** applies the position offset to the passed in vector */
263
259
  private applyAnchoring(pos: Vector3) {
264
- pos.x += this.anchoredPosition.x;
265
- pos.y += this.anchoredPosition.y;
266
260
 
261
+ if (!this._lastAnchoring) this._lastAnchoring = new Vector2();
262
+ const diff = this._lastAnchoring.sub(this._anchoredPosition)
263
+ this.gameObject.position.x += diff.x;
264
+ this.gameObject.position.y += diff.y;
265
+ this._lastAnchoring.copy(this._anchoredPosition);
266
+
267
+ pos.x += (this._initialPosition.x - this.gameObject.position.x);
268
+ pos.y += (this._initialPosition.y - this.gameObject.position.y);
269
+ pos.z += (this._initialPosition.z - this.gameObject.position.z);
270
+
267
271
  const parent = this._parentRectTransform;
268
272
  if (parent) {
269
273
  // Calculate vertical offset
src/engine-components/SyncedRoom.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Behaviour } from "./Component";
2
2
  import * as utils from "../engine/engine_utils"
3
3
  import { serializable } from "../engine/engine_serialization_decorator";
4
+ import { getParam } from "../engine/engine_utils";
4
5
 
5
6
  const viewParamName = "view";
6
7
  const debug = utils.getParam("debugsyncedroom");
@@ -40,7 +41,9 @@
40
41
  this.context.connection.joinRoom(viewId, true);
41
42
  return;
42
43
  }
43
- this.tryJoinRoom();
44
+ // If setup to join a random room
45
+ if (this.joinRandomRoom || getParam(this.urlParameterName))
46
+ this.tryJoinRoom();
44
47
  }
45
48
 
46
49
  onDisable(): void {
@@ -48,6 +51,13 @@
48
51
  this.context.connection.leaveRoom(this.roomName);
49
52
  }
50
53
 
54
+ /** Will generate a random room name, set it as an URL parameter and attempt to join the room */
55
+ tryJoinRandomRoom() {
56
+ this.setRandomRoomUrlParameter();
57
+ this.tryJoinRoom();
58
+ }
59
+
60
+ /** Try to join the currently set roomName */
51
61
  tryJoinRoom(call: number = 0): boolean {
52
62
  if (call === undefined) call = 0;
53
63
  let hasRoomParameter = false;
@@ -78,12 +88,6 @@
78
88
  return false;
79
89
  }
80
90
 
81
- if (!this.roomName || this.roomName.length <= 0) {
82
- if (debug)
83
- console.error("Missing room name on \"" + this.name + "\". Make sure this is correctly configured in Unity", this.context.connection.isDebugEnabled ? this : "");
84
- return false;
85
- }
86
-
87
91
  if (!this.context.connection.isConnected) {
88
92
  this.context.connection.connect();
89
93
  }
@@ -157,8 +161,6 @@
157
161
  urlParams.set(viewParamName, this.context.connection.currentRoomViewId);
158
162
  return window.location.origin + window.location.pathname + "?" + urlParams.toString();
159
163
  }
160
-
161
-
162
164
  return null;
163
165
  }
164
166
  }
src/engine-components/ui/Text.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  import { Canvas } from './Canvas';
7
7
  import { serializable } from '../../engine/engine_serialization_decorator';
8
8
  import { getParam, resolveUrl } from '../../engine/engine_utils';
9
- import { ICanvas } from './Interfaces';
9
+ import { ICanvas, IHasAlphaFactor } from './Interfaces';
10
10
 
11
11
  const debug = getParam("debugtext");
12
12
 
@@ -38,7 +38,7 @@
38
38
  BoldAndItalic = 3,
39
39
  }
40
40
 
41
- export class Text extends Graphic {
41
+ export class Text extends Graphic implements IHasAlphaFactor {
42
42
 
43
43
  @serializable()
44
44
  alignment: TextAnchor = TextAnchor.UpperLeft;
@@ -55,6 +55,13 @@
55
55
  @serializable()
56
56
  fontStyle: FontStyle = FontStyle.Normal;
57
57
 
58
+ // private _alphaFactor : number = 1;
59
+ setAlphaFactor(factor: number): void {
60
+ super.setAlphaFactor(factor);
61
+ this.uiObject?.set({ fontOpacity: this.color.alpha * this.alphaFactor });
62
+ this.markDirty();
63
+ }
64
+
58
65
  @serializable()
59
66
  get text(): string {
60
67
  return this._text;
@@ -78,11 +85,9 @@
78
85
  }
79
86
 
80
87
  set fontSize(val: number) {
81
-
82
88
  // Setting that kind of property in a parent, would cascade to each 'non-overrided' children.
83
89
  this._fontSize = val;
84
90
  this.uiObject?.set({ fontSize: val });
85
-
86
91
  }
87
92
 
88
93
 
src/engine-components/export/usdz/ThreeUSDZExporter.ts CHANGED
@@ -661,12 +661,6 @@
661
661
 
662
662
  }
663
663
 
664
- if ( ! ( material.uuid in context.materials ) ) {
665
-
666
- context.materials[ material.uuid ] = material;
667
-
668
- }
669
-
670
664
  } else {
671
665
 
672
666
  console.warn( 'THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)', name );
@@ -675,6 +669,15 @@
675
669
 
676
670
  }
677
671
 
672
+ if( material ) {
673
+
674
+ if ( ! ( material.uuid in context.materials ) ) {
675
+
676
+ context.materials[ material.uuid ] = material;
677
+
678
+ }
679
+ }
680
+
678
681
  for ( const ch of object.children ) {
679
682
 
680
683
  addResources( ch, context );
src/engine-components/export/usdz/USDZExporter.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  import { hasProLicense } from "../../../engine/engine_license";
16
16
  import { BehaviorExtension } from "./extensions/behavior/Behaviour";
17
17
  import { AudioExtension } from "./extensions/behavior/AudioExtension";
18
+ import { TextExtension } from "./extensions/USDZText";
18
19
 
19
20
  const debug = getParam("debugusdz");
20
21
 
@@ -96,6 +97,7 @@
96
97
  if (this.interactive) {
97
98
  this.extensions.push(new BehaviorExtension());
98
99
  this.extensions.push(new AudioExtension());
100
+ this.extensions.push(new TextExtension());
99
101
  }
100
102
  }
101
103
 
src/engine-components/export/usdz/extensions/USDZText.ts CHANGED
@@ -1,5 +1,11 @@
1
+ import { IUSDExporterExtension } from "../Extension";
1
2
  import { IBehaviorElement } from "../extensions/behavior/BehavioursBuilder";
2
- import { USDDocument, USDWriter } from "../ThreeUSDZExporter";
3
+ import { USDDocument, USDObject, USDWriter, USDZExporterContext } from "../ThreeUSDZExporter";
4
+ import { GameObject } from "../../../Component";
5
+ import { Text } from "../../../ui/Text"
6
+ import { RectTransform } from "../../../ui/RectTransform";
7
+ import { Color, Material, Matrix4, MeshStandardMaterial, Object3D, Vector3 } from "three";
8
+ import { TextAnchor } from "../../../ui/Text";
3
9
 
4
10
 
5
11
  export enum TextWrapMode {
@@ -41,6 +47,8 @@
41
47
  horizontalAlignment?: HorizontalAlignment;
42
48
  verticalAlignment?: VerticalAlignment;
43
49
 
50
+ material?: Material;
51
+
44
52
  setDepth(depth: number): USDZText {
45
53
  this.depth = depth;
46
54
  return this;
@@ -94,6 +102,10 @@
94
102
  if (this.verticalAlignment)
95
103
  writer.appendLine(`token verticalAlignment = "${this.verticalAlignment}"`);
96
104
 
105
+ if (this.material !== undefined) {
106
+ writer.appendLine(`rel material:binding = </Materials/Material_${this.material.id}>`)
107
+ }
108
+
97
109
  writer.closeBlock();
98
110
 
99
111
  }
@@ -120,23 +132,99 @@
120
132
  text.height = height;
121
133
  text.horizontalAlignment = horizontal;
122
134
  text.verticalAlignment = vertical;
123
- if (wrapMode)
135
+ if (wrapMode !== undefined)
124
136
  text.wrapMode = wrapMode;
125
137
  return text;
126
138
  }
127
139
  }
128
140
 
141
+ const rotateYAxisMatrix = new Matrix4().makeRotationY(Math.PI);
129
142
 
130
- export class TextExtension {
131
- onExportObject(_object, model, _context) {
132
- model.addEventListener("serialize", (writer, _context) => {
133
- const text = TextBuilder.multiLine("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
134
- 1, 1, HorizontalAlignment.justified, VerticalAlignment.top);
135
- text.pointSize = 300;
136
- text.depth = .01;
137
- text.writeTo(undefined, writer);
138
- });
143
+ export class TextExtension implements IUSDExporterExtension {
144
+ get extensionName(): string {
145
+ return "text";
139
146
  }
147
+
148
+ onExportObject(object: Object3D, model: USDObject, _context: USDZExporterContext) {
149
+
150
+ const text = GameObject.getComponent(object, Text);
151
+ if (text) {
152
+ const rt = GameObject.getComponent(object, RectTransform);
153
+ let width = 100;
154
+ let height = 100;
155
+ if (rt) {
156
+ width = rt.width;
157
+ height = rt.height;
158
+ }
159
+ const newModel = model.clone();
160
+ newModel.matrix = rotateYAxisMatrix.clone();
161
+ const color = new Color().copySRGBToLinear(text.color);
162
+ newModel.material = new MeshStandardMaterial({ color: color, emissive: color });
163
+ model.add(newModel);
164
+
165
+ // model.matrix.scale(new Vector3(100, 100, 100));
166
+ newModel.addEventListener("serialize", (writer: USDWriter, _context: USDZExporterContext) => {
167
+ const textObj = TextBuilder.multiLine(text.text, width, height, HorizontalAlignment.center, VerticalAlignment.bottom, TextWrapMode.flowing);
168
+ this.setTextAlignment(textObj, text.alignment);
169
+ this.setOverflow(textObj, text);
170
+ if (newModel.material)
171
+ textObj.material = newModel.material;
172
+ textObj.pointSize = this.convertToTextSize(text.fontSize);
173
+ textObj.depth = .001;
174
+ textObj.writeTo(undefined, writer);
175
+ });
176
+ }
177
+ }
178
+
179
+ private convertToTextSize(pixel: number) {
180
+ return 1 / 0.0502 * 144 * pixel;
181
+ }
182
+
183
+ private setOverflow(textObj: USDZText, text: Text) {
184
+ if (text.horizontalOverflow) {
185
+ textObj.wrapMode = TextWrapMode.singleLine;
186
+ }
187
+ else {
188
+ textObj.wrapMode = TextWrapMode.flowing;
189
+ }
190
+ }
191
+
192
+ private setTextAlignment(text: USDZText, alignment: TextAnchor) {
193
+ switch (alignment) {
194
+ case TextAnchor.LowerLeft:
195
+ case TextAnchor.MiddleLeft:
196
+ case TextAnchor.UpperLeft:
197
+ text.horizontalAlignment = HorizontalAlignment.left;
198
+ break;
199
+ case TextAnchor.LowerCenter:
200
+ case TextAnchor.MiddleCenter:
201
+ case TextAnchor.UpperCenter:
202
+ text.horizontalAlignment = HorizontalAlignment.center;
203
+ break;
204
+ case TextAnchor.LowerRight:
205
+ case TextAnchor.MiddleRight:
206
+ case TextAnchor.UpperRight:
207
+ text.horizontalAlignment = HorizontalAlignment.right;
208
+ break;
209
+ }
210
+ switch (alignment) {
211
+ case TextAnchor.LowerLeft:
212
+ case TextAnchor.LowerCenter:
213
+ case TextAnchor.LowerRight:
214
+ text.verticalAlignment = VerticalAlignment.bottom;
215
+ break;
216
+ case TextAnchor.MiddleLeft:
217
+ case TextAnchor.MiddleCenter:
218
+ case TextAnchor.MiddleRight:
219
+ text.verticalAlignment = VerticalAlignment.middle;
220
+ break;
221
+ case TextAnchor.UpperLeft:
222
+ case TextAnchor.UpperCenter:
223
+ case TextAnchor.UpperRight:
224
+ text.verticalAlignment = VerticalAlignment.top;
225
+ break;
226
+ }
227
+ }
140
228
  }
141
229
 
142
230
 
src/engine-components/webxr/WebXR.ts CHANGED
@@ -241,7 +241,7 @@
241
241
  this.context.domElement.append(buttonsContainer);
242
242
 
243
243
  // AR support
244
- // if (this.enableAR && this.createARButton && arSupported)
244
+ if (this.enableAR && this.createARButton && arSupported)
245
245
  {
246
246
  arButton = WebXR.createARButton(this);
247
247
  this._arButton = arButton;